glibc ptmalloc 内存管理详解
发布日期:2021-10-03 22:59:15 浏览次数:66 分类:技术文章

本文共 5495 字,大约阅读时间需要 18 分钟。

C语言程序中,我们一般是使用glibc库中的malloc()/free()库函数从heap或者mmap中分配和释放内存。

C++ 编程中的new和delete运算符最终也是使用malloc()/free()来分配和释放内存。

(See gcc-4.4.7\libstdc++-v3\libsupc++\new_op.cc, del_op.cc)。

1 内存布局(Linux X86_64

X86_64下Linux 进程的内存布局如下图所示:

相应的内核代码如下:

File arch\x86\include\asm\Processor.h/*User space process size. 47bits minus one guard page.*/#define TASK_SIZE_MAX	((1UL << 47) - PAGE_SIZE)#define TASK_SIZE  (test_thread_flag(TIF_ADDR32) ?                                 IA32_PAGE_OFFSET : TASK_SIZE_MAX)#define STACK_TOP		TASK_SIZE/*This decides where the kernel will search for a free chunk of vm space during mmap's.*/#define TASK_UNMAPPED_BASE	(PAGE_ALIGN(TASK_SIZE / 3))# cat /proc/sys/kernel/randomize_va_space  2   0 - Turn the process address space randomization off   1 - Make the addresses of mmap base, stack and VDSO page randomized   2 - Additionally enable heap randomizationSYSCALL_DEFINE3(execve,		const char __user *, filename,		const char __user *const __user *, argv,		const char __user *const __user *, envp){	struct filename *path = getname(filename);	int error = PTR_ERR(path);	if (!IS_ERR(path)) {		error = do_execve(path->name, argv, envp);		putname(path);	}	return error;}

 一般情况下, 应用程序并不是直接从操作系统分配内存,而是通过glibc库来进行内存分配。

glibc中使用的内存分配器是ptmalloc2 。

下面就讲一下ptmalloc2是如何工作的。

2 ptmalloc2的设计

1.
每个进程只有一个主分配区
(main arena)
,可能存在多个非主分配区
(non main arena),
ptmalloc
根据系统对分配区的争用情况动态增加非主分配区的数量
,
主分配区与非主分配区用环形链表进行管理
.
2.
主分配区可以访问进程的
heap
区域和
mmap
映射区域,也就是说主分配区可以使用
sbrk
mmap
向操作系统申请虚拟内存。而非主分配区只能访问进程的
mmap
映射区域,非主分配区每次使用
mmap
()
向操作系统“批发”
HEAP_MAX_SIZE
32
位系统上默认为
1MB
64
位系统默认为
64MB
)大小的虚拟内存,当用户向非主分配区请求分配内存时再切割成小块“零售”出去
.

特别大的内存分配总是使用mmap,使用mmap分配的内存在释放时直接归还给操作系统

4. 小块的内存分配使用brk,因为用mmap映射匿名页,当发生缺页异常时,linux内核为缺页分配一个新物理页,并将该物理页清0,一个mmap的内存块需要映射多个物理页,导致多次清0操作,很浪费系统资源,所以引入了mmap分配阈值动态调整机制,保证在必要的情况下才使用mmap分配内存。

5. 尽量只缓存临时使用的空闲小内存块,对大内存块在释放时都直接归还给操作系统。

6. 对空闲的小内存块只会在mallocfree的时候进行合并,free时空闲内存块可能放入pool中,不一定归还给操作系统。

7. 收缩堆的条件是当前free的块大小加上前后能合并chunk的大小大于64KB、,并且堆顶的大小达到阈值,才有可能收缩堆,把堆最顶端的空闲内存返回给操作系统。

8. 为了支持多线程,多个线程可以从同一个分配区(arena)中分配内存,ptmalloc假设线程A释放掉一块内存后,线程B会申请类似大小的内存,但是A释放的内存跟B需要的内存不一定完全相等,可能有一个小的误差,就需要不停地对内存块作切割和合并,这个过程中可能产生内存碎片

2.1 Chunk 

struct malloc_chunk {}

chunk指针: 指向一个chunk的开始,一个chunk中包含了用户请求的内存区域和相关的控制信息.

mem指针: 真正返回给用户的内存指针。

P: 表示前一个chunk是否在使用中,P0则表示前一个chunk为空闲,这时 chunk的第一个域prev_size才有效,prev_size表示前一个chunksize,程序可以使用这个值来找到前一个chunk的开始地址。当P1时,表示前一个chunk正在使用中,prev_size无效。

M: 表示该chunk是从哪个内存区域获得的虚拟内存。1表示从mmap映射区域分配的,否则是从heap区域分配的。

A: 表示该chunk属于主分配区或者非主分配区,1:非主分配区, 0:主分配区

chunk空闲时,原本是用户数据区的地方存储了四个指针,fd指向后一个空闲的chunkbk指向前一个空闲的chunk,这两个指针将大小相近的chunk连成一个双向链表。对于large bin中的空闲chunk,还有两个指针,fd_nextsizebk_nextsize,这两个指针用于加快在large bin中查找最近匹配的空闲chunk。不同的chunk链表又是通过bins或者fastbins来组织的

Used chunk结构:

Free chunk 结构:

 

 2.2 Arena/Bins

Struct malloc_state {

     mfastbinptr      fastbinsY[NFASTBINS]; /* Fastbins */ Array size = 10, <=160 B

     mchunkptr      top; /*top chunk*/

     mchunkptr        last_remainder;/*remainder chunk*/

     mchunkptr        bins[NBINS * 2 - 2]; /* Normal bins*/

}

ptmalloc将相似大小的chunk用双向链表链接起来,这样的一个链表被称为一个binPtmalloc一共维护了128bin,并使用一个数组来存储这些bin.

small bins数组第2-63的前62bin,同一个small bin中的chunk具有相同的大小。两个相邻的small bin中的chunk大小相差8bytes(x86_64 is 16 bytes.), size < 1024 B

large binsSmall bins后面的bin(64-126)large bins中的每一个bin分别包含了一个给定范围内的chunk,其中的chunk按大小序排列。按“smallest-firstbest-fit”原则在空闲large bins中查找合适的chunk

fastbins:不大于max_fast(默认值为128B)chunk被释放后,首先会被放到fast bins 中,fast bins中的chunk并不改变它的使用标志P。这样也就无法将它们合并,当需要给用户分配的chunk小于或等于max_fast时,ptmalloc首先会在fast bins中查找相应的空闲块,然后才会去查找bins中的空闲chunk

unsorted bin数组中的第一个。如果被用户释放的chunk大于max_fast,或者fast bins中的空闲chunk合并后,这些chunk首先会被放到unsorted bin队列中,在进行malloc操作的时候,如果在fast bins中没有找到合适的chunk,则ptmalloc会先在unsorted bin中查找合适的空闲chunk,然后才查找bins。如果unsorted bin不能满足分配要求。malloc便会将unsorted bin中的chunk加入bins中。然后再从bins中继续进行查找和分配过程。

 

 

2.3 top chunk对于主分配区和非主分配区是不一样的:

非主分配区: 会预先从mmap区域分配一块较大的空闲内存模拟sub-heap,通过管理sub-heap来响应用户的需求.

内存是按地址从低向高进行分配的,在空闲内存的最高处,必然存在着一块空闲chunk,叫做top chunk。当binsfast bins都不能满足分配需要的时候,ptmalloc会设法在top chunk中分出一块内存给用户,如果top chunk本身不够大,分配程序会重新分配一个sub-heap,并将top chunk迁移到新的sub-heap上,新的sub-heap与已有的sub-heap用单向链表连接起来,然后在新的top chunk上分配所需的内存以满足分配的需要。Top chunk的大小随着分配和回收不停变换。如果在free时回收的内存大于某个阈值,并且top chunk的大小也超过了收缩阈值,ptmalloc会收缩sub-heap,如果top-chunk包含了整个sub-heapptmalloc会调用munmap把整个sub-heap的内存返回给操作系统。

主分配区:是进程heap区域的分配区,它可以通过sbrk()来增大或是收缩进程heap的大小,ptmalloc在开始时会预先分配一块较大的空闲内存,主分配区的top chunk在第一次调用malloc时会分配一块(chunk_size + 128KB) align 4KB大小的空间作为初始的heap,用户从top chunk分配内存时,可以直接取出一块内存给用户。在回收内存时,回收的内存恰好与top chunk相邻则合并成新的top chunk,当该次回收的空闲内存大小达到某个阈值,并且top chunk的大小也超过了收缩阈值,会执行内存收缩,减小top chunk的大小,但至少要保留一个页大小的空闲内存,从而把内存归还给操作系统。如果向主分配区的top chunk申请内存,而top chunk中没有空闲内存,ptmalloc会调用sbrk()将的进程heap的边界brk上移,然后修改top chunk的大小。

mmaped chunk

当需要分配的chunk足够大,而且fast binsbins都不能满足要求,甚至top chunk本身也不能满足分配需求时,ptmalloc会使用mmap来直接使用内存映射来将页映射到进程空间。这样分配的chunk在被free时将直接解除映射,于是就将内存归还给了操作系统,再次对这样的内存区的引用将导致segmentation fault错误。这样的chunk也不会包含在任何bin中。

Last remainder chunk

Last remainder是另外一种特殊的chunk。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。如果分配的chunk是在large bins中的chunk split 之后的到的, split之后剩下的chunk会被加到unsorted chunk中,同时如果分配的chunk sizesmall chunk之内,split之后剩下的chunk还会被赋值给Last remainder.

malloc()和free()的代码位置如下:

glibc-2.15\malloc\malloc.cstatic struct malloc_state main_arena =  {    .mutex = MUTEX_INITIALIZER,    .next = &main_arena  };malloc()  -> void* public_mALLOc(size_t bytes)Free()-> void public_fREe(void* mem)

3 malloc()的流程图

 

 

4 Free()的流程图

 

 

 

转载地址:https://blog.csdn.net/lclfans1983/article/details/106578895 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:ROS Navigation源代码剖析(1)-move_base 线程框架
下一篇:sudo rosdep init ERROR错误处理

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月16日 07时07分19秒