伙伴算法
1、原理
Linux的伙伴算法把所有的空闲页面分为11个块组,每组中块的大小是2的幂次方个页面,例如,第0组中块的大小都为20 (1个页面),第1组中块的大小为都为21(2个页面),第10组中块的大小都为210(1024个页面)。也就是说,每一组中块的大小是相同的,且这同样大小的块形成一个链表。对第10组块的最大请求对应着4M(每个页面的大小为4K)大小的连续RAM。每个块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16 个页框的块,其起始地址是16 × 212(212 = 4096,这是一个常规页的大小)的倍数。
我们通过一个简单的例子来说明该算法的工作原理。
假设要求分配的块其大小为128个页面(由多个页面组成的块我们就叫做页面块)。该算法先在块大小为128个页面的链表中查找,看是否有这样一个空闲块。如果有,就直接分配;如果没有,该算法会查找下一个更大的块,具体地说,就是在块大小为256个页面的链表中查找一个空闲块。如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。如果在块大小为256个页面的链表中也没有找到空闲页块,就继续找更大的块,即512个页面的块。如果存在这样的块,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中。然后把剩余的128个页面插入到块大小为128个页面的链表中。如果512个页面的链表中还没有空闲块,该算法就放弃分配,并发出出错信号。
2 、Buddy(伙伴的定义):
这里给出伙伴的概念,满足以下三个条件的称为伙伴:
1)两个块大小相同;
2)两个块地址连续;
3)两个块必须是同一个大块中分离出来的;第0块和第1块是伙伴,第2块和第3块是伙伴,但是第1块和第2块不是伙伴)
内存分配:
数据结构
struct free_area
{
Struct list_head, free_list;
unsigned long nr_free;
};
可以看出free_area结构体仅仅包含了一个双向链表,指向了空闲块的前后页,还有一个里面的free的页数。
举个例子:在分配4(2^2)页的时候,系统会先从free_area[2]中查看其成员nr_free是否空,如果空则从中分配,如果free_area[2]中没有空的,就从它的上一级free_area[3]中分配,并将多余的内存块加入到free_area[2]中。如果free_area[3]的nr_free也没有空闲,则从更上一级,以此类推直至free_area[max_order],如果顶级都没有空间,就报告分配失败。
内存释放
内存的释放是分配的逆过程,也可以看做是伙伴的合并。当释放一个块时,先在其对应的free_area链表中查找是否有伙伴存在,如果没有伙伴块,直接将要释放的块插入链表头;如果有,则将其从链表下摘下,合并成一个大块,然后继续查找在合并后的块在更大一级链表中是否有伙伴存在,直至不能合并或者已经合并至最大的块2^10为止。
伙伴算法的不足:
a。合并的要求太过严格:只能是满足伙伴关系的块。比如第1,2块就不能合并。
b。碎片问题:如何内存管理的算法都存在这个问题,一个连续的内存中仅仅一个页面被占用,导致这整个内存区都不具备合并的条件。
c。算法中的浪费现象:伙伴算法是按2的幂次方分配内存区的,当需要257(2^8+1)个页面时,不得不申请2^9的页面。于是就有255个页面被浪费掉了。
d。算法的效率问题:伙伴算法涉及了比较多的计算还有链表和位图的操作,开销还是比较大的,如果每次2^n大小的伙伴块就会合并到2^(n+1)的链表队列中,那么2^n大小链表中的块就会因为合并操作而减少,但系统随后立即有可能又有对该大小块的需求,为此必须再从2^(n+1)大小的链表中拆分,这样的合并又立即拆分的过程是无效率的。
伙伴位图:
伙伴算法通过位图进行操作,用一位描述相邻的两块(第0块和第1块是伙伴,第2块和第3块是伙伴,但是第1块和第2块不是伙伴)的状态。这个位码叫伙伴位码。
为0的时候:说明这两块都为空闲
为1的时候:说明这两块有一块是忙.
如果对相应位的异或操作得1,则没有伙伴可以合并,如果异或操作得0,就进行合并,并且继续按这种方式合并伙伴,直到不能合并为止。