`
mqzhuang
  • 浏览: 187277 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Glibc内存管理--ptmalloc2源代码分析(二十三)

阅读更多

5.6.5 new_heap()

New_heap() 函数负责从 mmap 区域映射一块内存来作为 sub_heap ,在 32 位系统上,该函数每次映射 1M 内存,映射的内存块地址按 1M 对齐;在 64 为系统上,该函数映射 64M 内存,映射的内存块地址按 64M 对齐。 New_heap() 函数只是映射一块虚拟地址空间,该空间不可读写,不会被 swap New_heap() 函数的实现源代码如下:

/* If consecutive mmap (0, HEAP_MAX_SIZE << 1, ...) calls return decreasing
   addresses as opposed to increasing, new_heap would badly fragment the
   address space.  In that case remember the second HEAP_MAX_SIZE part
   aligned to HEAP_MAX_SIZE from last mmap (0, HEAP_MAX_SIZE << 1, ...)
   call (if it is already aligned) and try to reuse it next time.  We need
   no locking for it, as kernel ensures the atomicity for us - worst case
   we'll call mmap (addr, HEAP_MAX_SIZE, ...) for some value of addr in
   multiple threads, but only one will succeed.  */
static char *aligned_heap_area;

/* Create a new heap.  size is automatically rounded up to a multiple
   of the page size. */
static heap_info *
internal_function
#if __STD_C
new_heap(size_t size, size_t top_pad)
#else
new_heap(size, top_pad) size_t size, top_pad;
#endif
{
  size_t page_mask = malloc_getpagesize - 1;
  char *p1, *p2;
  unsigned long ul;
  heap_info *h;

  if(size+top_pad < HEAP_MIN_SIZE)
    size = HEAP_MIN_SIZE;
  else if(size+top_pad <= HEAP_MAX_SIZE)
    size += top_pad;
  else if(size > HEAP_MAX_SIZE)
    return 0;
  else
    size = HEAP_MAX_SIZE;
  size = (size + page_mask) & ~page_mask;

 调整size 的大小, size 的最小值为 32K, 最大值 HEAP_MAX_SIZE 在不同的系统上不同,在 32 位系统为 1M 64 位系统为 64M ,将 size 的大小调整到最小值与最大值之间,并以页对齐,如果 size 大于最大值,直接报错。

/* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.
     No swap space needs to be reserved for the following large
     mapping (on Linux, this is the case for all non-writable mappings
     anyway). */
  p2 = MAP_FAILED;
  if(aligned_heap_area) {
    p2 = (char *)MMAP(aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
                      MAP_PRIVATE|MAP_NORESERVE);
    aligned_heap_area = NULL;
    if (p2 != MAP_FAILED && ((unsigned long)p2 & (HEAP_MAX_SIZE-1))) {
      munmap(p2, HEAP_MAX_SIZE);
      p2 = MAP_FAILED;
    }
  }

 全局变量 aligned_heap_area 是上一次调用 mmap 分配内存的结束虚拟地址,并已经按照 HEAP_MAX_SIZE 大小对齐。如果 aligned_heap_area 不为空,尝试从上次映射结束地址开始映射大小为 HEAP_MAX_SIZE 的内存块,由于全局变量 aligned_heap_area 没有锁保护,可能存在多个线程同时 mmap() 函数从 aligned_heap_area 开始映射新的虚拟内存块,操作系统会保证只会有一个线程会成功,其它在同一地址映射新虚拟内存块都会失败。无论映射是否成功,都将全局变量 aligned_heap_area 设置为 NULL 。如果映射成功,但返回的虚拟地址不是按 HEAP_MAX_SIZE 大小对齐的,取消该区域的映射,映射失败。

if(p2 == MAP_FAILED) {
    p1 = (char *)MMAP(0, HEAP_MAX_SIZE<<1, PROT_NONE,
                      MAP_PRIVATE|MAP_NORESERVE);

 全局变量 aligned_heap_area NULL ,或者从 aligned_heap_area 开始映射失败了,尝试映射 2 HEAP_MAX_SIZE 大小的虚拟内存,便于地址对齐,因为在最坏可能情况下,需要映射 2 HEAP_MAX_SIZE 大小的虚拟内存才能实现地址按照 HEAP_MAX_SIZE 大小对齐。

 if(p1 != MAP_FAILED) {
      p2 = (char *)(((unsigned long)p1 + (HEAP_MAX_SIZE-1))
                    & ~(HEAP_MAX_SIZE-1));
      ul = p2 - p1;
      if (ul)
        munmap(p1, ul);
      else
        aligned_heap_area = p2 + HEAP_MAX_SIZE;
      munmap(p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);

 映射2 HEAP_MAX_SIZE 大小的虚拟内存成功,将大于等于 p1 并按 HEAP_MAX_SIZE 大小对齐的第一个虚拟地址赋值给 p2 p2 作为 sub_heap 的起始虚拟地址, p2+ HEAP_MAX_SIZE 作为 sub_heap 的结束地址,并将 sub_heap 的结束地址赋值给全局变量 aligned_heap_area ,最后还需要将多余的虚拟内存还回给操作系统。

 } else {
      /* Try to take the chance that an allocation of only HEAP_MAX_SIZE
         is already aligned. */
      p2 = (char *)MMAP(0, HEAP_MAX_SIZE, PROT_NONE, MAP_PRIVATE|MAP_NORESERVE);
      if(p2 == MAP_FAILED)
        return 0;
      if((unsigned long)p2 & (HEAP_MAX_SIZE-1)) {
        munmap(p2, HEAP_MAX_SIZE);
        return 0;
      }

 映射2 HEAP_MAX_SIZE 大小的虚拟内存失败了,再尝试映射 HEAP_MAX_SIZE 大小的虚拟内存,如果失败,返回;如果成功,但该虚拟地址不是按照 HEAP_MAX_SIZE 大小对齐的,返回。

 }
  }
  if(mprotect(p2, size, PROT_READ|PROT_WRITE) != 0) {
    munmap(p2, HEAP_MAX_SIZE);
    return 0;
  }
  h = (heap_info *)p2;
  h->size = size;
  h->mprotect_size = size;
  THREAD_STAT(stat_n_heaps++);

调用 mprotect() 函数将 size 大小的内存设置为可读可写,如果失败,解除整个 sub_heap 的映射。然后更新 heap_info 实例中的相关字段。

 
  return h;
}

  5.6.6 get_free_list()和reused_arena()

两个函数在开启了 PER_THRAD 优化时用于获取分配区( arena ), arena_get2 首先调用 get_free_list() 尝试获得 arena ,如果失败在尝试调用 reused_arena() 获得 arena ,如果仍然没有获得分配区,调用 _int_new_arena() 创建一个新的分配区。 Get_free_list() 函数的源代码如下:

static mstate
get_free_list (void)
{
  mstate result = free_list;
  if (result != NULL)
    {
      (void)mutex_lock(&list_lock);
      result = free_list;
      if (result != NULL)
        free_list = result->next_free;
      (void)mutex_unlock(&list_lock);

      if (result != NULL)
        {
          (void)mutex_lock(&result->mutex);
          tsd_setspecific(arena_key, (Void_t *)result);
          THREAD_STAT(++(result->stat_lock_loop));
        }
    }

  return result;
}

 这个函数实现很简单,首先查看 arena free_list 中是否为 NULL ,如果不为 NULL ,获得全局锁 list_lock ,将 free_list 的第一个 arena 从单向链表中取出,解锁 list_lock 。如果从 free_list 中获得一个 arena ,对该 arena 加锁,并将该 arena 加入线程的私有实例中。

reused_arena() 函数的源代码实现如下:

static mstate
reused_arena (void)
{
  if (narenas <= mp_.arena_test)
    return NULL;

 首先判断全局分配区的总数是否小于分配区的个数的限定值( arena_test ), 32 位系统上 arena_test 默认值为 2 64 位系统上的默认值为 8 ,如果当前进程的分配区数量没有达到限定值,直接返回 NULL

static int narenas_limit;
  if (narenas_limit == 0)
    {
      if (mp_.arena_max != 0)
        narenas_limit = mp_.arena_max;
      else
        {
          int n  = __get_nprocs ();

          if (n >= 1)
            narenas_limit = NARENAS_FROM_NCORES (n);
          else
            /* We have no information about the system.  Assume two
               cores.  */
            narenas_limit = NARENAS_FROM_NCORES (2);
        }
    }

  if (narenas < narenas_limit)
    return NULL;

 设定全局变量 narenas_limit ,如果应用层设置了进程的最大分配区个数( arena_max ),将 arena_max 赋值给 narenas_limit ,否则根据系统的 cpu 个数和系统的字大小设定 narenas_limit 的大小, narenas_limit 的大小默认与 arena_test 大小相同。然后再次判断进程的当前分配区个数是否达到了分配区的限制个数,如果没有达到限定值,返回。

mstate result;
  static mstate next_to_use;
  if (next_to_use == NULL)
    next_to_use = &main_arena;

  result = next_to_use;
  do
    {
      if (!mutex_trylock(&result->mutex))
        goto out;

      result = result->next;
    }
  while (result != next_to_use);

  /* No arena available.  Wait for the next in line.  */
  (void)mutex_lock(&result->mutex);

 out:
  tsd_setspecific(arena_key, (Void_t *)result);
  THREAD_STAT(++(result->stat_lock_loop));
  next_to_use = result->next;

 全局变量 next_to_use 指向下一个可能可用的分配区,该全局变量没有锁保护,主要用于记录上次遍历分配区循环链表到达的位置,避免每次都从同一个分配区开始遍历,导致从某个分配区分配的内存过多。首先判断 next_to_use 是否为 NULL ,如果是,将主分配区赋值给 next_to_use 。然后从 next_to_use 开始遍历分配区链表,尝试对遍历的分配区加锁,如果加锁成功,退出循环,如果遍历分配区循环链表中的所有分配区,尝试加锁都失败了,等待获得 next_to_use 指向的分配区的锁。执行到 out 的代码,意味着已经获得一个分配区的锁,将该分配区加入线程私有实例,并将当前分配区的下一个分配区赋值给 next_to_use

  return result;
}

 

 

 

 

 

分享到:
评论

相关推荐

    Glibc内存管理--ptmalloc2源代码分析(三十四)

    《Glibc内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...

    glibc内存管理ptmalloc源代码分析PDF

    在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...

    glibc内存管理ptmalloc源代码分析-高清PDF-pdf版

    《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...

    glibc内存管理ptmalloc源代码分析@华庭1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...

    glibc内存管理ptmalloc源代码分析4.pdf

    标题与描述概述的知识点主要集中在glibc内存管理的细节,特别是ptmalloc的源代码分析,这对于深入理解C库如何高效地处理内存分配和回收至关重要。以下是对这些知识点的详细解析: ### Glibc内存管理与ptmalloc ###...

    glibc内存管理ptmalloc源代码分析.pdf

    glibc内存管理ptmalloc源代码分析

    glibc内存管理ptmalloc源代码分析1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...

    glibc内存管理ptmalloc源代码分析

    ### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...

    glibc内存管理ptmalloc源代码分析-清晰版.pdf

    ### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...

    Python-用于检测glibc堆ptmalloc的gdbpython库

    `glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...

    内存管理与调试详细剖析

    本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...

    malloc源码分析glibc库

    通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...

    pwn学习总结(八)—— 堆(持续更新)

    学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...

    堆漏洞的利用技巧1

    《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。

Global site tag (gtag.js) - Google Analytics