Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。系统在分配内存时不再要求大块的连续内存,但是实际上系统使用内存时还是倾向于分配连续的内存块。为了尽量减少不连续情况,内核采用了“伙伴”关系来管理空闲页面。
Linux的伙伴算法把所有的空闲页面分为10个块组,每组中块的大小是2的幂次方个页面,即第0组中块的大小都为20(1个页面),第9组中块的大小都为29(512个页面)。也就是说每一组中块的大小是相同的,且这些同样大小的快形成一个链表。以下例子展示该算法的工作过程:
假设要求分配的块大小为128个页面。该算法现在块大小为128个页面的链表中查找,看是否有这样一个空闲块。如果有就直接分配,否则查找下一个更大的块,也就是在块大小为256个页面的链表中查找一个空闲块。如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。如果在块大小为256个页面的链表中也没有空闲页块,就继续找更大的块(512个页面的块)。如果存在,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中,把剩下的128个页面插入到块大小为128个页面的链表中。如果连512个页面的链表中也没有空闲块,则放弃分配,返回错误信息。
以上过程的逆过程就是块的释放过程,满足以下条件的两个块称为伙伴:1.两个块大小相同;2.两个块物理地址连续。伙伴算法把满足以上条件的两个快合并为一个块。而且该算法是迭代的,如果合并后的块还存在伙伴,则继续合并。
内核中分配空闲页面的基本函数时get_free_page/get_free_pages,用于分配单页或者指定的页面数。get_free_pages实在内核中分配内存,而malloc实在用户空间中分配。malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然以页为单位分配。
Slab
内核常常会使用到圆圆小于一页的内存块,而且这些小块内存频繁地生成和销毁。为了满足内核对小内存块的需要,Linux采用了一种称为slab分配器的技术。核心思想是“存储池”的运用。内存片段被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”中,以供下次使用。Slab并不是脱离伙伴关系而独立存在的一种内存分配方式,仍然是建立在页面基础上,它将来自伙伴关系管理的页面分割成众多小内存块以供使用。
Kmalloc
Kmalloc是Slab分配器提供的借口,用于完成内核程序中对小于一页的小块内存的请求。
Vmalloc
伙伴关系只是减轻了外部分片(所谓外部分片是指系统虽然有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求)的危害,但并未彻底消除。
解决的思路时利用不连续的内存块组成“看起来很大的内存块”,类似于用户空间分配虚拟内存,内存逻辑上连续,其实映射到并不一定连续的物理内存上。内核提供vmalloc函数分配内核虚拟内存。