伙伴系统及其申请方式
原文链接:https://blog.csdn.net/badbayyj/article/details/125475407
通过伙伴系统申请内核内存的函数:
alloc_pages(gfp_mask, order),__get_free_pages(gfp_mask, order)等
伙伴系统
是内核用来管理物理内存的一种算法。
在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。
伙伴系统就是为了缓解这种碎片化,它把管理起来的内存分为了不同的组,总共11组,每个组中的内存块大小都是一样的,都是2的幂次个物理页。
这十一组的大小分别是20,21,22,23……2^10,而一个内存页一般是4KB,所以大小就是4KB,8KB,16KB,32KB……4MB,内核就使用11个链表来表示11个不同大小的内存块。
在linux中我们把最小内存块称为page
,大小一般为4kb,每个page
都有两个状态:
1:free 表示未分配 2:allocated 表示已分配
内存分配:
当分配内存时,会优先从大小匹配的内存链表中查找是否有空闲的内存,当发现对应大小的内存都被使用完毕后,就会向大一倍的内存链表去寻找空闲内存。
但并不是直接去使用更大内存链表中的内存块,而是把内存块分为相同的两份,一份给请求者使用,另一份则给下一级,也就是对应内存块的上的内存链表上。
如:现在需要分配一个大小为8K内存,但是在对应内存块的内存链表上已经没有了,那么伙伴系统就会去16K内存链表上去查找一个空闲内存块,并分为两个8K的内存块。一个给申请内存块的使用,另一个则加入到8K的内存链表中去进行管理。当然如果16K的也没有空闲内存块了,就会去更高一级的32KB中去查询有没有空间的内存块,会把32K分成一个16K和两个8K,16K放到16K的内存块中管理,两个8K的,一个返回给申请者,一个8K的放到8K的内存块中管理。
在Linux中可以通过 echo m > /proc/sysrq-trigger来观察buddy状态和/proc/buddyinfo也可以观察到。
内存的释放
当释放内存时,会扫描对应大小的内存块链表,查看是否存在地址能够连续在一起的内存块,如果发现有,那么就合并两个内存块放置到更大一级的内存块链表上,以此类推。比如我们释放8KB大小的内存,那么会从对应的链表扫描是否有能够合并的内存块,如果有另一个8KB大小的内存和我们使用的内存地址连续,那么就合并它们组成一个16KB大小的内存块,然后接着扫描16KB大小的内存块链表,继续查找合并的可能,以此类推下去。
优点:
较好的解决了外部碎片问题
只要申请的块不超过1024个页面(4M),内核就尽量分配连续的页面
针对大内存分配设计
缺点:
合并的要求太过严格,只能是满足伙伴关系的块才能合并。一个很小的块往往会阻碍一个大块的合并,一个系统中,对内存块的分配,大小是随机的,一片内存中仅一个小的内存块没有释放,旁边两个大的就不能合并
浪费问题:伙伴系统是按2的幂次方大小进行分配内存块,如果所需内存大小不是2的幂次方,就会有部分页面浪费,有时候还很严重。
拆分和合并开销很大