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是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...
在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...
《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...
glibc内存管理ptmalloc源代码分析
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...
标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...
标题与描述概述的知识点主要集中在glibc内存管理的细节,特别是ptmalloc的源代码分析,这对于深入理解C库如何高效地处理内存分配和回收至关重要。以下是对这些知识点的详细解析: ### Glibc内存管理与ptmalloc ###...
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...
### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...
### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...
`glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...
本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...
通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...
《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。