内核中分配内存基本都基于伙伴系统,但是在内核启动之初,伙伴系统尚未建立,这时需要一个临时的内存分配器负责提供内核早期的内存需求,例如存放内核的代码段和数据段,以及将内存进行简单的管理供后续伙伴系统使用,这就是bootmem分配器。
本文基于Linux 2.6.31的内核源码对bootmem分配器的工作过程进行分析。为方便说明代码流程,我以最简单的情形为例:在内核中没有开启内核选项CONFIG_DISCONTIMEM、CONFIG_SPARSEMEM和CONFIG_NUMA。只开启了CONFIG_FLATMEM选项,不考虑SMP的情况。
并且为了让代码更容易理解,我假设内存总大小为64MB,这样一些代码会看的更直观。
内核启动之前,内核代码已经拷贝到内存中,这部分内存不能被修改,即不能用作其他用处。所以,这样的内存要被标记为reserved(已被使用)。
内存是划分为节点的,每个节点关联到系统中的一个处理器,在内核中表示为struct pglist_data结构的实例(该数据结构后面会讲到)。
各个节点又划分为内存域,是内存的进一步细分,内核中定义了如下的内存域类型:
enum zone_type { #ifdef CONFIG_ZONE_DMA ZONE_DMA, #endif #ifdef CONFIG_ZONE_DMA32 ZONE_DMA32, #endif ZONE_NORMAL, #ifdef CONFIG_HIGHMEM ZONE_HIGHMEM, #endif ZONE_MOVABLE, __MAX_NR_ZONES };
ZONE_DMA标记适合DMA的内存域;
ZONE_DMA32标记了使用32位地址可寻址的、适合DMA的内存域,只有64位系统上两种DMA内存域才有差异,32位计算机上,本内存域是空的;
ZONE_NORMAL标记了可直接映射到内核段的普通内存域,该内存域是唯一一个所有体系结构上都会保证存在的内存域;
ZONE_HIGHMEM标记了超出内核段物理内存的高端内存域;
ZONE_MOVABLE是一个虚拟内存域,在防止物理内存碎片的机制中需要使用该内存域。
本文中我们只考虑ZONE_NORMAL内存域。
先看一些数据结构和全局变量:
结构体struct pglist_data用于表示内存节点,每个CPU都有一个这个结构,它的bdata成员指向系统启动期间的bootmem分配器的实例,用于管理属于这个节点的内存。
typedef struct pglist_data{ struct zone node_zones[MAX_NR_ZONES]; /* 节点中的内存域 */ struct zonelist node_zonelists[MAX_ZONELISTS]; /* 分配内存的备用列表 */ int nr_zones; struct bootmem_data *bdata; unsigned long node_start_pfn; unsigned long node_present_pages; /* total number of physicalpages */ unsigned long node_spanned_pages; /* total size of physical page range,including holes */ int node_id; /* node id */ wait_queue_head_t kswapd_wait; struct task_struct *kswapd; int kswapd_max_order; } pg_data_t;
在单处理器的情况下,我们只需要一个节点,内核为了方便,针对这种情况进行了如下定义:
struct pglist_data __refdata contig_page_data= { .bdata = &bootmem_node_data[0] };
bootmem_node_data数组也是一个全局定义:
bootmem_data_t bootmem_node_data[MAX_NUMNODES]__initdata;
单CPU的MAX_NUMNODES = 1。按照如上所说,bootmem_node_data[0]就是该CPU上的bootmem分配器的实例。
我们在获取CPU的节点时,不直接使用contig_page_data,而是通过下面的宏:
#define NODE_DATA(nid) (&contig_page_data)
例如通过NODE_DATA(0) 来获取node id为0的内存节点。
每个节点的bootmem分配器是一个struct bootmem_data结构,
/* * node_bootmem_map is a map pointer - the bitsrepresent all physical * memory pages (including holes) on the node. */ typedefstruct bootmem_data { unsigned long node_min_pfn; /* 可管理的第一个页帧的编号,通常是0 */ unsigned long node_low_pfn; /* 可管理的最后一个页帧的编号 */ void *node_bootmem_map; /* 位图,标记所有页帧的状态 */ unsigned long last_end_off; unsigned long hint_idx; struct list_head list; } bootmem_data_t;
其中,node_bootmem_map成员是一个位图,用于标记所有内存页帧是否是reserve的:1为reserved,0为空闲的。由于这时的系统处理启动初期,并不需要太复杂的内存分配机制,相反,bootmem分配器只用在内核启动初期,因此分配方式越简单越好,使用一个位图来管理所有的内存页就足够了。
系统中所有的bootmem分配器都放在一个全局链表上,用于全局查找特定内存区域来分配内存或标记页的状态。
static struct list_head bdata_list__initdata = LIST_HEAD_INIT(bdata_list);
在内核中,内存是以页为单位来管理的。通常约定“页帧”表示物理页,“页”表示虚拟页,注意,struct page结构描述的是物理页。在我的内核配置中,一个页的大小为4KB。
基于内存大小为64MB的假设,我们来看一下代码流程:
初始化的工作在bootmem_init()函数中完成,它做了如下的工作:
1. 给bdata->node_bootmem_map成员自身分配空间,因为我们后面会一直用到它。由于这时还没有什么内存分配机制,所以直接找能用的内存存放该成员即可。
我们已经提到,内核代码段是保留的,内核代码段中的最后一个符号为_end,在我编译内核后生成的System.map文件中可以看到它的值为0x81042470,由于0x8000000是内核态的基地址,所以,对于物理内存来说,_end的地址为0x1042470,一个page的大小为4K,则可知内核代码部分需占用0x1043个页帧的大小。
分配完成后,我们看到bdata->node_bootmem_map自身的起始地址是0x81043000,即紧跟在_end后面。每个bit位表示一个页帧是否是reserve的,1为reserve,0为可用。那标记所有0x4000(64MB)个页帧,需要的空间为(0x4000 / 8)个字节。
在这一步,所有的页帧都被标记为reserved了。
动作完成后,bdata即 bootmem_node_data[0]的值为:
bdata->node_bootmem_map的地址为0x81043000;
bdata->node_min_pfn =0x0;
bdata->node_low_pfn = 0x4000; /* 64M内存的页数,一页4K。 */
bdata->list被添加到全局链表bdata_list中。
2. 初始化early_node_map[],这个一个全局数组,全局变量nr_nodemap_entries为数组元素的个数。初始化完成后,
early_node_map[0].nid = 0;
early_node_map[0].start_pfn= 0x0;
early_node_map[0].end_pfn =0x4000;
nr_nodemap_entries = 1;
3. 将bdata->node_bootmem_map中0x1043到0x4000的页帧都标记为非reserve的,即这些内存是空闲的。
4. 将bdata->node_bootmem_map自身占用的空间标记为reserve的。
我们可以算一下,整个64M内存(0x0-0x4000个页帧),有哪些页被标记为了reserved。很简单:_end之前占用了0x1043个页帧,bdata->node_bootmem_map指向的地址空间占用了(0x4000 / 8)字节,不到一页,所以一共需要保留0x1044个页。
物理内存可分为下面的内存域:
enum zone_type { #ifdef CONFIG_ZONE_DMA ZONE_DMA, #endif #ifdef CONFIG_ZONE_DMA32 ZONE_DMA32, #endif ZONE_NORMAL, #ifdef CONFIG_HIGHMEM ZONE_HIGHMEM, #endif ZONE_MOVABLE, __MAX_NR_ZONES };
由于我们这几个宏都没定义,所以可以简化成这样:
include/linux/mmzone.h enum zone_type { /* * Normal addressable memoryis in ZONE_NORMAL. DMA operations can be * performed on pages inZONE_NORMAL if the DMA devices support * transfers to alladdressable memory. */ ZONE_NORMAL, ZONE_MOVABLE, __MAX_NR_ZONES };
这个函数的目的是初始化内存节点,即struct pglist_data结构,我们只有一个节点,其中包含ZONE_NORMAL和ZONE_MOVABLE两个内存域。
使用宏NODE_DATA(nid)可以获得node id为nid的节点,第一个节点nid=0。
该函数一开始初始化了好多全局变量,这些变量只在这个函数里面使用,所以我们并不关心。
我们只需要关注下面的代码:
void__init free_area_init_nodes(unsigned long *max_zone_pfn) { unsigned long nid; ······ for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); free_area_init_node(nid, NULL, find_min_pfn_for_node(nid), NULL); } ······ }
这里面find_min_pfn_for_node(int nid)函数返回early_node_map[nid].start_pfn的值。
free_area_init_node()函数用来完成节点的初始化,它的第2和第4个参数是NULL,因为在打开了CONFIG_ARCH_POPULATES_NODE_MAP配置时,不使用这两个参数。这个函数定义如下:
void __paginginitfree_area_init_node(int nid, unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size) { pg_data_t *pgdat = NODE_DATA(nid); pgdat->node_id = nid; pgdat->node_start_pfn = node_start_pfn; calculate_node_totalpages(pgdat, zones_size,zholes_size); alloc_node_mem_map(pgdat); free_area_init_core(pgdat, zones_size, zholes_size); }
calculate_node_totalpages()函数执行完后,内存节点pgdat的下列成员被赋值:
pgdat->node_id = 0;
pgdat->node_start_pfn = 0x0;
pgdat->node_spanned_pages = 0x4000;
pgdat->node_present_pages = 0x4000;
接着执行的alloc_node_mem_map()函数目的为分配用来管理这个节点上所有页帧的struct page结构。每个页帧都需要一个struct page来记录它的使用情况,所以需要0x4000个struct page结构,因此需要分配0x4000 *sizeof(struct page)大小的空间,分配完成后,pgdat->node_mem_map指向这个空间的起始地址:
pgdat->node_mem_map = 0x81044000;
通过赋值后pgdat->node_mem_map的地址,可以看到它是跟在struct bootmem_data结构位图的后面存放的。
需要注意,代码将内存映射对齐到伙伴系统的最大分配阶的页数MAX_ORDER_NR_PAGES,因为要使所有的计算都工作正常,这是必需的。
在这里同时给全局变量mem_map赋值为pgdat->node_mem_map。mem_map是一个struct page类型的指针,后面会用到它。于是这里赋值后,mem_map也指向0x81044000。
free_area_init_core()函数的主要工作是对节点中的所有内存域做全面的初始化,即pgdat的node_zones[]成员,它是struct zone类型的。
typedef struct pglist_data{
struct zone node_zones[MAX_NR_ZONES]; /* 内存域 */
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones; /* 包含内存域的个数。 */
······
}
函数名中带个“core”就能看出函数中做了很多工作。
注意,node_zones[]成员不是指针,而是内嵌到struct pglist_data结构中的。上面提到过我们的内核有ZONE_NORMAL和ZONE_MOVABLE两个内存域,因此MAX_NR_ZONES= 2。
struct zone是一个很大的结构体,该结构由ZONE_PADDING宏分隔为几部分,这样做是因为对zone结构的访问非常频繁,在多处理器系统上,通常会有不同的CPU试图同时访问结构成员。如果数据保存在CPU高速缓存中,那么会处理的非常快,使用ZONE_PADDING使zone中的每个自旋锁都处于自身的缓存行中。
#if defined(CONFIG_SMP) struct zone_padding { char x[0]; } ____cacheline_internodealigned_in_smp; #define ZONE_PADDING(name) struct zone_padding name; #else #define ZONE_PADDING(name) #endif
free_area_init_core()函数实现如下:
/* * Set up the zone data structures: * -mark all pages reserved * -mark all memory queues empty * -clear the memory bitmaps */ static void __paginginitfree_area_init_core(struct pglist_data *pgdat, unsigned long *zones_size,unsigned long *zholes_size) { enum zone_type j; int nid = pgdat->node_id; unsigned long zone_start_pfn = pgdat->node_start_pfn; int ret; pgdat_resize_init(pgdat); pgdat->nr_zones = 0; /* 包含zone的个数,随着下面for循环会增加。 */ /* 初始化pgdat->kswapd_wait:新建一个lock和list_head */ init_waitqueue_head(&pgdat->kswapd_wait); pgdat->kswapd_max_order = 0; pgdat_page_cgroup_init(pgdat); for (j = 0; j < MAX_NR_ZONES; j++) { struct zone *zone =pgdat->node_zones + j; unsigned long size, realsize, memmap_pages; enum lru_list l; size = zone_spanned_pages_in_node(nid, j, zones_size); realsize = size - zone_absent_pages_in_node(nid, j, zholes_size);/* 减去空洞 */ /* * Adjust realsize sothat it accounts for how much memory * is used by this zonefor memmap. This affects the watermark * and per-cpuinitialisations */ memmap_pages = PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT; if (realsize >= memmap_pages) { realsize -= memmap_pages; } else printk(KERN_WARNING " %s zone:%lu pages exceeds realsize %lu\n", zone_names[j], memmap_pages, realsize); /* Account for reserved pages */ if (j == 0 && realsize > dma_reserve) { realsize -= dma_reserve; } /* nr_kernel_pages统计所有一致映射的页,nr_all_pages除了一致映射的页还包含高端内存页在内。 */ if (!is_highmem_idx(j)) nr_kernel_pages += realsize; nr_all_pages += realsize; zone->spanned_pages=size; zone->present_pages=realsize; zone->name = zone_names[j]; spin_lock_init(&zone->lock); spin_lock_init(&zone->lru_lock); zone_seqlock_init(zone); zone->zone_pgdat=pgdat; /* 指向节点 */ zone->prev_priority = DEF_PRIORITY; /* 给zone->pageset[0]初始化 */ zone_pcp_init(zone); /* 冷热页分配器 */ for_each_lru(l) { INIT_LIST_HEAD(&zone->lru[l].list); zone->lru[l].nr_saved_scan = 0; } zone->reclaim_stat.recent_rotated[0] = 0; zone->reclaim_stat.recent_rotated[1] = 0; zone->reclaim_stat.recent_scanned[0] = 0; zone->reclaim_stat.recent_scanned[1] = 0; zap_zone_vm_stats(zone); /* 将zone->vm_stat统计信息清零 */ zone->flags = 0; /* zone flags */ if (!size) continue; set_pageblock_order(pageblock_default_order()); /* empty. */ /* 计算zone->pageblock_flags需要的空间并分配空间 */ setup_usemap(pgdat, zone, size); ret = init_currently_empty_zone(zone,zone_start_pfn, size, MEMMAP_EARLY); BUG_ON(ret); /* zone中每一页的struct page信息的初始化,并将所有page标记为reserved(设置page->flags位为PG_reserved)。上面讲到过这些page在函数alloc_node_mem_map()中分配空间了。 */ memmap_init(size,nid, j, zone_start_pfn); zone_start_pfn += size; } }
该函数的主要工作有以下几点:
1. memmap_init()函数将zone中每个页帧对应的struct page信息的初始化,并将所有page标记为reserved(设置page->flags位为PG_reserved)。
2. 在memmap_init_zone()函数中将所有的页帧的迁移类型标记为MIGRATE_MOVABLE。为页帧设置迁移类型是内核防止内存碎片而将具有相同可移动性的页进行分组的一种手段,在这里我们不做讨论。
struct zone的pageblock_flags成员用于保存所有页帧的迁移类型,它是一个位图。目前内核中定义了5中迁移类型,所以该页是何种迁移类型需要占用3个bit位来记录,这样一来, pageblock_flags的每3bit(如0-2、3-5、6-8、9-11位)标记一个页的迁移类型。
3. init_currently_empty_zone()函数初始化zone->free_area[],这个数组需要后续重点关注,因为伙伴系统会基于该数组来分配内存。不过在此时只是初始化链表头,并没有往里面添加页,直至停用bootmem分配器,开启伙伴分配器,才会被正确设置。
build_all_zonelists()函数为内存节点structpglist_data结构的node_zonelists[MAX_ZONELISTS]成员赋值,在UMA系统中,MAX_ZONELISTS=1。
pgdat的node_zonelists[]成员指定了备用节点及其内存域的列表,以便在当前节点没有可用空间时,在备用节点分配内存。
typedef struct pglist_data{ struct zone node_zones[MAX_NR_ZONES]; struct zonelist node_zonelists[MAX_ZONELISTS]; int nr_zones; ······ } pg_data_t;
struct zonelist的定义如下,其中_zonerefs[]数组即保存这个list中包含的所有内存域。
struct zonelist { struct zonelist_cache *zlcache_ptr; // NULL or &zlcache struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1]; #ifdef CONFIG_NUMA struct zonelist_cache zlcache; //optional ... #endif }; /* Maximum number of zoneson a zonelist */ #defineMAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)
注意,_zonerefs成员并没有定义为struct zone结构,而是struct zoneref结构体,这个结构是struct zone的包裹,增加了一个zone_idx成员,用来方便读取zonelist中特定的zone。
struct zoneref { struct zone *zone; /*Pointer to actual zone */ int zone_idx; /*zone_idx(zoneref->zone) */ };
build_zonelists()代码:
static voidbuild_zonelists(pg_data_t *pgdat) { int node, local_node; enum zone_type j; struct zonelist *zonelist; local_node = pgdat->node_id; /* 0 */ zonelist = &pgdat->node_zonelists[0]; /* 先添加本节点上的zone到zonelist中去。 */ j = build_zonelists_node(pgdat,zonelist, 0, MAX_NR_ZONES - 1); /* 添加其他节点的zone: * Now we build the zonelistso that it contains the zones * of all the other nodes. * We don't want to pressurea particular node, so when * building the zones fornode N, we make sure that the * zones coming right afterthe local ones are those from * node N+1 (modulo N) */ /* 下面两个for循环是把local_node+1 ~ MAX_NUMNODES以及0 ~ local_node的节点的zone添加到当前节点的zonelist中去。作为备用。*/ for (node = local_node + 1; node < MAX_NUMNODES; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node),zonelist, j, MAX_NR_ZONES - 1); } for (node = 0; node < local_node; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node),zonelist, j, MAX_NR_ZONES - 1); } /* 添加完成,以NULL作为结尾标识。 */ zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0; }
其中添加nodelist的函数为build_zonelists_node(),该函数返回已经添加完成的节点的index,供下次添加继续使用。
static intbuild_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones,enum zone_type zone_type) { struct zone *zone; BUG_ON(zone_type >= MAX_NR_ZONES); zone_type++; do { zone_type--; zone = pgdat->node_zones + zone_type; if (populated_zone(zone)) { zoneref_set_zone(zone, &zonelist->_zonerefs[nr_zones++]); check_highest_zone(zone_type); /* empty. */ } } while (zone_type); return nr_zones; }
代码中以内存域类型优先级递增的顺序添加备用节点。内核定义了内存的一个层次结构,首先试图分配“廉价”内存,如果失败,则根据访问速度和容量,逐渐尝试分配更“昂贵”的内存。其优先级顺序如下:
1. 高端内存是最廉价的,因为内核没有任何部分依赖于从该内存域分配的内存。如果高端内存域用尽,对内核没有任何副作用。
2. 普通内存则不同,许多内核数据结构必须保存在该内存域,如果普通内存完全用尽,那么内核会面临emergency。
3. DMZ内存最为昂贵,因为它用于外设和系统之间的数据传输。
4. 如果当前节点所有内存域都用尽,内核还可以从当前节点的备用节点上分配内存。
例如,一个系统有4个节点,每个节点都包含内存域ZONE_HIGHMEM、ZONE_NORMAL和ZONE_DMA:
A=(NUMA)节点0 0=DMA内存域
B=(NUMA)节点1 1=普通内存域
C=(NUMA)节点2 2=高端内存域
D=(NUMA)节点3
那经过build_all_zonelists()函数后,节点2的pgdat->node_zonelists[0]._zonerefs[]的内容为:
对总数N个节点的节点m来说,内核生成备用列表时,选择备用节点的顺序是:m、m+1、m+2、···、N-1、0、1、···、m-1。这确保了不过度使用任何节点。
对于我们的系统来讲,只有ZONE_NORMAL和ZONE_MOVABLE,其中MOVABLE是虚拟内存域。所以只需要记录ZONE_NORMAL的内存域,并且只有一个节点。
所以,有:
pgdat->node_zonelists[0]. _zonerefs[0]= ZONE_NORMAL内存域;
pgdat->node_zonelists[0]. _zonerefs[1]= NULL;
build_all_zonelists()函数最后根据节点中可用内存页的数目,决定是否打开页面迁移特性,如果可用页太少,则没有必要打开,于是将page_group_by_mobility_disabled=1,关于该特性详见伙伴系统部分。(这个特性可以在系统启动时关闭,后面如果有memory-hotadd使节点的页面数增加,内核会检查然后打开)。
/* * Set up kernel memory allocators */ static void __initmm_init(void) { /* * page_cgroup requirescountinous pages as memmap * and it's bigger thanMAX_ORDER unless SPARSEMEM. */ page_cgroup_init_flatmem(); /* empty */ mem_init(); kmem_cache_init(); pgtable_cache_init(); /* empty */ vmalloc_init(); }
mm_init()用于停用bootmem分配器并迁移到实际的内存管理函数。
停用bootmem分配器,我们上面讲到过pgdat->bdata->node_bootmem_map位图用于标记bootmem分配器管理的每个页帧是reserve的还是非reserve的。mem_map即pgdat->node_mem_map用于管理所有页帧的struct page结构。
这里的工作就是停用pgdat->bdata->node_bootmem_map,并将pgdat->node_mem_map中可用的页的page->flags的PG_reserved位清除(前面讲到过free_area_init_core()函数曾将所有的页都标记为PG_reserved的),即可供后续使用。
哪些页可以清除PG_reserved标记呢?pgdat->bdata->node_bootmem_map位图中为0的页肯定是可以的,另外就是pgdat->bdata->node_bootmem_map本身占用的空间,因为该位图在停用bootmem分配器之后就没用了。
释放页是通过__free_pages_bootmem()完成的,它调用了下面的函数:
__ClearPageReserved(page); set_page_count(page, 0); set_page_refcounted(page); __free_page(page);
__free_page()函数没看。不过这个函数的一个作用就是将页释放到zone->free_area[]里面去或者放到pcp的冷热页链表中(冷热页在伙伴系统部分介绍),而上面讲到过zone->free_area[]数组是用于伙伴系统的,并且将free_area[]的nr_free成员都赋值为0。而现在要停用bootmem并启用伙伴系统了,所以我们要给free_area[]赋正确的值。
free_area[]数组的长度为MAX_ORDER,即伙伴系统的最大分配阶,
struct zone { ...... structfree_area free_area[MAX_ORDER]; ...... }____cacheline_internodealigned_in_smp;
buddy分配器分配的最小单位是一页,并且只能分配2的幂次的页。struct zone的free_area[]数组存放了各阶的内存列表,数组下标可取0~MAX_ORDER-1。
struct free_area { struct list_head free_list[MIGRATE_TYPES]; unsigned long nr_free; };
struct free_area有两个成员,free_list[]是不同migrate type页链表的数组,每个数组元素就是一个structpage的链表,由每个struct page的page->lru连起来。nr_free表示这个order空闲页的buddy数量,例如,阶为2的页块共有2个,则nr_free=2,实际上这个阶的空闲页数为2^2*2=8。
在free_all_bootmem_core()函数的开头和结尾都加上下面的打印,即打印各分配阶的伙伴数:
my_zone =&(NODE_DATA(0)->node_zones[ZONE_NORMAL]); for (i = 0; i < MAX_ORDER; i++) printk("[free_all_bootmem_core] order %d nr_free%lu\n", i, my_zone->free_area[i].nr_free);
打印结果如下:
开头:
[ 0.000000] [free_all_bootmem_core] order 0nr_free 0
[ 0.000000] [free_all_bootmem_core] order 1nr_free 0
[ 0.000000] [free_all_bootmem_core] order 2nr_free 0
[ 0.000000] [free_all_bootmem_core] order 3nr_free 0
[ 0.000000] [free_all_bootmem_core] order 4nr_free 0
[ 0.000000] [free_all_bootmem_core] order 5nr_free 0
[ 0.000000] [free_all_bootmem_core] order 6nr_free 0
[ 0.000000] [free_all_bootmem_core] order 7nr_free 0
[ 0.000000] [free_all_bootmem_core] order 8nr_free 0
[ 0.000000] [free_all_bootmem_core] order 9nr_free 0
[ 0.000000] [free_all_bootmem_core] order 10nr_free 0
结尾:
[ 0.000000] [free_all_bootmem_core] order 0nr_free 1
[ 0.000000] [free_all_bootmem_core] order 1nr_free 2
[ 0.000000] [free_all_bootmem_core] order 2nr_free 1
[ 0.000000] [free_all_bootmem_core] order 3nr_free 1
[ 0.000000] [free_all_bootmem_core] order 4nr_free 1
[ 0.000000] [free_all_bootmem_core] order 5nr_free 2
[ 0.000000] [free_all_bootmem_core] order 6nr_free 1
[ 0.000000] [free_all_bootmem_core] order 7nr_free 1
[ 0.000000] [free_all_bootmem_core] order 8nr_free 2
[ 0.000000] [free_all_bootmem_core] order 9nr_free 2
[ 0.000000] [free_all_bootmem_core] order 10nr_free 10
可见,节点的内存域已经为伙伴系统准备好了。
在内核中要通过bootmem分配器分配内存可使用alloc_bootmem()和alloc_bootmem_pages()两个接口,他们都通过__alloc_bootmem()函数完成分配内存的工作。当然,这些函数只能在bootmem分配器还未停用的时候来使用。