浅析linux内核内存管理之buddy system

                            浅析linux内核内存管理之buddy system



       Linux采用著名的伙伴系统(buddy system)算法来解决外碎片问题。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框。对1024个页框的最大请求对应着4MB大小的连续RAM块。每个块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16个页框的块,其起始地址是16*2^12的倍数。内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块。满足以下条件的两个块称为伙伴:

  • 两个块具有相同的大小,记作b
  • 他们的物理地址是连续的
  • 第一块的第一个页框的物理地址是2*b*2^12的倍数
该算法是迭代的,如果它成功合并所释放的块,它会试图合并2b的块,以再次试图形成更大的块


核心数据结构


实际上,每个管理区都关系到mem_map元素的子集,子集中的第一个元素和元素个数分别由管理区描述符的zone_mem_map和size字段指定。包含有11个元素,元素类型为free_area的一个数组,每个元素对应一种块大小。该数组存放在描述符的free_area字段中。

[html] view plaincopy
  1. struct free_area {  
  2.         struct list_head        free_list;  
  3.         unsigned long           nr_free;  
  4. };  

我们考虑管理区描述符中free_area数组的第k个元素,它标识所有大小为2^k的空闲块。这个元素的free_list字段是双向循环链表的头,这个双向循环链表集中了大小为2^k页的空闲块对应的页描述符。更精确的说,该链表包含每个空闲页块(大小为2^k)的起始页框的页描述符;指向链表中相邻元素的指针存放在页描述符的lru字段中。除了链表头外,free_area数组的第k个元素同样包含字段nr_free,它指定了大小为2^k页的空闲块的个数。当然,如果没有大小为2^k的空闲页框块,则nr_free等于0且free_list为空(free_list的两个指针都指向它自己的free_list字段)。最后,一个2^k的空闲页块的第一个页的描述符的private字段存放了块的order,也就是数字k。正是由于这个字段,当页块被释放时,内核可以确定这个块的伙伴是否也空闲,如果是的话,它可以把两个块结合成大小为2^(k+1)页的单一块。


从buddy system分配pages


page allocator是buddy system的前端,buddy system进行实际的页管理与分配,下面分析buddy system的页分配函数:

[html] view plaincopy
  1. 458static int rmqueue_bulk(struct zone *zone, unsigned int order,   
  2. 459                        unsigned long count, struct list_head *list)  
  3. 460{  
  4. 461        unsigned long flags;  
  5. 462        int i;  
  6. 463        int allocated = 0;  
  7. 464        struct page *page;  
  8. 465          
  9. 466        spin_lock_irqsave(&zone->lock, flags);  
  10. 467        for (i = 0; i < count; ++i) {  
  11. 468                page = __rmqueue(zone, order);  
  12. 469                if (page == NULL)  
  13. 470                        break;  
  14. 471                allocated++;  
  15. 472                list_add_tail(&page->lru, list);  
  16. 473        }  
  17. 474        spin_unlock_irqrestore(&zone->lock, flags);  
  18. 475        return allocated;  
  19. 476}  
主要调用了__rmqueue函数:

[html] view plaincopy
  1. 431static struct page *__rmqueue(struct zone *zone, unsigned int order)  
  2. 432{  
  3. 433        struct free_area * area;  
  4. 434        unsigned int current_order;  
  5. 435        struct page *page;  
  6. 436  
  7. 437        for (current_order = order; current_order < MAX_ORDER; ++current_order) {  
  8. 438                area = zone->free_area + current_order;  
  9. 439                if (list_empty(&area->free_list))  
  10. 440                        continue;  
  11. 441  
  12. 442                page = list_entry(area->free_list.next, struct page, lru);  
  13. 443                list_del(&page->lru);  
  14. 444                rmv_page_order(page);  
  15. 445                area->nr_free--;  
  16. 446                zone->free_pages -1UL << order;  
  17. 447                return expand(zone, page, order, current_order, area);  
  18. 448        }  
  19. 449  
  20. 450        return NULL;  
  21. 451}  
  1. 因为在指定的order的free_area的链表中不一定能获得指定的page块,所以可能会去更大的order的链表里找page块
  2. 获得指定order的free_area数组元素
  3. 如果指定order的free_area的链表为空,去更大order的free_area找
  4. 否则,从free_area的free_list上删除page块的第一个page的描述符
  5. 将page块的第一个page的描述符的本来存放order的private字段清0
    [html] view plaincopy
    1. 187static inline void rmv_page_order(struct page *page)  
    2. 188{  
    3. 189        __ClearPagePrivate(page);  
    4. 190        page->private = 0;  
    5. 191}  
  6. 减少指定free_area上的空闲page块的计数
  7. 减少管理器的空闲page数
  8. 如果从比指定的order高的order的free_area分配,则会产生一些伙伴,把伙伴放到低于分配的order的free_area链表中
分析expand实现:

[html] view plaincopy
  1. 368expand(struct zone *zone, struct page *page,  
  2. 369        int low, int high, struct free_area *area)  
  3. 370{  
  4. 371        unsigned long size = 1 << high;  
  5. 372  
  6. 373        while (high > low) {  
  7. 374                area--;  
  8. 375                high--;  
  9. 376                size >>= 1;  
  10. 377                BUG_ON(bad_range(zone, &page[size]));  
  11. 378                list_add(&page[size].lru, &area->free_list);  
  12. 379                area->nr_free++;  
  13. 380                set_page_order(&page[size], high);  
  14. 381        }  
  15. 382        return page;  
  16. 383}  

比如之前需要从order为2的分配,但是order为2的free_area的free_list没有page块了,则去order为3的链表上找,如果此时order为3的链表上也没有空闲page块,则到order为4的链表上去找,如果此时恰巧找到则停止。这样获得了一个有16个page的page块。但是只请求了4个page,此时那些多余的page要放到order为2,order为3的链表上。看程序,首先获得size,也就是这里说的16,在我的例子里,high为4,low为2。area--也就是获得order为3的那个free_area;high--,此时为3;size>>=1,此时size为8;将刚才那个16个page的page块的后8个page组成块放到order为3的链表上,增加order为3上的块计数,

[html] view plaincopy
  1. 182static inline void set_page_order(struct page *page, int order) {  
  2. 183        page->private = order;  
  3. 184        __SetPagePrivate(page);  
  4. 185}  
设置这个有8个page的page块的第一个page的页描述符的private字段存放order值,下一个循环照此原理,最后反回给page allocator的是刚才那16个page的块的最前面的4个page组成的page块的第一个page的页描述符的地址。


释放pages到buddy system


[html] view plaincopy
  1. 308free_pages_bulk(struct zone *zone, int count,  
  2. 309                struct list_head *list, unsigned int order)  
  3. 310{  
  4. 311        unsigned long flags;  
  5. 312        struct page *base, *page = NULL;  
  6. 313        int ret = 0;  
  7. 314  
  8. 315        base = zone->zone_mem_map;  
  9. 316        spin_lock_irqsave(&zone->lock, flags);  
  10. 317        zone->all_unreclaimable = 0;  
  11. 318        zone->pages_scanned = 0;  
  12. 319        while (!list_empty(list) && count--) {  
  13. 320                page = list_entry(list->prev, struct page, lru);  
  14. 321                /* have to delete it as __free_pages_bulk list manipulates */  
  15. 322                list_del(&page->lru);  
  16. 323                __free_pages_bulk(page, base, zone, order);  
  17. 324                ret++;  
  18. 325        }  
  19. 326        spin_unlock_irqrestore(&zone->lock, flags);  
  20. 327        return ret;  
  21. 328}  
主要是调用了__free_pages_bulk函数

[html] view plaincopy
  1. 236static inline void __free_pages_bulk (struct page *page, struct page *base,  
  2. 237                struct zone *zone, unsigned int order)  
  3. 238{  
  4. 239        unsigned long page_idx;  
  5. 240        struct page *coalesced;  
  6. 241        int order_size = 1 << order;  
  7. 242  
  8. 243        if (unlikely(order))  
  9. 244                destroy_compound_page(page, order);  
  10. 245  
  11. 246        page_idx = page - base;  
  12. 247  
  13. 248        BUG_ON(page_idx & (order_size - 1));  
  14. 249        BUG_ON(bad_range(zone, page));  
  15. 250  
  16. 251        zone->free_pages += order_size;  
  17. 252        while (order < MAX_ORDER-1) {  
  18. 253                struct free_area *area;  
  19. 254                struct page *buddy;  
  20. 255                int buddy_idx;  
  21. 256  
  22. 257                buddy_idx = (page_idx ^ (1 << order));  
  23. 258                buddy = base + buddy_idx;  
  24. 259                if (bad_range(zone, buddy))  
  25. 260                        break;  
  26. 261                if (!page_is_buddy(buddy, order))  
  27. 262                        break;  
  28. 263                /* Move the buddy up one level. */  
  29. 264                list_del(&buddy->lru);  
  30. 265                area = zone->free_area + order;  
  31. 266                area->nr_free--;  
  32. 267                rmv_page_order(buddy);  
  33. 268                page_idx &= buddy_idx;  
  34. 269                order++;  
  35. 270        }  
  36. 271        coalesced = base + page_idx;  
  37. 272        set_page_order(coalesced, order);  
  38. 273        list_add(&coalesced->lru, &zone->free_area[order].free_list);  
  39. 274        zone->free_area[order].nr_free++;  
  40. 275}  

  •  page_idx局部变量包含块中第一个页框的下标,这是相对管理区中的第一个页框而言的。zone_mem_map是指向管理区第一个页描述符的指针。buddy_idx是page_idx的伙伴的下标
  • 这个是buddy_idx是怎样算出来的呢,可以举一个例子
    [html] view plaincopy
    1. 例如对于0 order,   (0,1), (2,3), (3,4)... , 已知2, 则伙伴就是3.  
    2. 对于1order,  (0/1, 2/3), (4/5,6/7), (8/9, 10/11),  已知2/3, 则伙伴是0/1.  
    如果现在order=1,并且是4/5这个块,现在要找伙伴6/7这个块,那么4为0100,与1<<1即0010异或,得0110,即6
    如果现在order=1,并且是6/7这个块,现在要找伙伴4/5这个块,那么6为0110,与1<<1即0010异或,得0100,即4
  • 所以ULK3上说使用(1<<order)掩码的异或(XOR)转换page_idx第order位的值。因此,如果这个位原先是0,buddy_idx就等于page_idx+order_size;相反,如果这个位原先是1,buddy_idx就等于page_idx-order_size
  • page_idx &= buddy_idx可以通过刚才的例子看出这个是获得两个兄弟前面的那个兄弟

[html] view plaincopy
  1. 202static inline int page_is_buddy(struct page *page, int order)  
  2. 203{  
  3. 204       if (PagePrivate(page)           &&  
  4. 205           (page_order(page) == order) &&  
  5. 206           !PageReserved(page)         &&  
  6. 207            page_count(page) == 0)  
  7. 208               return 1;  
  8. 209       return 0;  
  9. 210}  

检查两个page块是否是兄弟

注意这里是个while循环,page块释放到buddy system后,如果能与其他page块合并,则一层层向上找伙伴,然后合并成更大的,再向上找伙伴,实在找不到了就停止了!


阅读更多

没有更多推荐了,返回首页