内存碎片分为:内部碎片和外部碎片
外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。
外部碎片是出于任何已分配区域或页面外部的空闲存储块。这些存储块的总和可以满足当前申请的长度要求,但是由于它们的地址不连续或其他原因,使得系统无法满足当前申请。
造成原因:系统虽有足够的内存,但却都是分散的碎片,无法满足对大块“连续内存”的需求。
Buddy System是一种经典的内存管理算法。在Unix和Linux操作系统中都有用到。其作用是减少存储空间中的空洞、减少外部碎片、增加利用率。
伙伴算法把内存块按大小分组管理,即将所有空闲页帧分组为11个块链表(block List),每个块链表分别包含1,2,4,8,16,32,64,128,256,512,1024个连续的页帧(也就是2的0次方,到2的10次方)。
我们通过一个简单的例子来说明该算法的工作原理。
假设要求分配的块其大小为128个页面(由多个页面组成的块我们就叫做页面块)。
(1)该算法先在块大小为128个页面的链表中查找,看是否有这样一个空闲块。如果有,就直接分配;
(2)如果没有,该算法会查找下一个更大的块,具体地说,就是在块大小为256个页面的链表中查找一个空闲块。如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。
(3)如果在块大小为256个页面的链表中也没有找到空闲页块,就继续找更大的块,即512个页面的块。如果存在这样的块,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中。然后把剩余的128个页面插入到块大小为128个页面的链表中。
(4)如果512个页面的链表中还没有空闲块,就继续找更大的块,即1024个页面的块。重复类似上面的过程。如果在1024页面的块链表中还没找到,则返回出错信号。
以上过程的逆过程就是块的释放过程,这也是该算法名字的来由。页帧块在释放时,会主动将两个连续的页帧块合并为一个较大的页帧块。
满足以下条件的两个块称为伙伴:
· 两个块的大小相同
· 两个块的物理地址连续
伙伴算法把满足以上条件的两个块合并为一个块,该算法是迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。
由于页帧的分配不再盲目,因此在一定程度上减轻了外部碎片,但并未彻底解决外部碎片的问题。
与此同时,伙伴系统还带来了很多内部碎片。
Linux内核通过slab机制来解决内部碎片的问题。
Linux内核以页为最小单位分配内存.但内核最常使用的内存往往很小,远小于1页4K,如文件描述符fd,进程描述符PID,虚拟内存区描述符等。根据前面的理论,“内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间”,可见这种情况下容易导致内部碎片问题。此外,内核通常依赖于这些小对象的分配,因此这些对象会在系统生命周期内进行无数次分配(包括初始化、构造及销毁),影响系统的性能。
为了满足对这种小内存块的需要,Linux内核采用了一种“slab缓存分配器”的技术,其核心思想就是“存储池”的运用。
小块内存被看做对象,当被使用完后,并不直接释放,而是被缓存到存储池里,下次可继续使用,这无疑避免了频繁创建和销毁对象带来的系统开销。
slab分配器还支持通用对象的初始化,从而避免了为同一目的而对同一对象重复进行初始化。