关闭

伙伴算法

标签: linux 内存管理 伙伴算法
90人阅读 评论(0) 收藏 举报
分类:

内存分配中的伙伴算法


1.几个重要的概念

节点:在某些体系结构中CPU对内存不同位置的访问所需的时间不相同。Linux支持这种被称为非一致内存访问模式(Non-Uniform Memory Access,NUMA),相应的也就存在这样一种情况:CPU对内存的单元的访问都需要相同的时间(UMA)。正因为存在NUMA这种情况,系统的物理内存被划分为几个节点,在一个节点中任意给定的cpu访问页面的时间都相同。而对于UMA而言只有一个节点。每个节点用pg_data_t类型描述。 
管理区:每个节点中的内存被分为几个管理区(zone),使用结构struct zone描述。 
页框:每个管理区又被分为几个页框使用结构struct page描述。

2.伙伴算法原理

当内核频繁地请求和释放不同大小的一组连续页框,会导致在已分配页框块内分散着许多小块的空闲页框,这就是所谓的外碎片问题。这个问题会导致即使有足够的空闲页框可以满足请求,但要分配一个大块的连续页框就无法满足。 
伙伴算法就是为了解决这个问题。它将所有的空闲页框分组为11个链表,每个链表中有相同大小的页框块(2的order次方个页框),order对应数组的下标。举例来说,如果请求一个256个页框的块,就先在256个页框的链表中检查是否为空,不为空就分配否则在512个页框的链表中检查,若不为空,就把512个页框中的256个分配,把剩余的256个插到相应的链表中。若为空,就在1024个页框的链表中检查,若不为空,就把其中的256个分配,把其余的768个页框分成两份一份512,一份256,然后插入相应的链表中。若为空算法就此结束,分配失败。

3.分配页框的流程

3.1.页框分配的入口函数:alloc_pages

  1. #define alloc_pages(gfp_mask, order) \
  2. alloc_pages_node(numa_node_id(), gfp_mask, order)

3.2 alloc_pages_node函数:

numa_node_id():获取节点id 
gfp_mask:gfp_mask是一组标志,它指明如何寻找空闲页框。 
order:伙伴算法的阶数

  1. static inline struct page *alloc_pages_node(int nid, unsigned int gfp_mask, unsigned int order)
  2. {
  3. if (unlikely(order >= MAX_ORDER))
  4. return NULL;
  5. return __alloc_pages(gfp_mask, order,
  6. NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK));
  7. }

alloc_pages_node先判断阶数是否大于最大阶数值,然后调用__alloc_pages函数。其中 NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK)是计算zonelist数据结构的指针

3.3 __alloc_pages

zone_watermark_ok函数接收几个参数,它们决定内存管理区中空闲页框个数的阀值min。先对内存管理区第一次扫描。 
pages_min:管理区中保留页的数目 
page_low:回收页框使用的下界。同时也被管理区分配器为作为阈值使用,它总被设置为pages_min的5/4。 
pages_high:回收页框使用的上界,同时也被管理区分配器作为阈值使用,它总被设置为pages_min的3/2。 
先对内存管理区第一次扫描:

  1. for (i = 0; (z = zones[i]) != NULL; i++) {
  2. if (!zone_watermark_ok(z, order, z->pages_low,
  3. classzone_idx, 0, 0))
  4. continue;
  5. page = buffered_rmqueue(z, order, gfp_mask);
  6. if (page)
  7. goto got_pg;
  8. }

如果内存充足的话,之前已经得到内存,如果内存紧张的话就调用wakeup_kswapd,wakeup_kswapd应该是用于回收。

  1. for (i = 0; (z = zones[i]) != NULL; i++)
  2. wakeup_kswapd(z, order);

对内存管理区进行第二次扫描,将值z->pages_min作为阀值传入。

  1. for (i = 0; (z = zones[i]) != NULL; i++) {
  2. if (!zone_watermark_ok(z, order, z->pages_min,
  3. classzone_idx, can_try_harder,
  4. gfp_mask & __GFP_HIGH))
  5. continue;
  6. page = buffered_rmqueue(z, order, gfp_mask);
  7. if (page)
  8. goto got_pg;
  9. }

如果产生内存分配的内核控制路径不是一个中断处理程序或者可延迟函数, 并且它试图回收页框(PF_MEMALLOC,TIF_MEMDIE标志被置位),对内存管理区进行第三次扫描。

  1. if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE))) && !in_interrupt()) {
  2. for (i = 0; (z = zones[i]) != NULL; i++) {
  3. page = buffered_rmqueue(z, order, gfp_mask);
  4. if (page)
  5. goto got_pg;
  6. }
  7. goto nopage;
  8. }
  9. if (!wait) //如果是非阻塞,就结束
  10. goto nopage;
  11. }

还有一种情况就是阻塞的情况,这部分还没看懂。总体来看 __alloc_pages是调用 buffered_rmqueue函数完成分配的下面介绍一下 buffered_rmqueue函数。

3.4 buffered_rmqueue函数:伙伴算法的实现

内核经常请求和释放单个页框,也就是order值为0时,为了提高效率,内核管理区引入了“每CPU页框高速缓存”,这部分还没看懂。当申请的是非单页页框时 buffered_rmqueue调用__rmqueue函数实现分配。

3.5 __rmqueue

如果对应的空闲块链表为空,就在更大的空闲块链表中进行循环搜索,直到找到合适的空闲块。找到后先在空闲块链表中删除第一个页框描述符并减少空闲管理区的空闲页数量。如果2^order空闲块链表中没有合适的空闲块,那么就是从更大的空闲链表中分配的。 将剩余的空闲块分散到合适的链表中去。expand函数完成这项功能。

  1. static struct page *__rmqueue(struct zone *zone, unsigned int order)
  2. {
  3. struct free_area * area;
  4. unsigned int current_order;
  5. struct page *page;
  6. for (current_order = order; current_order < MAX_ORDER; ++current_order) {
  7. area = zone->free_area + current_order;
  8. if (list_empty(&area->free_list))
  9. continue;
  10. page = list_entry(area->free_list.next, struct page, lru);
  11. list_del(&page->lru);
  12. rmv_page_order(page);
  13. area->nr_free--;
  14. zone->free_pages -= 1UL << order;
  15. return expand(zone, page, order, current_order, area);
  16. }
  17. return NULL;
  18. }
  1. expand(struct zone *zone, struct page *page,
  2. int low, int high, struct free_area *area)
  3. {
  4. unsigned long size = 1 << high;
  5. while (high > low) {
  6. area--;
  7. high--;
  8. size >>= 1;
  9. BUG_ON(bad_range(zone, &page[size]));
  10. list_add(&page[size].lru, &area->free_list);
  11. area->nr_free++;
  12. set_page_order(&page[size], high);
  13. }
  14. return page;
  15. }
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1604次
    • 积分:63
    • 等级:
    • 排名:千里之外
    • 原创:5篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档