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

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

Go 
阅读更多

5.6.7 grow_heap,shrink_heap,delete_heap,heap_trim

 

几个函数实现 sub_heap 和增长和收缩, grow_heap() 函数主要将 sub_heap 中可读可写区域扩大; shrink_heap() 函数缩小 sub_heap 的虚拟内存区域,减小该 sub_heap 的虚拟内存占用量; delete_heap() 为一个宏,如果 sub_heap 中所有的内存都空闲,使用该宏函数将 sub_heap 的虚拟内存还回给操作系统; heap_trim() 函数根据 sub_heap top chunk 大小调用 shrink_heap() 函数收缩 sub_heap

函数的实现代码如下:

static int
#if __STD_C
grow_heap(heap_info *h, long diff)
#else
grow_heap(h, diff) heap_info *h; long diff;
#endif
{
  size_t page_mask = malloc_getpagesize - 1;
  long new_size;

  diff = (diff + page_mask) & ~page_mask;
  new_size = (long)h->size + diff;
  if((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)
    return -1;
  if((unsigned long) new_size > h->mprotect_size) {
    if (mprotect((char *)h + h->mprotect_size,
                 (unsigned long) new_size - h->mprotect_size,
                 PROT_READ|PROT_WRITE) != 0)
      return -2;
    h->mprotect_size = new_size;
  }

  h->size = new_size;
  return 0;
}

 Grow_heap() 函数的实现比较简单,首先将要增加的可读可写的内存大小按照页对齐,然后计算 sub_heap 总的可读可写的内存大小 new_size ,判断 new_size 是否大于 HEAP_MAX_SIZE ,如果是,返回,否则判断 new_size 是否大于当前 sub_heap 的可读可写区域大小,如果否,调用 mprotect() 设置新增的区域可读可写,并更新当前 sub_heap 的可读可写区域的大小为 new_size 。最后将当前 sub_heap 的字段 size 更新为 new_size

 

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

static int
#if __STD_C
shrink_heap(heap_info *h, long diff)
#else
shrink_heap(h, diff) heap_info *h; long diff;
#endif
{
  long new_size;

  new_size = (long)h->size - diff;
  if(new_size < (long)sizeof(*h))
    return -1;
  /* Try to re-map the extra heap space freshly to save memory, and
     make it inaccessible. */
#ifdef _LIBC
  if (__builtin_expect (__libc_enable_secure, 0))
#else
  if (1)
#endif
    {
      if((char *)MMAP((char *)h + new_size, diff, PROT_NONE,
                      MAP_PRIVATE|MAP_FIXED) == (char *) MAP_FAILED)
        return -2;
      h->mprotect_size = new_size;
    }
#ifdef _LIBC
  else
    madvise ((char *)h + new_size, diff, MADV_DONTNEED);
#endif
  /*fprintf(stderr, "shrink %p %08lx\n", h, new_size);*/

  h->size = new_size;
  return 0;
}

 Shrink_heap() 函数的参数 diff 已经页对齐,同时 sub_heap size 也是安装页对齐的,所以计算 sub_heap new_size 时不用再处理页对齐。如果 new_size sub_heap 的首地址还小,报错退出,如果该函数运行在非 Glibc 中,则从 sub_heap 中切割出 diff 大小的虚拟内存,创建一个新的不可读写的映射区域,注意 mmap() 函数这里使用了 MAP_FIXED 标志,然后更新 sub_heap 的可读可写内存大小。如果该函数运行在 Glibc 库中,则调用 madvise() 函数,实际上 madvise() 函数什么也不做,只是返回错误,这里并没有处理 madvise() 函数的返回值。

#define delete_heap(heap) \
  do {                                                          \
    if ((char *)(heap) + HEAP_MAX_SIZE == aligned_heap_area)    \
      aligned_heap_area = NULL;                                 \
    munmap((char*)(heap), HEAP_MAX_SIZE);                       \
  } while (0)

 Delete_heap() 宏函数首先判断当前删除的 sub_heap 的结束地址是否与全局变量 aligned_heap_area 指向的地址相同,如果相同,则将全局变量 aligned_heap_area 设置为 NULL ,因为当前 sub_heap 删除以后,就可以从当前 sub_heap 的起始地址或是更低的地址开始映射新的 sub_heap ,这样可以尽量从地地址映射内存。然后调用 munmap() 函数将整个 sub_heap 的虚拟内存区域释放掉。在调用 munmap() 函数时, heap_trim() 函数调用 shrink_heap() 函数可能已将 sub_heap 切分成多个子区域, munmap() 函数的第二个参数为 HEAP_MAX_SIZE ,无论该 sub_heap (大小为 HEAP_MAX_SIZE )的内存区域被切分成多少个子区域,将整个 sub_heap 都释放掉了。

Heap_trim() 函数的源代码如下:

static int
internal_function
#if __STD_C
heap_trim(heap_info *heap, size_t pad)
#else
heap_trim(heap, pad) heap_info *heap; size_t pad;
#endif
{
  mstate ar_ptr = heap->ar_ptr;
  unsigned long pagesz = mp_.pagesize;
  mchunkptr top_chunk = top(ar_ptr), p, bck, fwd;
  heap_info *prev_heap;
  long new_size, top_size, extra;

  /* Can this heap go away completely? */
  while(top_chunk == chunk_at_offset(heap, sizeof(*heap))) {

 每个非主分配区至少有一个 sub_heap ,每个非主分配区的第一个 sub_heap 中包含了一个 heap_info 的实例和 malloc_state 的实例,分主分配区中的其它 sub_heap 中只有一个 heap_info 实例,紧跟 heap_info 实例后,为可以用于分配的内存块。当当前非主分配区的 top chunk 与当前 sub_heap heap_info 实例的结束地址相同时,意味着当前 sub_heap 中只有一个空闲 chunk ,没有已分配的 chunk 。所以可以将当前整个 sub_heap 都释放掉。

prev_heap = heap->prev;
    p = chunk_at_offset(prev_heap, prev_heap->size - (MINSIZE-2*SIZE_SZ));
    assert(p->size == (0|PREV_INUSE)); /* must be fencepost */
    p = prev_chunk(p);
    new_size = chunksize(p) + (MINSIZE-2*SIZE_SZ);
    assert(new_size>0 && new_size<(long)(2*MINSIZE));
    if(!prev_inuse(p))
      new_size += p->prev_size;
    assert(new_size>0 && new_size<HEAP_MAX_SIZE);
    if(new_size + (HEAP_MAX_SIZE - prev_heap->size) < pad + MINSIZE + pagesz)
      break;

 每个sub_heap 的可读可写区域的末尾都有两个 chunk 用于 fencepost ,以 64 位系统为例,最后一个 chunk 占用的空间为 MINSIZE-2 *SIZE_SZ ,为 16B ,最后一个 chuk size 字段记录的前一个 chunk inuse 状态,并标识当前 chunk 大小为 0 ,倒数第二个 chunk inuse 状态,这个 chunk 也是 fencepost 的一部分,这个 chunk 的大小为 2 *SIZE_SZ ,为 16B ,所以用于 fencepost 的两个 chunk 的空间大小为 32B fencepost 也有可能大于 32B ,第二个 chunk 仍然为 16B ,第一个 chunk 的大小大于 16B ,这种情况发生在 top chunk 的空间小于 2*MINSIZE ,大于 MINSIZE ,但对于一个完全空闲的 sub_heap 来说, top chunk 的空间肯定大于 2*MINSIZE ,所以在这里不考虑这种情况。用于 fencepost chunk 空间其实都是被分配给应用层使用的, new_size 表示当前 sub_heap 中可读可写区域的可用空间,如果倒数第二个 chunk 的前一个 chunk 为空闲状态,当前 sub_heap 中可读可写区域的可用空间大小还需要加上这个空闲 chunk 的大小。如果 new_size sub_heap 中剩余的不可读写的区域大小之和小于 32+4K 64 位系统),意味着前一个 sub_heap 的可用空间太少了,不能释放当前的 sub_heap

ar_ptr->system_mem -= heap->size;
    arena_mem -= heap->size;
    delete_heap(heap);
    heap = prev_heap;
    if(!prev_inuse(p)) { /* consolidate backward */
      p = prev_chunk(p);
      unlink(p, bck, fwd);
    }
    assert(((unsigned long)((char*)p + new_size) & (pagesz-1)) == 0);
    assert( ((char*)p + new_size) == ((char*)heap + heap->size) );
    top(ar_ptr) = top_chunk = p;
    set_head(top_chunk, new_size | PREV_INUSE);
/*check_chunk(ar_ptr, top_chunk);*/

 首先更新非主分配区的内存统计,然后调用 delete_heap() 宏函数释放该 sub_heap ,把当前 heap 设置为被释放 sub_heap 的前一个 sub_heap p 指向的是被释放 sub_heap 的前一个 sub_heap 的倒数第二个 chunk ,如果 p 的前一个 chunk 为空闲状态,由于不可能出现多个连续的空闲 chunk ,所以将 p 设置为 p 的前一个 chunk ,也就是 p 指向空闲 chunk ,并将该空闲 chunk 从空闲 chunk 链表中移除,并将将该空闲 chunk 赋值给 sub_heap top chunk ,并设置 top chunk size ,标识 top chunk 的前一个 chunk 处于 inuse 状态。然后继续判断循环条件,如果循环条件不满足,退出循环,如果条件满足,继续对当前 sub_heap 进行收缩。

 }
  top_size = chunksize(top_chunk);
  extra = ((top_size - pad - MINSIZE + (pagesz-1))/pagesz - 1) * pagesz;
  if(extra < (long)pagesz)
    return 0;
  /* Try to shrink. */
  if(shrink_heap(heap, extra) != 0)
    return 0;
  ar_ptr->system_mem -= extra;
  arena_mem -= extra;

  /* Success. Adjust top accordingly. */
  set_head(top_chunk, (top_size - extra) | PREV_INUSE);
  /*check_chunk(ar_ptr, top_chunk);*/

 首先查看 top chunk 的大小,如果 top chunk 的大小减去 pad MINSIZE 小于一页大小,返回退出,否则调用 shrink_heap() 函数对当前 sub_heap 进行收缩,将空闲的整数个页收缩掉,仅剩下不足一页的空闲内存,如果 shrink_heap() 失败,返回退出,否则,更新内存使用统计,更新 top chunk 的大小。

  return 1;
}
 

 

 

分享到:
评论

相关推荐

    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源代码分析.pdf

    glibc内存管理ptmalloc源代码分析

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

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

    Glibc内存管理Ptmalloc2源代码分析

    标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...

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

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