伙伴系统的概述

 

        Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题,在这里我们先讨论外部碎片问题。避免外部碎片的方法有两种:一种是之前介绍过的利用非连续内存的分配;另外一种则是用一种有效的方法来监视内存,保证在内核只要申请一小块内存的情况下,不会从大块的连续空闲内存中截取一段过来,从而保证了大块内存的连续性和完整性。显然,前者不能成为解决问题的普遍方法,一来用来映射非连续内存线性地址空间有限,二来每次映射都要改写内核的页表,进而就要刷新TLB,这使得分配的速度大打折扣,这对于要频繁申请内存的内核显然是无法忍受的。因此Linux采用后者来解决外部碎片的问题,也就是著名的伙伴系统。

 

什么是伙伴系统?

      伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个256K的内存块分配给内存,这样就是一个分配的过程。下面我们结合示意图来了解伙伴系统分配和回收内存块的过程。

伙伴系统java实现 伙伴系统分配内存题目_链表

 

1 初始化时,系统拥有1M的连续内存,允许的最小的内存块为64K,图中白色的部分为空闲的内存块,着色的代表分配出去了得内存块。

2 程序A申请一块大小为34K的内存,对应的order为0,即2^0=1个最小内存块

   2.1 系统中不存在order 0(64K)的内存块,因此order 4(1M)的内存块分裂成两个order 3的内存块(512K)

   2.2 仍然没有order 0的内存块,因此order 3的内存块分裂成两个order 2的内存块(256K)

   2.3 仍然没有order 0的内存块,因此order 2的内存块分裂成两个order 1的内存块(128K)

   2.4 仍然没有order 0的内存块,因此order 1的内存块分裂成两个order 0的内存块(64K)

   2.5 找到了order 0的内存块,将其中的一个分配给程序A,现在伙伴系统的内存为一个order 0的内存块,一个order

                1的内存块,一个order 2的内存块以及一个order 3的内存块

3 程序B申请一块大小为66K的内存,对应的order为1,即2^1=2个最小内存块,由于系统中正好存在一个order 1的内

     存块,所以直接用来分配

4 程序C申请一块大小为35K的内存,对应的order为0,同样由于系统中正好存在一个order 0的内存块,直接用来分  

   配

5 程序D申请一块大小为67K的内存,对应的order为1

   5.1 系统中不存在order 1的内存块,于是将order 2的内存块分裂成两块order 1的内存块

   5.2 找到order 1的内存块,进行分配

6 程序B释放了它申请的内存,即一个order 1的内存块

7 程序D释放了它申请的内存

   7.1 一个order 1的内存块回收到内存当中

   7.2由于该内存块的伙伴也是空闲的,因此两个order 1的内存块合并成一个order 2的内存块

8 程序A释放了它申请的内存,即一个order 0的内存块

9 程序C释放了它申请的内存

   9.1 一个order 0的内存块被释放

   9.2 两个order 0伙伴块都是空闲的,进行合并,生成一个order 1的内存块m

   9.3 两个order 1伙伴块都是空闲的,进行合并,生成一个order 2的内存块

   9.4 两个order 2伙伴块都是空闲的,进行合并,生成一个order 3的内存块

   9.5 两个order 3伙伴块都是空闲的,进行合并,生成一个order 4的内存块

 

相关的数据结构

     在前面的文章中已经简单的介绍过struct zone这个结构,对于每个管理区都有自己的struct zone,而struct zone中的struct free_area则是用来描述该管理区伙伴系统的空闲内存块的

 

  1. [cpp] view plaincopy
1. 2. struct zone { 
3. ...
4. ...
5. struct free_area free_area[MAX_ORDER]; 
6. ...
7. ...
8. }

 

 

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

 

 

free_area共有MAX_ORDER个元素,其中第order个元素记录了2^order的空闲块,这些空闲块在free_list中以双向链表的形式组织起来,对于同等大小的空闲块,其类型不同,将组织在不同的free_list中,nr_free记录了该free_area中总共的空闲内存块的数量。MAX_ORDER的默认值为11,这意味着最大内存块的大小为2^10=1024个页框。对于同等大小的内存块,每个内存块的起始页框用于链表的节点进行相连,这些节点对应的着struct page中的lru域

  1. [cpp] view plaincopy
1. 2. struct page { 
3.  
4. ...
5. ...
6. struct list_head lru; /* Pageout list, eg. active_list 
7.  * protected by zone->lru_lock ! 
8.  */ 
9. ...
10. }


连接示意图如下:

                                         

伙伴系统java实现 伙伴系统分配内存题目_#define_02

在2.6.24之前的内核版本中,free_area结构中只有一个free_list数组,而从2.6.24开始,free_area结构中存有MIGRATE_TYPES个free_list,这些数组是根据页框的移动性来划分的,为什么要进行这样的划分呢?实际上也是为了减少碎片而提出的,我们考虑下面的情况:

        

伙伴系统java实现 伙伴系统分配内存题目_地址空间_03

       图中一共有32个页,只分配出了4个页框,但是能够分配的最大连续内存也只有8个页框(因为伙伴系统分配出去的内存必须是2的整数次幂个页框),内核解决这种问题的办法就是将不同类型的页进行分组。分配出去的页面可分为三种类型:

  • 不可移动页(Non-movable pages):这类页在内存当中有固定的位置,不能移动。内核的核心分配的内存大多属于这种类型
  • 可回收页(Reclaimable pages):这类页不能直接移动,但可以删除,其内容页可以从其他地方重新生成,例如,映射自文件的数据属于这种类型,针对这种页,内核有专门的页面回收处理
  • 可移动页:这类页可以随意移动,用户空间应用程序所用到的页属于该类别。它们通过页表来映射,如果他们复制到新的位置,页表项也会相应的更新,应用程序不会注意到任何改变。

   假如上图中大部分页都是可移动页,而分配出去的四个页都是不可移动页,由于不可移动页插在了其他类型页的中间,就导致了无法从原本空闲的连续内存区中分配较大的内存块。考虑下图的情况:

伙伴系统java实现 伙伴系统分配内存题目_链表_04

将可回收页和不可移动页分开,这样虽然在不可移动页的区域当中无法分配大块的连续内存,但是可回收页的区域却没有受其影响,可以分配大块的连续内存。

 

内核对于迁移类型的定义如下:

 

  1. [cpp] view plaincopy
1. 2. #define MIGRATE_UNMOVABLE     0  
3. #define MIGRATE_RECLAIMABLE   1  
4. #define MIGRATE_MOVABLE       2  
5. #define MIGRATE_PCPTYPES      3 /* the number of types on the pcp lists */  
6. #define MIGRATE_RESERVE       3  
7. #define MIGRATE_ISOLATE       4 /* can't allocate from here */  
8. #define MIGRATE_TYPES         5

前三种类型已经介绍过

MIGRATE_PCPTYPES是per_cpu_pageset,即用来表示每CPU页框高速缓存的数据结构中的链表的迁移类型数目

MIGRATE_RESERVE是在前三种的列表中都没用可满足分配的内存块时,就可以从MIGRATE_RESERVE分配

MIGRATE_ISOLATE用于跨越NUMA节点移动物理内存页,在大型系统上,它有益于将物理内存页移动到接近于是用该页最频繁地CPU

MIGRATE_TYPES表示迁移类型的数目

当一个指定的迁移类型所对应的链表中没有空闲块时,将会按以下定义的顺序到其他迁移类型的链表中寻找

  1. [cpp] view plaincopy
1. 2. static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {  
3.         [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },  
4.         [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_RESERVE },  
5.         [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },  
6. /* Never used */  
7.     };

Linux伙伴系统原理-内存分配和释放

主要分析Linux伙伴系统算法,内存的分配和释放

1.伙伴系统简介

     Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生,

Linux采用伙伴系统解决外部碎片的问题,采用slab解决内 部碎片的问题.

伙伴算法(Buddy system)把所有的空闲页框分为11个块链表,每块链表中分布包含特定的连续页框地址空间,比如第0个块链表包含大小为2^0个连续的页框,第1个块链表中,每个链表元素包含2个页框大小的连续地址空间,….,第10个块链表中,每个链表元素代表4M的连续地址空间。每个链表中元素的个数在系统初始化时决定,在执行过程中,动态变化。伙伴算法每次只能分配2的幂次页的空间,比如一次分配1页,2页,4页,8页,…,1024页(2^10)等等,每页大小一般为4K,因此,伙伴算法最多一次能够分配4M的内存空间。

伙伴系统java实现 伙伴系统分配内存题目_伙伴系统java实现_05

1.1.1 关键数据结构

struct zone {
    struct free_area    free_area[MAX_ORDER];
}
struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long        nr_free;
};

free_area共有MAX_ORDER个元素,其中第order个元素记录了2^order的空闲块,这些空闲块在free_list中以双向链表的形式组织起来,对于同等大小的空闲块,其类型不同,将组织在不同的free_list中,nr_free记录了该free_area中总共的空闲内存块的数量。MAX_ORDER的默认值为11,这意味着最大内存块的大小为2^10=1024个页框。对于同等大小的内存块,每个内存块的起始页框用于链表的节点进行相连,这些节点对应的着struct page中的lru域

struct page {
    struct list_head lru;        /* Pageout list, eg. active_list
                     * protected by zone->lru_lock !
                     */
}

1.1.2 迁移类型

不可移动页(Non-movable pages):这类页在内存当中有固定的位置,不能移动。内核的核心分配的内存大多属于这种类型
可回收页(Reclaimable pages):这类页不能直接移动,但可以删除,其内容页可以从其他地方重新生成,例如,映射自文件的数据属于这种类型,针对这种页,内核有专门的页面回收处理
可移动页:这类页可以随意移动,用户空间应用程序所用到的页属于该类别。它们通过页表来映射,如果他们复制到新的位置,页表项也会相应的更新,应用程序不会注意到任何改变。
当一个指定的迁移类型所对应的链表中没有空闲块时,将会按以下定义的顺序到其他迁移类型的链表中寻找

static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_RESERVE },
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
    [MIGRATE_RESERVE]     = { MIGRATE_RESERVE,     MIGRATE_RESERVE,   MIGRATE_RESERVE }, /* Never used */
};

通过cat /proc/pagetypeinfo可以看到迁移类型和order的关系

伙伴系统java实现 伙伴系统分配内存题目_伙伴系统java实现_06

1.2 内存的分配

 分配函数为alloc_pages,最终调用到__alloc_pages_nodemask

1. 2. __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
3. struct zonelist *zonelist, nodemask_t *nodemask)
4. {
5. struct zoneref *preferred_zoneref;
6. struct page *page = NULL;
7. unsigned int cpuset_mems_cookie;
8. int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR;
9. gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
10. struct alloc_context ac = {
11. /*根据gfp_mask,找到合适的zone idx和migrate类型 */
12. gfp_zone(gfp_mask),
13. 		.nodemask = nodemask,
14. gfpflags_to_migratetype(gfp_mask),
15. 	};
16.  
17. retry_cpuset:
18. read_mems_allowed_begin();
19.  
20. /* We set it here, as __alloc_pages_slowpath might have changed it */
21. 	ac.zonelist = zonelist;
22.  
23. /* Dirty zone balancing only done in the fast path */
24. 	ac.spread_dirty_pages = (gfp_mask & __GFP_WRITE);
25.  
26. /*根据分配掩码,确认先从哪个zone分配 */
27. /* The preferred zone is used for statistics later */
28. first_zones_zonelist(ac.zonelist, ac.high_zoneidx,
29. 				ac.nodemask ? : &cpuset_current_mems_allowed,
30. 				&ac.preferred_zone);
31. if (!ac.preferred_zone)
32. goto out;
33. zonelist_zone_idx(preferred_zoneref);
34.  
35. /* First allocation attempt */
36. 	alloc_mask = gfp_mask|__GFP_HARDWALL;
37. /*快速分配 */
38. get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
39. if (unlikely(!page)) {
40. /*慢速分配,涉及到内存回收,暂不分析 */
41. 		page = __alloc_pages_slowpath(alloc_mask, order, &ac);
42. 	}
43.  
44. out:
45. if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie)))
46. goto retry_cpuset;
47.  
48. return page;
49. }

1.2.1 水位控制

   每个zone有三个水位(watermark),用以标识系统内存存量,由数组 watermark[NR_WMARK]表示.

WMARK_MIN,WMARK_LOW,WMARK_HIGH,当内存存量低于对应水位时,就会调用zone_reclaim()进行内存回收.

mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
        if (!zone_watermark_ok(zone, order, mark,//判断水位是否正常
                       ac->classzone_idx, alloc_flags)) {            ret = zone_reclaim(zone, gfp_mask, order);
}

1.2.2 单个页面的分配

get_page_from_freelist->buffered_rmqueue:

order=0时,单个页面直接从per cpu的free list分配,这样效率最高.

1. 2. 		struct per_cpu_pages *pcp;
3. list;
4. 		local_irq_save(flags);
5. 		pcp = &this_cpu_ptr(zone->pageset)->pcp;
6. list = &pcp->lists[migratetype];
7. /*如果对应list为空,则从伙伴系统拿内存*/
8. if (list_empty(list)) {
9. 0,
10. list,
11. 					migratetype, gfp_flags);
12. if (unlikely(list_empty(list)))
13. goto failed;
14. 		}
15.  
16. /*分配一个页面 */
17. if ((gfp_flags & __GFP_COLD) != 0)
18. list->prev, struct page, lru);
19. else
20. list->next, struct page, lru);
21.  
22. if (!(gfp_flags & __GFP_CMA) &&
23. 				is_migrate_cma(get_pcppage_migratetype(page))) {
24. NULL;
25. 			local_irq_restore(flags);
26. else {
27. 			list_del(&page->lru);
28. 			pcp->count--;
29. 		}
30. 	}

1.2.3 多个页面的分配(order>1)

1. static struct page *__rmqueue(struct zone *zone, unsigned int order,
2. int migratetype, gfp_t gfp_flags)
3. {
4. struct page *page = NULL;
5. /*CMA内存的分配 */
6. if ((migratetype == MIGRATE_MOVABLE) && (gfp_flags & __GFP_CMA))
7. 		page = __rmqueue_cma_fallback(zone, order);
8.  
9. if (!page)/*根据order和migrate type找到对应的freelist分配内存 */
10. 		page = __rmqueue_smallest(zone, order, migratetype);
11.  
12. if (unlikely(!page))/*当对应的migrate type无法满足order分配时,进行fallback规则分配
13. ,分配规则定义在fallbacks数组中。 */
14. 		page = __rmqueue_fallback(zone, order, migratetype);
15.  
16.  
17. return page;
18. }

这里分析migrate type能够满足内存分配的情况

static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                        int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    /* 根据order,逐级向上查找*/
    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        area = &(zone->free_area[current_order]);
        if (list_empty(&area->free_list[migratetype]))
            continue;        page = list_entry(area->free_list[migratetype].next,
                            struct page, lru);
        list_del(&page->lru);
        rmv_page_order(page);
        area->nr_free--;  /* 切蛋糕,如果current_order大于目标order,则要把多余的内存挂到对应的order链表.*/
        expand(zone, page, order, current_order, area, migratetype);
        set_pcppage_migratetype(page, migratetype);
        return page;
    }    return NULL;
}1. static inline void expand(struct zone *zone, struct page *page,
2. int low, int high, struct free_area *area,
3. int migratetype)
4. {
5. unsigned long size = 1 << high;//先一分为2
6.  
7. while (high > low) {
8. /* 回退到下一级链表*/
9. 		high--;
10. 1;/*Size减半 */
11. /*接入下一级空闲链表 */
12. list_add(&page[size].lru, &area->free_list[migratetype]);
13. 		area->nr_free++;
14. set_page_order(&page[size], high);
15. 	}
16. }

1.2.4 fallback分配

当前migrate type不能满足内存分配需求时,需要到其他migrate type空闲链表分配内存.

规则如下:

static int fallbacks[MIGRATE_TYPES][4] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
    [MIGRATE_CMA]         = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    [MIGRATE_ISOLATE]     = { MIGRATE_TYPES }, /* Never used */
#endif
1. __rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype)
2. {
3. struct free_area *area;
4. unsigned int current_order;
5. struct page *page;
6. int fallback_mt;
7. bool can_steal;
8.  
9. /* 先从最大的order list中找到合适的块 */
10. for (current_order = MAX_ORDER-1;
11. -1;
12. 				--current_order) {
13. 		area = &(zone->free_area[current_order]);
14. /*从fallbacks数组中找到合适的migrate type链表 */
15. 		fallback_mt = find_suitable_fallback(area, current_order,
16. false, &can_steal);
17. if (fallback_mt == -1)
18. continue;
19.  
20. 		page = list_entry(area->free_list[fallback_mt].next,
21. struct page, lru);
22. if (can_steal &&/*改变pageblock的migrate type */
23. 			get_pageblock_migratetype(page) != MIGRATE_HIGHATOMIC)
24. 			steal_suitable_fallback(zone, page, start_migratetype);
25.  
26. /* Remove the page from the freelists */
27. 		area->nr_free--;
28. 		list_del(&page->lru);
29. 		rmv_page_order(page);
30. /*切蛋糕 */
31. 		expand(zone, page, order, current_order, area,
32. 					start_migratetype);
33.  
34. 		set_pcppage_migratetype(page, start_migratetype);
35.  
36. 		trace_mm_page_alloc_extfrag(page, order, current_order,
37. 			start_migratetype, fallback_mt);
38.  
39. return page;
40. 	}
41.  
42. return NULL;
43. }

如UNMOVABLE链表内存不足时,优先从RECLAIMABLE链表分配,再从MOVABLE链表分配

1.3 内存的释放

页面的释放,最终调用到__free_one_page函数.

首先两块内存是伙伴块,必须满足以下条件:

1. 伙伴不能在空洞页面中,要有实实在在的物理页面/the buddy is not in a hole/
2. 伙伴块在伙伴系统中,也就是伙伴块要是空闲的,没有被分配出去的内存块/the buddy is in the buddy system/
3. 要有相同的order /a page and its buddy have the same order/
4. 位于同一个zone./a page and its buddy are in the same zone/
5. 物理上要相连.且两个page的起始pfn号一定相差2^order,则有计算公式B2 = B1 ^ (1 << O)

static inline unsigned long
__find_buddy_index(unsigned long page_idx, unsigned int order)
{
    return page_idx ^ (1 << order);//计算buddy page的index
}1. static inline void __free_one_page(struct page *page,
2. unsigned long pfn,
3. struct zone *zone, unsigned int order,
4. int migratetype)
5. {
6. unsigned long page_idx;
7. unsigned long combined_idx;
8. unsigned long uninitialized_var(buddy_idx);
9. struct page *buddy;
10. unsigned int max_order;
11.  
12. unsigned int, MAX_ORDER, pageblock_order + 1);
13.  
14. /*计算page_idx, */
15. 1 << MAX_ORDER) - 1);
16.  
17. 1 << order) - 1), page);
18. 	VM_BUG_ON_PAGE(bad_range(zone, page), page);
19.  
20. continue_merging:
21. while (order < max_order - 1) {
22. /*找到buddy ix */
23. 		buddy_idx = __find_buddy_index(page_idx, order);
24. 		buddy = page + (buddy_idx - page_idx);
25. /*判断是否是伙伴块 */
26. if (!page_is_buddy(page, buddy, order))
27. goto done_merging;
28. /*
29. 		 * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
30. 		 * merge with it and move up one order.
31. 		 */
32. if (page_is_guard(buddy)) {
33. 			clear_page_guard(zone, buddy, order, migratetype);
34. else {
35. 			list_del(&buddy->lru);
36. 			zone->free_area[order].nr_free--;
37. 			rmv_page_order(buddy);
38. 		}
39. /*继续向上合并 */
40. 		combined_idx = buddy_idx & page_idx;
41. 		page = page + (combined_idx - page_idx);
42. 		page_idx = combined_idx;
43. 		order++;
44. 	}
45.  
46. done_merging:
47. 	set_page_order(page, order);
48.  
49. /*
50. 	 * If this is not the largest possible page, check if the buddy
51. 	 * of the next-highest order is free. If it is, it's possible
52. 	 * that pages are being freed that will coalesce soon. In case,
53. 	 * that is happening, add the free page to the tail of the list
54. 	 * so it's less likely to be used soon and more likely to be merged
55. 	 * as a higher order page
56. 	 */
57. if ((order < MAX_ORDER-2) && pfn_valid_within(page_to_pfn(buddy))) {
58. struct page *higher_page, *higher_buddy;
59. /*这里检查更上一级是否存在伙伴关系,如果是的,则把page添加到链表末尾,这样有利于页面回收. */
60. 		combined_idx = buddy_idx & page_idx;
61. 		higher_page = page + (combined_idx - page_idx);
62. 1);
63. 		higher_buddy = higher_page + (buddy_idx - combined_idx);
64. if (page_is_buddy(higher_page, higher_buddy, order + 1)) {
65. 			list_add_tail(&page->lru,
66. 				&zone->free_area[order].free_list[migratetype]);
67. goto out;
68. 		}
69. 	}
70. /*链接到对应的free list 链表 */
71. 	list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
72. out:
73. 	zone->free_area[order].nr_free++;
74. }

摘抄自网络,便于检索查找。