Buddy 算法中释放过程解释

在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, &current->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的空闲链表中。然后再判断其伙伴块状态,如此反复。

 

 

你可能感兴趣的:(数据结构,算法,struct,list,reference,linux内核)