在page_init中 通过调用pagetable_init();建立了页目录表和页表;通过调用zone_sizes_init(); 函数建立了Node, Zone,Page的分层的结构,并且初始化了伙伴算法的基本数据结构(为每个zone的free_area 的map字段分配内存,并且初始化为0)。
位图大小
free_area[ 0] . map位图大小= ( ( size- 1) / 2) / 8= ( size- 1) > > ( 0+ 1+ 3)
free_area[ 1] . map位图大小= ( ( size- 1) / 4) / 8= ( size- 1) > > ( 1+ 1+ 3)
. . .
free_area[ 9] . map位图大小= ( ( size- 1) / 1024) / 8= ( size- 1) > > ( 9+ 1+ 3)
这里之所以要在order的基础上要加1,是因为每一位bit管理着两块内存块。
然后在mem_init中调用__free_pages_ok来将某个zone中的所有页框组织到free_area中。下面分析一下__free_pages_ok。
在分析之前先说一下buddy 算法的原理:
"位图中的一个位代表相邻的两个伙伴页当前被使用情况" , 这样1个位顶2个位来用, 就可以管理其2倍大小的size- 1个内存页了.
我们知道偶数号位图- A和+ 1后的紧邻奇数号位图- B, 它们是伙伴, 它们两个共用位图A> > 1, 如:
下面索引的意思是第几块的意思.
对于order= 0, 索引0- [ 0] , 索引1- [ 1] 是伙伴 - - - - - - - - - - - - - - - - 共用位图0
索引2- [ 2] , 索引3- [ 3] 是伙伴 - - - - - - - - - - - - - - - - 共用位图1
. . . . . .
索引2* n- [ 2* n] , 索引2* n+ 1- [ 2* n+ 1] 是伙伴 - - - - - - - - - - - - - - - - 共用位图n
对于order= 1, [ 索引0- 页地址0, 索引1- 页地址2=2*1] 是伙伴 - - - - - - - - - - - - - - - - 共用位图0
[ 索引2- 页地址4=2*2, 索引3- 页地址6=2*3] 是伙伴 - - - - - - - - - - - - - - - - 共用位图1
. . . . . .
[ 索引n- 页地址2n, 索引n+ 1- 页地址2( n+ 1) ] 是伙伴- - - - - - - - - - - - - - - - 共用位图n
对于order= 2, [ 索引0- 页地址0, 索引1- 页地址4=4*1 ] 是伙伴 - - - - - - - - - - - - - - - - 共用位图0
[ 索引2- 页地址8=4*2, 索引3- 页地址12=4*1] 是伙伴 - - - - - - - - - - - - - - 共用位图1
. . . . . .
[ 索引n- 页地址4n, 索引n+ 1- 页地址4( n+ 1) ] 是伙伴- - - - - - - - - - - - - 共用位图n
从上图可以看到伙伴是固定的,不会变的。在order为任何大小的时候,相邻的两块都是伙伴,都是0和1,2和3,4和5,.....这样一对一对下去的。
就是说0和2绝对不会是伙伴。
所以用n个位图就可以描述2n个单元了.
拿第80位map_bit80来说, 一开始map_bit80管理的两个伙伴都没有被分配出去, 这时map_bit80= 0,
由于需要现在A页块要被分配出去了, A分配出去的同时和1进行异或操作map_bit80= map_bit80^1= 1,
现在有两种情况:
1. B不被分配出去, A此时需要释放了, A开始"找朋友" ,
首先计算A和朋友B对应的位图, 因为map_bit80此时为1, 所以与1异或之后map_bit80= map_bit80^1= 0,
之后判断异或之后的结果map_bit80, 如果异或结果为0, 表明A的"朋友" , 存在, "找朋友" 顺利完成,
将A和它的朋友B合并成大空间A+ B, 放入orde= order+ 1, 上, 通过循环, 此时order已经加1, 继续检测加1后
order的朋友是否可以继续合并, 这样一直合并到MAX_ORDER- 1为止.
2. B被分配出去, 此时A仍在使用, B对共用位图map_bit80同样进行异或操作map_bit80= map_bit80^1= 0,
3. B和A都被分配出去了, 此时其中有一个要释放, 比如A, 它开始"找朋友" , 我们知道它肯定找不到朋友, 因为B也被分配出去了,
那A是怎么知道B不存在呢, 还是异或操作, 两个都被分配出去了, 所以当前map_bit80= 0, 经过异或之后
map_bit80= map_bit80^1= 1, 结果为1, 表示"伙伴" 不存在, 那么不用合并, 直接回收即可.
总结: 不论是"申请分配" 动作还是"释放回收" 动作, 都会对共用位图进行异或操作, 之所以这样来做, 目的不在于"申请分配" 上,
而是在"释放回收" 上, 当回收时, 通过异或结果来判断, 当前回收页的伙伴是否已经空闲的存在, 结果为0, 表示可以"合并成大页" ,
结果为1, 表示"伙伴" 忙, 所以位图存在的主要意义是"能够将小块内存合并成大块内存".
__free_pages_ok :
static void fastcall __free_pages_ok (struct page *page, unsigned int order)
{
unsigned long index, page_idx, mask, flags;
free_area_t *area;
struct page *base;
zone_t *zone;
/*
* Yes, think what happens when other parts of the kernel take
* a reference to a page in order to pin it for io. -ben
*/
if (PageLRU(page)) {
if (unlikely(in_interrupt())) {
unsigned long flags;
spin_lock_irqsave(&free_pages_ok_no_irq_lock, flags);
page->next_hash = free_pages_ok_no_irq_head;
free_pages_ok_no_irq_head = page;
page->index = order;
spin_unlock_irqrestore(&free_pages_ok_no_irq_lock, flags);
schedule_task(&free_pages_ok_no_irq_task);
return;
}
lru_cache_del(page);
}
if (page->buffers)
BUG();
if (page->mapping)
BUG();
if (!VALID_PAGE(page))
BUG();
if (PageLocked(page))
BUG();
if (PageActive(page))
BUG();
ClearPageReferenced(page);
ClearPageDirty(page);
//需要释放1个页面到进程的local_pages中
if (current->flags & PF_FREE_PAGES)
goto local_freelist;
back_local_freelist:
//该page位于DMA、NORMAL或者HIGHMEM之中的一个zone内
zone = page_zone(page);
//order 0 1 2 3 4 5 6 7 8 9
//mask -1 -2 -4 -8 -16 -32 -64 -128 -256 -512
//-maks 1 2 4 8 16 32 64 128 256 512
//~mask 0 1 3 7 15 31 63 127 255 511
mask = (~0UL) << order;
/*calculate the page index */
//该管理zone所有4k页面的起始mem_map虚拟地址
base = zone->zone_mem_map;
page_idx = page - base;
//该page一定是(1 << order)字节对齐的
if (page_idx & ~mask)
BUG();
//使用buddy算法,求得该page对应的map管理位图值索引(page_idx >> order)/2
index = page_idx >> (1 + order);
//获取该zone对应的free_area[order]
area = zone->free_area + order;
spin_lock_irqsave(&zone->lock, flags);
//将释放的空闲页数目,加到该zone的free_pages中去
zone->free_pages -= mask;
//升级的最多次数为10次
while (mask + (1 << (MAX_ORDER-1))) {
struct page *buddy1, *buddy2;
if (area >= zone->free_area + MAX_ORDER)
BUG();
//对index进行(^)异或运算,返回0,表示伙伴不在当前area内,或序伙伴忙,或许伙伴被拆到了其他area空闲着
//这个函数实现异或并返回原来的值,
//0 表示原来处于相同的状态,现在处于不同的状态
//1 表示原来处于不同的状态,现在处于相同的状态
if (!__test_and_change_bit(index, area->map))
/*
* the buddy page is still allocated.
*/
break;
/*
* Move the buddy up one level.
* This code is taking advantage of the identity:
* -mask = 1+~mask
*/
//如果现在处于相同状态则找到buddy并进行合并,将合并后的块升级到下一个free_area中。
//求得它的伙伴对应的struct page单元,对边界位(1<<order)进行异或操作
//order 0 1 2 3 4 5 6 7 8 9
//mask -1 -2 -4 -8 -16 -32 -64 -128 -256 -512
//-maks 1 2 4 8 16 32 64 128 256 512
//~mask 0 1 3 7 15 31 63 127 255 511
buddy1 = base + (page_idx ^ -mask);
buddy2 = base + page_idx;
if (BAD_RANGE(zone,buddy1))
BUG();
if (BAD_RANGE(zone,buddy2))
BUG();
//将合并后的块从目前层级的free_area中删除。
list_del(&buddy1->list);
//计算升级后的mask
mask <<= 1;
//指向升级后的area
area++;
//计算order+1对应的位图管理位索引号index [= page_idx >> (1 + new_order)]
//new_order = old_order +1;
index >>= 1;
//调整page_idx成(1<<order)页对齐,即:调整page_idx成伙伴中"比较小的那个小伙伴"对应的地址
page_idx &= mask;
}
//将经过Buddy伙伴合并后的页,添加到相应order对应的area对应的free_list中
list_add(&(base + page_idx)->list, &area->free_list);
spin_unlock_irqrestore(&zone->lock, flags);
return;
local_freelist:
if (current->nr_local_pages)
goto back_local_freelist;
if (in_interrupt())
goto back_local_freelist;
list_add(&page->list, ¤t->local_pages);
page->index = order;
current->nr_local_pages++;
}
当mem_init中调用__free_pages_ok时,所有位图里的位的值都为0,也就是系统中所有的页都处于已经分配状态。
(1)当对第0页进行调用__free_pages_ok时,free_area[0].map中位0值通过异惑后得到1,这样就将当前页插入到free_area[0]中。
(2)当对第1页进行调用__free_pages_ok时,free_area[0].map中位0值通过异惑后得到0,这样就将当前页升一级 order 值为1,
计算它的伙伴,将伙伴从原来的free_area中删除,计算当前页在新的free_area[order].map中位的索引0。
异或在新的free_area中bit 位,得到1,这样就将这个合并后的块插入到新free_area[order]中。
同样对于第2第3页经过(1) (2)步后,升级到order=1,这是计算出free_area[order].map中位的索引也为0 :3>>(1+1) = 0
-------------------------------------觉得这个人的总结也挺好的------------------------------
from http://blog.csdn.net/littlehedgehog/archive/2008/09/14/2790724.aspx
关于位图
Linux内核伙伴算法中每个order 的位图都表示所有的空闲块,比如我家的电脑内存256M(现在连上个qq主页都比较卡),理论上的order为0的bitmap有256M/(4K*2)块。为什么要除以二呢? 因为 位图的某位对应于两个伙伴块,为1就表示其中一块忙,为0表示两块都闲。
每次alloc或者free要操作伙伴系统时我们都要异或运算,这是因为 所谓异或,是指刚开始两块都闲为0,后来其中一块用了异或一下得1,后来另一块也用了异或一下得0,后来前面一块回收了异或一下得1,后来另一块也回收了异或一下得0,
这样(如果为1就不合并)就又可以和前面一块合并成一大块了。
位图的主要用途是在回收算法中指示是否可以和伙伴块合并,分配时只要搜索空闲链表就足够了。当然,分配的同时还要对相应位异或一下了,这是为回收算法服务。
关于分配算法
假设在初始阶段,全是大小为2^9大小的块( MAX_ORDER为10),序号依次为0, 512, 1024等等,并且所有area的map位都为0(实际上操作系统代码要占一部分空间,但这里只是举例),现在要分配一个2^3大小的页面块,有以下动作:
1. 从order为3的area的空闲链表开始搜索,没找到就向高一级area搜索,依次类推,按照假设条件,会一直搜索到order为9的area,找到了序号为0的2^9页块。
2. 把序号为0的2^9页块从order为9的area的空闲链表中摘除并对该area的第0位( 0>>(1+9) )异或一下得1。
3. 把序号为0的2^9页块拆分成两个序号分别为0和256的2^8页块,前者放入order为8的area的空闲链表中,并对该area的第0位( 0>>(1+8) )异或一下得1。
4. 把序号为256的2^8页块拆分成两个序号分别为256和384的2^7页块,前者放入order为7的area的空闲链表中,并对该area的第1位( 256>>(1+7) )异或一下得1。
5. 把序号为384的2^7页块拆分成两个序号分别为384和448的2^6页块,前者放入order为6的area的空闲链表中,并对该area的第3位( 384>>(1+6) )异或一下得1。
6. 把序号为448的2^6页块拆分成两个序号分别为448和480的2^5页块,前者放入order为5的area的空闲链表中,并对该area的第7位( 448>>(1+5) )异或一下得1。
7. 把序号为480的2^5页块拆分成两个序号分别为480和496的2^4页块,前者放入order为4的area的空闲链表中,并对该area的第15位( 480>>(1+4) )异或一下得1。
8. 把序号为496的2^4页块拆分成两个序号分别为496和504的2^3页块,前者放入order为3的area的空闲链表中,并对该area的第31位( 496>>(1+3) )异或一下得1。
9. 序号为504的2^3页块就是所求的块。
关于回收算法
1. 当回收序号为4的1页块时,先找到order为0的area,把该 页面块加入到该area的空闲链表中,然后判断其伙伴块(序号为5的1页块)的状态,读该area (不是其它area !)的map的第2位( 4>>(1+order) ),假设伙伴块被占,则该位为0(回收4块前,4、5块
都忙),现异或一下得1,并不再向上合并。
2. 当 回收序号为5的1页块时,同理,先找到order为0的area,把该页面块加入到该area的空闲链表中,然后判断其伙伴块(序号为4的1页块)的状 态,读该area的map的第2位(5>>(1+order) ), 这时该位为1(4块已回收),现异或一下得0,并向上合并,把序
号 为4的1页块和序号为5的1页块从该area的空闲链表中摘除,合并成序号为4的2页块,并放到order为1的area的空闲链表中。同理,此时又要判 断合并后的块的伙伴块(序号为6的2页块)的状态,读该area( order为1的area,不是其它! ) 的map的第1位((4>> (1+order) ),假设伙伴块在此之前已被回收,则该位为1,现异或一下得0,并向上合并,把序号为4的2页块和序号为6的2页块从order为1 的area的空闲链表中摘除,合并成序号为4的4页块,并放到order为2的area的空闲链表中。然后再判断其伙伴块状态,如此反复。