ptmalloc底层调用过程

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fad32948000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=34566, ...}) = 0
mmap(NULL, 34566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fad3293f000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1924768, ...}) = 0
mmap(NULL, 3750184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fad32395000
mprotect(0x7fad3251f000, 2097152, PROT_NONE) = 0
mmap(0x7fad3271f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x7fad3271f000
mmap(0x7fad32725000, 14632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fad32725000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fad3293e000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fad3293d000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fad3293c000
arch_prctl(ARCH_SET_FS, 0x7fad3293d700) = 0
mprotect(0x7fad3271f000, 16384, PROT_READ) = 0
mprotect(0x7fad32949000, 4096, PROT_READ) = 0
munmap(0x7fad3293f000, 34566)           = 0
brk(0)                                  = 0x686000
brk(0x6a7000)                           = 0x6a7000
exit_group(0)                           = ?


brk munmap mprotect mmap arch

http://blog.csdn.net/qq_37058442/article/details/78842194 具体参考这篇文章mmap的使用。



获取虚拟空间时应该将mmap参数flag设置为MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 Ptmalloc 每次调用 mmap都设置该标志。然后文件fd设置为-1;

上节提到 heap 和 mmap 映射区域是可以提供给用户程序使用的虚拟内存空间,如何获
得该区域的内存呢? 操作系统提供了相关的系统调用来完成相关工作。对 heap 的操作,操
作系统提供了 brk()函数, C 运行时库提供了 sbrk()函数;对 mmap 映射区域的操作,操作系
统提供了 mmap()和 munmap()函数。 sbrk(), brk() 或者 mmap() 都可以用来向我们的进程添
加额外的虚拟内存。 Glibc 同样是使用这些函数向操作系统申请虚拟内存。

内存的延迟分配, 只有在真正访问一个地址的时候才建
立这个地址的物理映射,这是 Linux 内存管理的基本思想之一。 Linux 内核在用户申请内存的
时候,只是给它分配了一个线性区(也就是虚拟内存),并有分配实际物理内存;只有当
用户使用这块内存的时候,内核才会分配具体的物理页面给用户,这时候才占用宝贵的物理
内存。内核释放物理页面是通过释放线性区,找到其所对应的物理页面,将其全部释放的过
程.

 Glibc 的 malloc 函数族( realloc, calloc 等)
就调用 sbrk()函数将数据段的下界移动, sbrk()函数在内核的管理下将虚拟地址空间映射到内
存,供 malloc()函数使用。

ptmalloc 实现了 malloc(), free()以及一组其它的函数. 以提供动态内存管理的支持。 分
配器处在用户程序和内核之间,它响应用户的分配请求,向操作系统申请内存,然后将其返
回给用户程序,为了保持高效的分配, 分
配器一般都会预先分配一块大于用户请求的内存,
并通过某种算法管理这块内存。来满足用户的内存分配要求,用户释放掉的内存也并不是立
即就返回给操作系统,相反, 分配器会管理这些被释放掉的空闲空间,以应对用户以后的内
存分配要求。也就是说, 分配器不但要管理已分配的内存块,还需要管理空闲的内存块,

响应用户分配要求时, 分配器会首先在空闲空间中寻找一块合适的内存给用户,在空闲空间
中找不到的情况下才分配一块新的内存。

有长生命周期的大内存分配使用 mmap。
特别大的内存分配总是使用 mmap。

 具有短生命周期的内存分配使用 brk,因为用 mmap 映射匿名页, 当发生缺页异常

时, linux 内核为缺页分配一个新物理页,并将该物理页清 0, 一个 mmap 的内存块
需要映射多个物理页,导致多次清 0 操作,很浪费系统资源,所以引入了 mmap
分配阈值动态调整机制,保证在必要的情况下才使用 mmap 分配内存。
4. 尽量只缓存临时使用的空闲小内存块,对大内存块或是长生命周期的大内存块在释
放时都直接归还给操作系统。
5. 对空闲的小内存块只会在 malloc 和 free 的时候进行合并, free 时空闲内存块可能
放入 pool 中,不一定归还给操作系统。
6. 收缩堆的条件是当前 free 的块大小加上前后能合并 chunk 的大小大于 64KB、,并且
堆顶的大小达到阈值,才有可能收缩堆, 把堆最顶端的空闲内存返回给操作系统。
7. 需要保持长期存储的程序不适合用 ptmalloc 来管理内存。
8. 为了支持多线程,多个线程可以从同一个分配区( arena) 中分配内存, ptmalloc
假设线程 A 释放掉一块内存后, 线程 B 会申请类似大小的内存,但是 A 释放的内
存跟 B 需要的内存不一定完全相等,可能有一个小的误差, 就需要不停地对内存块
作切割和合并,这个过程中可能产生内存碎片。



pt malloc的结构

内存分配器有一个主分区和若干个非主分区每次分配内存都必须对主分配区加锁,分配完成后释放锁,若干分区的出现主要是解决 SMP 多线程环境下,对主分配区的锁的争用很激烈,严重影响了 malloc 的分配效率。
Glibc 的 malloc 可以支持多线程,增加了非主分配区( non main arena)支持, 主分配区与非
主分配区用环形链表进行管理。 每一个分配区利用互斥锁( mutex)使线程对于该分配区的
访问互斥。

每个进程只有一个主分区 主分区可以通过调用brk和mmap获取虚拟内存。非主分区的个数不会减少他的个数。但是他会增加当所有分区都被锁定之后需要增加非主分区。(对于任意一个线程而言他会优先查看自己私有变量中的是否存在已经分配区如果有的话就尝试加锁分配内存。如果失败便利整个环去查找可以使用的分配区。)非主分区只能通过mmap获取虚拟内存32位1mb 64为64mb当分配小内存时进行分割。进行零售。在释放内存的时候如果该分区被其他线程使用必须等到完了之后才能释放这个内存块(别人用完重新释放这个内存)每个分区的碎片问题进行解决所以cpu会进行整理合并,所有要使用分区的线程在等待。所以性能不高。

主分配区可以访问 heap 区域,如果用户不调用 brk()或是 sbrk()函数(获取新的空间链接到虚拟地址空间当heap区满的话就要调用brk调用新的空间), 分配程序就可以
保证分配到连续的虚拟地址空间,因为每个进程只有一个主分配区使用 sbrk()分配 heap 区
域的虚拟内存。 内核对 brk 的实现可以看着是 mmap 的一个精简版,相对高效一些。 如果主
分配区的内存是通过 mmap()向系统分配的,当 free 该内存时,主分配区会直接调用 munmap()
将该内存归还给系统。

对于分配出去的chunk和空闲块上的chunk还是有区别的


空闲的chunk中他的格式会改变增加了空闲的chunk是通过双向链表进行管理和使用的。m标志位主要是为了说明该使用的chunk实在哪里申请的如果是mmap申请的 直接解除映射。如果不是就要放在空闲链表上等待复用。


每个malloc给用户分配的内存都是有控制信息。36+4并且每个里面都有标记使用块的大小和是否使用将所有块做成一个双向链表。进行链接。不同大小chunk是通过bin和fastbin组成的。


用户 free 掉的内存并不是都会马上归还给系统, ptmalloc 会统一管理 heap 和 mmap 映
射区域中的空闲的 chunk,当用户进行下一次分配请求时, ptmalloc 会首先试图在空闲的
chunk 中挑选一块给用户,这样就避免了频繁的系统调用,降低了内存分配的开销。 ptmalloc
将相似大小的 chunk 用双向链表链接起来,这样的一个链表被称为一个 bin。 Ptmalloc 一共
维护了 128 个 bin,并使用一个数组来存储这些 bin( 如下图所示)。

数组中的第一个为 unsorted bin, 数组中从 2 开始编号的前 64 个 bin 称为 small bins,同
一个 small bin中的 chunk具有相同的大小。两个相邻的 small bin中的 chunk大小相差 8bytes。
small bins 中的 chunk 按照最近使用顺序进行排列,最后释放的 chunk 被链接到链表的头部,
而申请 chunk 是从链表尾部开始,这样,每一个 chunk 都有相同的机会被 ptmalloc 选中。
Small bins 后面的 bin 被称作 large bins。 large bins 中的每一个 bin 分别包含了一个给定范围
内的 chunk,其中的 chunk 按大小序排列。相同大小的 chunk 同样按照最近使用顺序排列。
ptmalloc 使用“ smallest-first, best-fit”原则在空闲 large bins 中查找合适的 chunk。
当空闲的 chunk 被链接到 bin 中的时候, ptmalloc 会把表示该 chunk 是否处于使用中的
标志 P 设为 0( 注意, 这个标志实际上处在下一个 chunk 中), 同时 ptmalloc 还会检查它前
后的 chunk 是否也是空闲的, 如果是的话, ptmalloc 会首先把它们合并为一个大的 chunk,
然后将合并后的 chunk 放到 unstored bin 中。 要注意的是, 并不是所有的 chunk 被释放后就
立即被放到 bin 中。 ptmalloc 为了提高分配的速度, 会把一些小的的 chunk 先放到一个叫做
fast bins 的容器内

 Fast Bins
一般的情况是, 程序在运行时会经常需要申请和释放一些较小的内存空间。 当分配器合
并了相邻的几个小的 chunk 之后, 也许马上就会有另一个小块内存的请求, 这样分配器又需
要从大的空闲内存中切分出一块, 这样无疑是比较低效的, 故而, ptmalloc 中在分配过程中
引入了 fast bins,不大于 max_fast(默认值为 64B)的 chunk 被释放后,首先会被放到 fast bins
中, fast bins 中的 chunk 并不改变它的使用标志 P。 这样也就无法将它们合并, 当需要给用
户分配的 chunk 小于或等于 max_fast 时, ptmalloc 首先会在 fast bins 中查找相应的空闲块,
然后才会去查找 bins中的空闲 chunk。在某个特定的时候,ptmalloc会遍历 fast bins中的 chunk,
18
将相邻的空闲 chunk 进行合并, 并将合并后的 chunk 加入 unsorted bin 中,然后再将 usorted
bin 里的 chunk 加入 bins 中。


总结一下: 根据用户请求分配的内存的大小, ptmalloc 有可能会在两个地方为用户
分配内存空间。 在第一次分配内存时,一般情况下只存在一个主分配区,但也有可能从
父进程那里继承来了多个非主分配区,在这里主要讨论主分配区的情况, brk 值等于
start_brk, 所以实际上 heap 大小为 0, top chunk 大小也是 0。 这时, 如果不增加 heap
大小, 就不能满足任何分配要求。所以, 若用户的请求的内存大小小于 mmap 分配阈值,
则 ptmalloc 会初始 heap。 然后在 heap 中分配空间给用户, 以后的分配就基于这个 heap
进行。若第一次用户的请求就大于 mmap 分配阈值, 则 ptmalloc 直接使用 mmap()分配
一块内存给用户, 而 heap 也就没有被初始化, 直到用户第一次请求小于 mmap 分配阈
值的内存分配。第一次以后的分配就比较复杂了,简单说来,ptmalloc 首先会查找 fast bins,
如果不能找到匹配的 chunk, 则查找 small bins。 若还是不行,合并 fast bins,把 chunk
加入 unsorted bin,在 unsorted bin 中查找, 若还是不行, 把 unsorted bin 中的 chunk 全
加入 large bins 中,并查找 large bins。在 fast bins 和 small bins 中的查找都需要精确匹配,
而在 large bins 中查找时, 则遵循“ smallest-first, best-fit”的原则, 不需要精确匹配。
若以上方法都失败了, 则 ptmalloc 会考虑使用 top chunk。 若 top chunk 也不能满足分配
要求。 而且所需 chunk 大小大于 mmap 分配阈值, 则使用 mmap 进行分配。 否则增加
heap, 增大 top chunk。 以满足分配要求。

总结如下ptmalloc的规则如下

1. ptmalloc调用mmap的阈值是动态的,默认是128K,但是在内存分配和回收过程中是动态的增加和减少的,这个机制
保证了ptmalloc能够尽量地重用缓存中的空闲内存,不用每次申请大内存就去调用mmap。
2. ptmalloc分为主分配区和非主分配区,主分配区就是进程一开始就有的一块区域,是从start_brk处开始分配的。而非
主分配区是动态开出来的,用于减少多线程下的锁机制导致的效率低下问题引申处来的。
3. mmap效率低下的原因:
1. 系统调用,陷入内核。
2. 按页对齐,内存浪费。
3. linux强制把用mmap申请到的物理内存页面要清零,很低效。
4. 与ptmalloc无关,没办法复用。
但是也有好处:
1. 独立向操作系统申请和释放,与ptmalloc独立出来。对于长生存时间的大内存,使用mmap申请内存很适合。
2. 不会被ptmalloc锁在chunk中。
2. 数据结构概述
1. 主分配区和非主分配区
如果只有一个分配区的话,在SMP下,锁的争用激烈,效率会低下,所以引入多分配区的机制。
1. 一个进程只有一个主分配区,但是可以有多个非主分配区。分配区之间用环形链表组织。每个分配区都有一把锁。
2. ptmalloc会根据分配区的争用情况动态增加和非主分配区:当一个线程访问分配区的时候(可能是主分配区,也可
能是非主分配区),如果发现是加锁状态,就会遍历一下全局的环形链表,如果发现环形链表中所有的分配区都是锁住
的,就会开辟一个新的非主分配区使用。
3. 主分配区可以在heap区域、mmap映射区申请虚拟内存(调用sbrk和mmap),而非主分配区只能访问mmap区
域,非主分配区每次增长都是调用mmap去向OS申请1M虚拟空间使用,以备零售。
4. 非主分配区在mmap映射区获取的空间用来模拟heap空间,也可以叫做sub-heap(子堆)。
2. chunk
用户请求时候都是以chunk来组织的,归还时候也是归还给ptmalloc而不是直接归还给OS。
1. 一个已经分配出去的chunk
可见,每个分配出去的chunk确实是像《深度探索C++对象模型》中介绍的一样,在数据的上方是有一个区块大小
(chunk size)的。
另外还有M标志来判断是在mmap映射区还是heap区。A来判断是在主分配区还是非主分配区。P在空闲时用。
2. 空闲chunk
可见,ptmalloc组织空闲chunk是用双向链表。P位表示是否为空闲
3. 空闲chunk容器 各种bin(垃圾桶的意思)
1. bins
1)一共128和bin,第一个是unsorted bin,后面的64个(一半)是large bin(每个bin之间差64B,最大4K),前
63个是small bin(每个bin之间差8B,最大512B)。
每次有chunk被free的时候,就会链入这个类似于hash桶的bins中,首先会检查前后的chunk是否和自己是连续的,
如果是连续的,就会合并然后放入unsorted bin中。
2. fast bins
为了防止一些小struct的对象分配,频繁的申请和释放,这样合并来合并去,然后又是分裂来分裂去的,效率会低下,
所以有个fast bins,当申请的对象小于max_fast(64B)时,会先找fast bin,换回来的时候是不会把P标志清零的(这
样就不会被合并),之后在特定的时候,才会遍历fast bin中的chunk,将连续的合并,并放入unsorted bin中,然后再
将unsorted bin中的chunk链入bins中。
3. unsorted bin
如果申请的大小大于max_fast(64B)时,会先在unsorted bin中找合适的,如果不适合就会链入bins,如果
unsorted bin遍历完了还没有找到,就会去找bins。
但是,复杂的是,不是所有的chunk都是按照上面的bins来管理的。
4. top chunk
分主分配区的top chunk和非主分配区的top chunk。
非主分配区的top chunk:
因为开辟非主分配区的时候,是在mmap映射区申请的,用来模拟主分配区的heap,称为sub-heap(每次新开辟
的sub-heap默认大小是1M),在heap中除了之前的bins,还会在heap的顶部有一块剩余,这一块剩余称为top
chunk,是单独一个chunk哦,这个就是非主分配区的top chunk。
在使用时,在bins和fast bins都找过之后都没找到合适的chunk,就会来到top chunk,会从top chunk划分一块
给用户,也就是说剩下的部分还是叫top chunk只是变小了,可能小到甚至比bins中的large bins都小,但是他还是叫
top chunk!但是如果top chunk也不满足呢,就会重新mmap一个sub-heap给这个非主分配区,并用链表连起来所有
的sub-heap,这个top chunk就迁移到新的这个sub-heap上了。
当回收chunk时,如果chunk是和top chunk连续的,就会合并到top chunk中,top chunk就变大了,如果回收的
内存大于一个阈值(64KB),且top chunk的大小也超过了收缩的阈值(128KB默认,动态改变),就会收缩subheap,如果top chunk占整个sub-heap,就会用munmap直接把这块sub-heap还给OS。
主分配区的top chunk:
主分配区使用的是真正的heap啊,在ptmalloc开始时候就会开辟一块很大的内存的,当第一次malloc时候,会分
配一块chunk_size+128K并且对齐4K大小的内存给heap空间(就记着初始化是128K)。所以主分配区的top chunk就好像是一块超级大的sub-heap,不像非主分配区还要多个sub-heap链起来而已。同样的分配方式,同样的收缩方式,
用的是sbrk()去增长brk的值。
5. mmaped chunk
动动奶子都能想到,这个就是大于top chunk,且大于辣个动态阈值的chunk,直接使用mmap在mmap映射区找一
块chunk,被free时也是直接还给OS的。
6. last remainder
这个很奇怪,不懂也罢了。是给small chunk用的,有在small chunk中找不到时候,且小于这个last remainder
chunk,就会把这个last remainder chunk分一部分给用户,剩下的就是新的last remainder chunk,不知道是干嘛
的。
4. mmap动态分配阈值和收缩阈值
1. 主分配区的heap初始化是发生在第一次malloc且申请的chunk是小于初始的默认mmap阈值的(128KB),因为
大于128KB不用heap而是mmap映射区。初始化时初始为128K+chunksize 然后还要对齐页大小(4KB)。
2. 非主分配区的sub-heap初始化是1MB大小。
3. 动态改变分配阈值的算法:当回收用户的chunk值超过了当前的阈值且小于512KB,就会将把当前的分配阈值设置
为这个chunk的size,并且把收缩阈值设为2*分配阈值。
3. 内存分配的流程
1. 上锁,如果发现所有分配区都锁了,就开辟个非主分配区用mmap创建个sub-heap,设置top chunk。
2. 判断是否小于max_fast(64B),是的话去fast bins中找,找不到去small bins中找。
3. 如果是大于small bins的最大值(512B)的,那就先去fast bins中,把能合并的合并了去再放入到unsorted bins
中,然后遍历一下unsorted bins,如果unsorted bins中只有一个chunk而且这个chunk还大于要求大小,就把他撕开
分配出去。否则的话,就看大小该放到small放small,该large放large。
4. 在large中找个合适大小的给用户。
5. 走到第5步了,就说明用户要的内存有点大啊,那下面就用top chunk了,把top chunk分一部分给用户。
6. 纳尼?top chunk都不够?那可要分情况讨论了。
1)没大于mmap分配阈值:(也就是说这个top chunk要变大啦!)
1° 主分配区:如果第一次malloc,就开辟个128K+chunksize并对齐4KB,不是的话,就调用sbrk()增加heap大
小,也就是增加top chunk大小。
2° 非主分配区:调用mmap()增加个sub-heap,增大top chunk分配一块出去。
2)大于mmap分配阈值:(也就是说用户需求本来就大)
这里就是直接调用mmap找OS要吧。(可以根据需求优化的)
4. 内存回收的流程
1. 获取锁。
2. 如果这块chunk是mmaped chunk,调用munmap释放。更新分配阈值和收缩阈值。
3. 判断chunk所处的位置,如果是小于max_fast的,且不和top chunk挨着。就把他放在fast bins中(不设置P
位),结束free。
4. 看前面的chunk是否连续,是的话就合并了去。
5. 看下一块是不是top chunk。
1)不是top chunk:看后面的chunk是否连续,是的话合并了去,合并完了把这个chunk放在unsorted bins
中。
2)是top chunk:和top chunk合并。
6. 看合并完了之后的chunk(不管是不是top chunk)多大,看不是大于清理fastbin的阈值:
FASTBIN_CONSOLIDATION_THRESHOLD(64KB),不是的话就free结束。
7. 大于的话,就要来到这,做俩件事情。(1)清理fast bin:把fast bins中能合并的合并放到unsorted bins中,不
能合并的挂载到small bins上。(2)判断一下top chunk是否大于收缩阈值了(128KB,动态的,起始的那个128KB不
算),如果是主分配区就直接归还给OS,是非主分配区的话对sub-heap收缩(如果top chunk是整个sub-heap的话就
直接把sub-heap归还了),free结束。
从这个流程中可以看出来,收缩的条件是合并之后的chunk要大于64KB才能触发,触发后还要判断top chunk的值要大
于收缩阈值。也可以理解为top chunk的收缩是伴随着fast bins的清理。1. ptmalloc调用mmap的阈值是动态的,默认是128K,但是在内存分配和回收过程中是动态的增加和减少的,这个机制
保证了ptmalloc能够尽量地重用缓存中的空闲内存,不用每次申请大内存就去调用mmap。
2. ptmalloc分为主分配区和非主分配区,主分配区就是进程一开始就有的一块区域,是从start_brk处开始分配的。而非
主分配区是动态开出来的,用于减少多线程下的锁机制导致的效率低下问题引申处来的。
3. mmap效率低下的原因:
1. 系统调用,陷入内核。
2. 按页对齐,内存浪费。
3. linux强制把用mmap申请到的物理内存页面要清零,很低效。
4. 与ptmalloc无关,没办法复用。
但是也有好处:
1. 独立向操作系统申请和释放,与ptmalloc独立出来。对于长生存时间的大内存,使用mmap申请内存很适合。
2. 不会被ptmalloc锁在chunk中。
2. 数据结构概述
1. 主分配区和非主分配区
如果只有一个分配区的话,在SMP下,锁的争用激烈,效率会低下,所以引入多分配区的机制。
1. 一个进程只有一个主分配区,但是可以有多个非主分配区。分配区之间用环形链表组织。每个分配区都有一把锁。
2. ptmalloc会根据分配区的争用情况动态增加和非主分配区:当一个线程访问分配区的时候(可能是主分配区,也可
能是非主分配区),如果发现是加锁状态,就会遍历一下全局的环形链表,如果发现环形链表中所有的分配区都是锁住
的,就会开辟一个新的非主分配区使用。
3. 主分配区可以在heap区域、mmap映射区申请虚拟内存(调用sbrk和mmap),而非主分配区只能访问mmap区
域,非主分配区每次增长都是调用mmap去向OS申请1M虚拟空间使用,以备零售。
4. 非主分配区在mmap映射区获取的空间用来模拟heap空间,也可以叫做sub-heap(子堆)。
2. chunk
用户请求时候都是以chunk来组织的,归还时候也是归还给ptmalloc而不是直接归还给OS。
1. 一个已经分配出去的chunk
可见,每个分配出去的chunk确实是像《深度探索C++对象模型》中介绍的一样,在数据的上方是有一个区块大小
(chunk size)的。
另外还有M标志来判断是在mmap映射区还是heap区。A来判断是在主分配区还是非主分配区。P在空闲时用。
2. 空闲chunk
可见,ptmalloc组织空闲chunk是用双向链表。P位表示是否为空闲
3. 空闲chunk容器 各种bin(垃圾桶的意思)
1. bins
1)一共128和bin,第一个是unsorted bin,后面的64个(一半)是large bin(每个bin之间差64B,最大4K),前
63个是small bin(每个bin之间差8B,最大512B)。
每次有chunk被free的时候,就会链入这个类似于hash桶的bins中,首先会检查前后的chunk是否和自己是连续的,
如果是连续的,就会合并然后放入unsorted bin中。
2. fast bins
为了防止一些小struct的对象分配,频繁的申请和释放,这样合并来合并去,然后又是分裂来分裂去的,效率会低下,
所以有个fast bins,当申请的对象小于max_fast(64B)时,会先找fast bin,换回来的时候是不会把P标志清零的(这
样就不会被合并),之后在特定的时候,才会遍历fast bin中的chunk,将连续的合并,并放入unsorted bin中,然后再
将unsorted bin中的chunk链入bins中。
3. unsorted bin
如果申请的大小大于max_fast(64B)时,会先在unsorted bin中找合适的,如果不适合就会链入bins,如果
unsorted bin遍历完了还没有找到,就会去找bins。
但是,复杂的是,不是所有的chunk都是按照上面的bins来管理的。
4. top chunk
分主分配区的top chunk和非主分配区的top chunk。
非主分配区的top chunk:
因为开辟非主分配区的时候,是在mmap映射区申请的,用来模拟主分配区的heap,称为sub-heap(每次新开辟
的sub-heap默认大小是1M),在heap中除了之前的bins,还会在heap的顶部有一块剩余,这一块剩余称为top
chunk,是单独一个chunk哦,这个就是非主分配区的top chunk。
在使用时,在bins和fast bins都找过之后都没找到合适的chunk,就会来到top chunk,会从top chunk划分一块
给用户,也就是说剩下的部分还是叫top chunk只是变小了,可能小到甚至比bins中的large bins都小,但是他还是叫
top chunk!但是如果top chunk也不满足呢,就会重新mmap一个sub-heap给这个非主分配区,并用链表连起来所有
的sub-heap,这个top chunk就迁移到新的这个sub-heap上了。
当回收chunk时,如果chunk是和top chunk连续的,就会合并到top chunk中,top chunk就变大了,如果回收的
内存大于一个阈值(64KB),且top chunk的大小也超过了收缩的阈值(128KB默认,动态改变),就会收缩subheap,如果top chunk占整个sub-heap,就会用munmap直接把这块sub-heap还给OS。
主分配区的top chunk:
主分配区使用的是真正的heap啊,在ptmalloc开始时候就会开辟一块很大的内存的,当第一次malloc时候,会分
配一块chunk_size+128K并且对齐4K大小的内存给heap空间(就记着初始化是128K)。所以主分配区的top chunk就
好像是一块超级大的sub-heap,不像非主分配区还要多个sub-heap链起来而已。同样的分配方式,同样的收缩方式,
用的是sbrk()去增长brk的值。
5. mmaped chunk
动动奶子都能想到,这个就是大于top chunk,且大于辣个动态阈值的chunk,直接使用mmap在mmap映射区找一
块chunk,被free时也是直接还给OS的。
6. last remainder
这个很奇怪,不懂也罢了。是给small chunk用的,有在small chunk中找不到时候,且小于这个last remainder
chunk,就会把这个last remainder chunk分一部分给用户,剩下的就是新的last remainder chunk,不知道是干嘛
的。
4. mmap动态分配阈值和收缩阈值
1. 主分配区的heap初始化是发生在第一次malloc且申请的chunk是小于初始的默认mmap阈值的(128KB),因为
大于128KB不用heap而是mmap映射区。初始化时初始为128K+chunksize 然后还要对齐页大小(4KB)。
2. 非主分配区的sub-heap初始化是1MB大小。
3. 动态改变分配阈值的算法:当回收用户的chunk值超过了当前的阈值且小于512KB,就会将把当前的分配阈值设置
为这个chunk的size,并且把收缩阈值设为2*分配阈值。
3. 内存分配的流程
1. 上锁,如果发现所有分配区都锁了,就开辟个非主分配区用mmap创建个sub-heap,设置top chunk。
2. 判断是否小于max_fast(64B),是的话去fast bins中找,找不到去small bins中找。
3. 如果是大于small bins的最大值(512B)的,那就先去fast bins中,把能合并的合并了去再放入到unsorted bins
中,然后遍历一下unsorted bins,如果unsorted bins中只有一个chunk而且这个chunk还大于要求大小,就把他撕开
分配出去。否则的话,就看大小该放到small放small,该large放large。
4. 在large中找个合适大小的给用户。
5. 走到第5步了,就说明用户要的内存有点大啊,那下面就用top chunk了,把top chunk分一部分给用户。
6. 纳尼?top chunk都不够?那可要分情况讨论了。
1)没大于mmap分配阈值:(也就是说这个top chunk要变大啦!)
1° 主分配区:如果第一次malloc,就开辟个128K+chunksize并对齐4KB,不是的话,就调用sbrk()增加heap大
小,也就是增加top chunk大小。
2° 非主分配区:调用mmap()增加个sub-heap,增大top chunk分配一块出去。
2)大于mmap分配阈值:(也就是说用户需求本来就大)
这里就是直接调用mmap找OS要吧。(可以根据需求优化的)
4. 内存回收的流程
1. 获取锁。
2. 如果这块chunk是mmaped chunk,调用munmap释放。更新分配阈值和收缩阈值。
3. 判断chunk所处的位置,如果是小于max_fast的,且不和top chunk挨着。就把他放在fast bins中(不设置P
位),结束free。
4. 看前面的chunk是否连续,是的话就合并了去。
5. 看下一块是不是top chunk。
1)不是top chunk:看后面的chunk是否连续,是的话合并了去,合并完了把这个chunk放在unsorted bins
中。
2)是top chunk:和top chunk合并。
6. 看合并完了之后的chunk(不管是不是top chunk)多大,看不是大于清理fastbin的阈值:
FASTBIN_CONSOLIDATION_THRESHOLD(64KB),不是的话就free结束。
7. 大于的话,就要来到这,做俩件事情。(1)清理fast bin:把fast bins中能合并的合并放到unsorted bins中,不
能合并的挂载到small bins上。(2)判断一下top chunk是否大于收缩阈值了(128KB,动态的,起始的那个128KB不
算),如果是主分配区就直接归还给OS,是非主分配区的话对sub-heap收缩(如果top chunk是整个sub-heap的话就
直接把sub-heap归还了),free结束。
从这个流程中可以看出来,收缩的条件是合并之后的chunk要大于64KB才能触发,触发后还要判断top chunk的值要大
于收缩阈值。也可以理解为top chunk的收缩是伴随着fast bins的清理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值