5.2.6 添砖加瓦
回到setup_arch,来到1007,调用paging_init()进行页面初始化。
825void __init paging_init(void) 826{ 827 pagetable_init(); 828 829 __flush_tlb_all(); 830 831 kmap_init(); 832 833 /* 834 * NOTE: at this point the bootmem allocator is fully available. 835 */ 836 sparse_init(); 837 zone_sizes_init(); 838} |
我们看到,paging_init()分别调用下面的几个函数来实现页面的初始化工作:
pagetable_init();
kmap_init();
sparse_init();
zone_sizes_init();
首先pagetable_init()初始化页全局目录的目录项:
544 static void __init pagetable_init(void) 545 { 546 pgd_t *pgd_base = swapper_pg_dir; 547 548 permanent_kmaps_init(pgd_base); 549 } |
如果CONFIG_HIGHMEM被配置(当前大多数系统肯定都是大内存,都会有配置),则调用permanent_kmaps_init函数,把页全局目录swapper_pg_dir地址传给他。该函数调用page_table_range_init设置目录项。随后paging_init的两个函数kmap_init() 和sparse_init()分别对第一个页表项进行缓存,以及映射非线性的段(也就是不连续的段)。至于高端内存的映射,请查看博客“高端内存映射”相关内容。
paging_init的最后调用zone_sizes_init():
739static void __init zone_sizes_init(void) 740{ 741 unsigned long max_zone_pfns[MAX_NR_ZONES]; 742 memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); 743 max_zone_pfns[ZONE_DMA] = 744 virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT; 745 max_zone_pfns[ZONE_NORMAL] = max_low_pfn; 746#ifdef CONFIG_HIGHMEM 747 max_zone_pfns[ZONE_HIGHMEM] = highend_pfn; 748#endif 749 750 free_area_init_nodes(max_zone_pfns); 751} |
zone_sizes_init的741~749行设置一个内部变量max_zone_pfns数组其只有三个元素:ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,表示我们NUMA体系中一个NODE的三个Zone。那么元素的具体值就是每个Zone的理论上能使用的页框数最大值。
最后750行调用free_area_init_nodes()来实现节点空闲区域的初始化,这函数很复杂,其本质上就等于:
void __init free_area_init_nodes(unsigned long *max_zone_pfn)
{
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);
/* Any memory on that node */
if (pgdat->node_present_pages)
node_set_state(nid, N_HIGH_MEMORY);
check_for_regular_memory(pgdat);
}
}
for_each_online_node是什么呢?它定义为:
#define for_each_online_node(node) for_each_node_state(node, N_ONLINE)
#define for_each_node_state(__node, __state) /
for_each_node_mask((__node), node_states[__state])
#define for_each_node_mask(node, mask) /
for ((node) = first_node(mask); /
(node) < MAX_NUMNODES; /
(node) = next_node((node), (mask)))
而first_node和next_node又分别是:
#define first_node(src) __first_node(&(src))
static inline int __first_node(const nodemask_t *srcp)
{
return min_t(int, MAX_NUMNODES, find_first_bit(srcp->bits, MAX_NUMNODES));
}
#define next_node(n, src) __next_node((n), &(src))
static inline int __next_node(int n, const nodemask_t *srcp)
{
return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
}
好了,由于我们假设只有一个节点,所以这也是一个只执行一次的循环。这里有个奇怪的NODE_DATA(nid),这个nid我们知道,是0。那么NODE_DATA宏是什么?来看它的定义:
extern struct pglist_data *node_data[];
#define NODE_DATA(nid) (node_data[nid])
来了,最著名的pglist_data结构出现了:
600typedef struct pglist_data { 601 struct zone node_zones[MAX_NR_ZONES]; 602 struct zonelist node_zonelists[MAX_ZONELISTS]; 603 int nr_zones; 604#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */ 605 struct page *node_mem_map; 606#ifdef CONFIG_CGROUP_MEM_RES_CTLR 607 struct page_cgroup *node_page_cgroup; 608#endif 609#endif 610#ifndef CONFIG_NO_BOOTMEM 611 struct bootmem_data *bdata; 612#endif 613#ifdef CONFIG_MEMORY_HOTPLUG 614 /* 615 * Must be held any time you expect node_start_pfn, node_present_pages 616 * or node_spanned_pages stay constant. Holding this will also 617 * guarantee that any pfn_valid() stays that way. 618 * 619 * Nests above zone->lock and zone->size_seqlock. 620 */ 621 spinlock_t node_size_lock; 622#endif 623 unsigned long node_start_pfn; 624 unsigned long node_present_pages; /* total number of physical pages */ 625 unsigned long node_spanned_pages; /* total size of physical page 626 range, including holes */ 627 int node_id; 628 wait_queue_head_t kswapd_wait; 629 struct task_struct *kswapd; 630 int kswapd_max_order; 631} pg_data_t; |
这个结构是每个NUMA节点一个,那么我PC上唯一的0号NODE就是NODE_DATA(0),也就是全局数组node_data[]的0号元素。
再来看接下来的free_area_init_node函数,来自mm/page_alloc.c:
3932void __paginginit free_area_init_node(int nid, unsigned long *zones_size, 3933 unsigned long node_start_pfn, unsigned long *zholes_size) 3934{ 3935 pg_data_t *pgdat = NODE_DATA(nid); 3936 3937 pgdat->node_id = nid; 3938 pgdat->node_start_pfn = node_start_pfn; 3939 calculate_node_totalpages(pgdat, zones_size, zholes_size); 3940 3941 alloc_node_mem_map(pgdat); 3942#ifdef CONFIG_FLAT_NODE_MEM_MAP 3943 printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx/n", 3944 nid, (unsigned long)pgdat, 3945 (unsigned long)pgdat->node_mem_map); 3946#endif 3947 3948 free_area_init_core(pgdat, zones_size, zholes_size); 3949} |
传递给他的参数作为整个pg_data_t的第一个页号,由函数find_min_pfn_for_node(0)计算出:
static unsigned long __init find_min_pfn_for_node(int nid)
{
int i;
unsigned long min_pfn = ULONG_MAX;
/* Assuming a sorted map, the first range found has the starting pfn */
for_each_active_range_index_in_nid(i, nid)
min_pfn = min(min_pfn, early_node_map[i].start_pfn);
if (min_pfn == ULONG_MAX) {
printk(KERN_WARNING
"Could not find start_pfn for node %d/n", nid);
return 0;
}
return min_pfn;
}
其实很简单,就是位于early_node_map[]数组中第一个可用的页号(ULONG_MAX是32个1),即第一个空闲页面的页号。注意这里,最后返回的其实是early_node_map[]数组中,start_pfn最小的那个元素的值。
3935行的pgdat我们已经知道了,随后初始化它的两个字段:NUMA节点号和第一个Zone的第一个页框号。接着3939行计算pgdat的所有Zone的总共页面数和可用页面数,针对我们的x86体系MAX_NR_ZONES等于3:
3699static void __meminit calculate_node_totalpages(struct pglist_data *pgdat, 3700 unsigned long *zones_size, unsigned long *zholes_size) 3701{ 3702 unsigned long realtotalpages, totalpages = 0; 3703 enum zone_type i; 3704 3705 for (i = 0; i < MAX_NR_ZONES; i++) 3706 totalpages += zone_spanned_pages_in_node(pgdat->node_id, i, 3707 zones_size); 3708 pgdat->node_spanned_pages = totalpages; 3709 3710 realtotalpages = totalpages; 3711 for (i = 0; i < MAX_NR_ZONES; i++) 3712 realtotalpages -= 3713 zone_absent_pages_in_node(pgdat->node_id, i, 3714 zholes_size); 3715 pgdat->node_present_pages = realtotalpages; 3716 printk(KERN_DEBUG "On node %d totalpages: %lu/n", pgdat->node_id, 3717 realtotalpages); 3718} |
然后free_area_init_node的3941行,调用alloc_node_mem_map()为我们的NODE(0)的所有页描述符page分配空闲区域:
3891static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat) 3892{ 3893 /* Skip empty nodes */ 3894 if (!pgdat->node_spanned_pages) 3895 return; 3896 3897#ifdef CONFIG_FLAT_NODE_MEM_MAP 3898 /* ia64 gets its own node_mem_map, before this, without bootmem */ 3899 if (!pgdat->node_mem_map) { 3900 unsigned long size, start, end; 3901 struct page *map; 3902 3903 /* 3904 * The zone's endpoints aren't required to be MAX_ORDER 3905 * aligned but the node_mem_map endpoints must be in order 3906 * for the buddy allocator to function correctly. 3907 */ 3908 start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1); 3909 end = pgdat->node_start_pfn + pgdat->node_spanned_pages; 3910 end = ALIGN(end, MAX_ORDER_NR_PAGES); 3911 size = (end - start) * sizeof(struct page); 3912 map = alloc_remap(pgdat->node_id, size); 3913 if (!map) 3914 map = alloc_bootmem_node(pgdat, size); 3915 pgdat->node_mem_map = map + (pgdat->node_start_pfn - start); 3916 } 3917#ifndef CONFIG_NEED_MULTIPLE_NODES 3918 /* 3919 * With no DISCONTIG, the global mem_map is just set as node 0's 3920 */ 3921 if (pgdat == NODE_DATA(0)) { 3922 mem_map = NODE_DATA(0)->node_mem_map; 3923#ifdef CONFIG_ARCH_POPULATES_NODE_MAP 3924 if (page_to_pfn(mem_map) != pgdat->node_start_pfn) 3925 mem_map -= (pgdat->node_start_pfn - ARCH_PFN_OFFSET); 3926#endif /* CONFIG_ARCH_POPULATES_NODE_MAP */ 3927 } 3928#endif 3929#endif /* CONFIG_FLAT_NODE_MEM_MAP */ 3930} |
看到3897行,我在.config中找了找,没有看到CONFIG_FLAT_NODE_MEM_MAP配置选项,估计是最新的内核已经不把FLATMEM作为默认内存模型了。既然提到了内存模型(Memory Model),就多给大家补充一下这方面的知识。什么是内存模型,针对x86体系就是对页面的管理算法及其相关的数据结构。Linux目前支持三种内存管理模型:FLATMEM(平坦模型)、DISCONTIGMEM(折扣模型)和SPARSEMEM(稀疏模型)。不知道从哪个Linux版本以后,就把SPARSEMEM作为默认的内存模型了。
FLATMEM模型将所有的页描述符,全都放到全局数组mem_map中:
struct page *mem_map;
mem_map的每一个元素就是一个page结构,那么当我要获得一个页面的信息,就把对应页面的页号pfn加上首地址mem_map就能得到对应的页描述符结构page。
DISCONTIGMEM和SPARSEMEM模型则要复杂得多,牵涉到一个vmemmap全局变量和mem_section数据结构。笔者对这两个模型没有深入的研究,所以不敢妄言。下面就假设我们配置了CONFIG_FLAT_NODE_MEM_MAP,针对FLATMEM模型来对余下的代码进行讲解。
3899行pgdat->node_mem_map还没有初始化,肯定是空的,所以进入条件分支。设置内部变量start、end、size分别为本节点pglist_data的第一个可用页面页号node_start_pfn,最后一个可用页面页号,以及总的page大小。3912行首先尝试一下alloc_remap分配,由于我们没有配置CONFIG_HAVE_ARCH_ALLOC_REMAP,所以肯定会失败,那么肯定会调用alloc_bootmem_node函数进行分配。这个函数最终会走到__alloc_bootmem_node函数:
833void * __init __alloc_bootmem_node(pg_data_t *pgdat, unsigned long size, 834 unsigned long align, unsigned long goal) 835{ 836 if (WARN_ON_ONCE(slab_is_available())) 837 return kzalloc_node(size, GFP_NOWAIT, pgdat->node_id); 838 839#ifdef CONFIG_NO_BOOTMEM 840 return __alloc_memory_core_early(pgdat->node_id, size, align, 841 goal, -1ULL); 842#else 843 return ___alloc_bootmem_node(pgdat->bdata, size, align, goal, 0); 844#endif 845} |
836行,WARN_ON_ONCE(slab_is_available()),内有只要有万分之一可能性都要去尝试一下,比如这里有可能一个计算集群中的其他节点先于本地节点初始化了一个slab分配器,就去尝试使用它来分配。
我们配置了CONFIG_NO_BOOTMEM,所以调用__alloc_memory_core_early函数:
3412void * __init __alloc_memory_core_early(int nid, u64 size, u64 align, 3413 u64 goal, u64 limit) 3414{ 3415 int i; 3416 void *ptr; 3417 3418 /* need to go over early_node_map to find out good range for node */ 3419 for_each_active_range_index_in_nid(i, nid) { 3420 u64 addr; 3421 u64 ei_start, ei_last; 3422 3423 ei_last = early_node_map[i].end_pfn; 3424 ei_last <<= PAGE_SHIFT; 3425 ei_start = early_node_map[i].start_pfn; 3426 ei_start <<= PAGE_SHIFT; 3427 addr = find_early_area(ei_start, ei_last, 3428 goal, limit, size, align); 3429 3430 if (addr == -1ULL) 3431 continue; 3432 3433#if 0 3434 printk(KERN_DEBUG "alloc (nid=%d %llx - %llx) (%llx - %llx) %llx %llx => %llx/n", 3435 nid, 3436 ei_start, ei_last, goal, limit, size, 3437 align, addr); 3438#endif 3439 3440 ptr = phys_to_virt(addr); 3441 memset(ptr, 0, size); 3442 reserve_early_without_check(addr, addr + size, "BOOTMEM"); 3443 return ptr; 3444 } 3445 3446 return NULL; 3447} |
for_each_active_range_index_in_nid(i, nid)我们见过了,遍历所有early_node_map数组的元素。3427行,调用最低级的函数find_early_area在early_node_map[]数组的某个元素中找到一个可用的空间,来存放mem_map数组,对这个函数还有一些疑惑的同学可以在“着手建立永久内核页表”一节中找到答案。
最后,通过pgdat->node_mem_map = map + (pgdat->node_start_pfn - start),将pglist_data的node_mem_map字段指向这个size大小的page区域。
存放所有页描述符page的空间有了,回到free_area_init_node函数中。3948行,调用free_area_init_core,传递给他的参数是本节点的pglist_data、为NULL的zones_size和zholes_size:
3793/* 3794 * Set up the zone data structures: 3795 * - mark all pages reserved 3796 * - mark all memory queues empty 3797 * - clear the memory bitmaps 3798 */ 3799static void __paginginit free_area_init_core(struct pglist_data *pgdat, 3800 unsigned long *zones_size, unsigned long *zholes_size) 3801{ 3802 enum zone_type j; 3803 int nid = pgdat->node_id; 3804 unsigned long zone_start_pfn = pgdat->node_start_pfn; 3805 int ret; 3806 3807 pgdat_resize_init(pgdat); 3808 pgdat->nr_zones = 0; 3809 init_waitqueue_head(&pgdat->kswapd_wait); 3810 pgdat->kswapd_max_order = 0; 3811 pgdat_page_cgroup_init(pgdat); 3812 3813 for (j = 0; j < MAX_NR_ZONES; j++) { 3814 struct zone *zone = pgdat->node_zones + j; 3815 unsigned long size, realsize, memmap_pages; 3816 enum lru_list l; 3817 3818 size = zone_spanned_pages_in_node(nid, j, zones_size); 3819 realsize = size - zone_absent_pages_in_node(nid, j, 3820 zholes_size); 3821 3822 /* 3823 * Adjust realsize so that it accounts for how much memory 3824 * is used by this zone for memmap. This affects the watermark 3825 * and per-cpu initialisations 3826 */ 3827 memmap_pages = 3828 PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT; 3829 if (realsize >= memmap_pages) { 3830 realsize -= memmap_pages; 3831 if (memmap_pages) 3832 printk(KERN_DEBUG 3833 " %s zone: %lu pages used for memmap/n", 3834 zone_names[j], memmap_pages); 3835 } else 3836 printk(KERN_WARNING 3837 " %s zone: %lu pages exceeds realsize %lu/n", 3838 zone_names[j], memmap_pages, realsize); 3839 3840 /* Account for reserved pages */ 3841 if (j == 0 && realsize > dma_reserve) { 3842 realsize -= dma_reserve; 3843 printk(KERN_DEBUG " %s zone: %lu pages reserved/n", 3844 zone_names[0], dma_reserve); 3845 } 3846 3847 if (!is_highmem_idx(j)) 3848 nr_kernel_pages += realsize; 3849 nr_all_pages += realsize; 3850 3851 zone->spanned_pages = size; 3852 zone->present_pages = realsize; 3853#ifdef CONFIG_NUMA 3854 zone->node = nid; 3855 zone->min_unmapped_pages = (realsize*sysctl_min_unmapped_ratio) 3856 / 100; 3857 zone->min_slab_pages = (realsize * sysctl_min_slab_ratio) / 100; 3858#endif 3859 zone->name = zone_names[j]; 3860 spin_lock_init(&zone->lock); 3861 spin_lock_init(&zone->lru_lock); 3862 zone_seqlock_init(zone); 3863 zone->zone_pgdat = pgdat; 3864 3865 zone->prev_priority = DEF_PRIORITY; 3866 3867 zone_pcp_init(zone); 3868 for_each_lru(l) { 3869 INIT_LIST_HEAD(&zone->lru[l].list); 3870 zone->reclaim_stat.nr_saved_scan[l] = 0; 3871 } 3872 zone->reclaim_stat.recent_rotated[0] = 0; 3873 zone->reclaim_stat.recent_rotated[1] = 0; 3874 zone->reclaim_stat.recent_scanned[0] = 0; 3875 zone->reclaim_stat.recent_scanned[1] = 0; 3876 zap_zone_vm_stats(zone); 3877 zone->flags = 0; 3878 if (!size) 3879 continue; 3880 3881 set_pageblock_order(pageblock_default_order()); 3882 setup_usemap(pgdat, zone, size); 3883 ret = init_currently_empty_zone(zone, zone_start_pfn, 3884 size, MEMMAP_EARLY); 3885 BUG_ON(ret); 3886 memmap_init(size, nid, j, zone_start_pfn); 3887 zone_start_pfn += size; 3888 } 3889} |
3807行,调用pgdat_resize_init函数初始化pglist_data的自旋锁:
void pgdat_resize_init(struct pglist_data *pgdat)
{
spin_lock_init(&pgdat->node_size_lock);
}
3809行,初始化pglist_data的kswapd_wait字段,作为等待队列的头。有关等待队列的知识如果还欠缺的话,请访问博客“非运行状态进程的组织”。
3813,进入一个循环,循环次数MAX_NR_ZONES,我们知道是3。每个NODE的Zone信息都存放在pglist_data的node_zones数组中,所以3814首先获得每个元素的地址,赋给内部变量zone。随后zone_spanned_pages_in_node为根据传递进来的zholes_size去掉一些page。由于我们传递进来的是NULL,所以这个函数啥也不做,得到的size实际上就是页框page的总量,即early_node_map[i].end_pfn减去early_node_map[i].start_pfn的结果:
void __meminit get_pfn_range_for_nid(unsigned int nid,
unsigned long *start_pfn, unsigned long *end_pfn)
{
int i;
*start_pfn = -1UL;
*end_pfn = 0;
for_each_active_range_index_in_nid(i, nid) {
*start_pfn = min(*start_pfn, early_node_map[i].start_pfn);
*end_pfn = max(*end_pfn, early_node_map[i].end_pfn);
}
if (*start_pfn == -1UL)
*start_pfn = 0;
}
继续走,3819行zone_absent_pages_in_node也是个空函数,所以realsize与size的值相等。3827行,计算出所有页框的大小:PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT。结果左移PAGE_SHIFT位的意思我们很熟悉了,就是计算出要装下size个页描述符需要几个实际的页框,最后把这个数赋值给内部变量memmap_pages。这个逻辑关系千万要注意!
继续,2829 - 3838其实是个出错信息的打印,因为如果计算出来的memmap_pages比现在已经有的realsize还大,那我PC内存可就太小了,可以退休了。3841 - 3849,让realsize去掉一些保留的页面并增加一些内核使用的页面数量,最后将计算出的总的页框数量size和实际使用的页框数量realsize赋值给zone的spanned_pages字段和present_pages。
后面3853 - 3885初始化每个pgdat->node_zones数组的其他字段,最后3886行调用memmap_init(size, nid, j, zone_start_pfn)初始化所有的页描述符:
#ifndef __HAVE_ARCH_MEMMAP_INIT
#define memmap_init(size, nid, zone, start_pfn) /
memmap_init_zone((size), (nid), (zone), (start_pfn), MEMMAP_EARLY)
#endif
.config文件中找不到__HAVE_ARCH_MEMMAP_INIT,所以内核调用memmap_init_zone()初始化所有的页描述符,下面是具体的初始化,传入的参数是总页框数size,节点号nid(为0),zone号(0~2),以及第一个可用页面的页号zone_start_pfn:
2995void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone, 2996 unsigned long start_pfn, enum memmap_context context) 2997{ 2998 struct page *page; 2999 unsigned long end_pfn = start_pfn + size; 3000 unsigned long pfn; 3001 struct zone *z; 3002 3003 if (highest_memmap_pfn < end_pfn - 1) 3004 highest_memmap_pfn = end_pfn - 1; 3005 3006 z = &NODE_DATA(nid)->node_zones[zone]; 3007 for (pfn = start_pfn; pfn < end_pfn; pfn++) { 3008 /* 3009 * There can be holes in boot-time mem_map[]s 3010 * handed to this function. They do not 3011 * exist on hotplugged memory. 3012 */ 3013 if (context == MEMMAP_EARLY) { 3014 if (!early_pfn_valid(pfn)) 3015 continue; 3016 if (!early_pfn_in_nid(pfn, nid)) 3017 continue; 3018 } 3019 page = pfn_to_page(pfn); 3020 set_page_links(page, zone, nid, pfn); 3021 mminit_verify_page_links(page, zone, nid, pfn); 3022 init_page_count(page); 3023 reset_page_mapcount(page); 3024 SetPageReserved(page); 3025 /* 3026 * Mark the block movable so that blocks are reserved for 3027 * movable at startup. This will force kernel allocations 3028 * to reserve their blocks rather than leaking throughout 3029 * the address space during boot when many long-lived 3030 * kernel allocations are made. Later some blocks near 3031 * the start are marked MIGRATE_RESERVE by 3032 * setup_zone_migrate_reserve() 3033 * 3034 * bitmap is created for zone's valid pfn range. but memmap 3035 * can be created for invalid pages (for alignment) 3036 * check here not to call set_pageblock_migratetype() against 3037 * pfn out of zone. 3038 */ 3039 if ((z->zone_start_pfn <= pfn) 3040 && (pfn < z->zone_start_pfn + z->spanned_pages) 3041 && !(pfn & (pageblock_nr_pages - 1))) 3042 set_pageblock_migratetype(page, MIGRATE_MOVABLE); 3043 3044 INIT_LIST_HEAD(&page->lru); 3045#ifdef WANT_PAGE_VIRTUAL 3046 /* The shift won't overflow because ZONE_NORMAL is below 4G. */ 3047 if (!is_highmem_idx(zone)) 3048 set_page_address(page, __va(pfn << PAGE_SHIFT)); 3049#endif 3050 } 3051} |
前面alloc_node_mem_map为我们的NODE(0)的所有页描述符page分配空闲区域,并由全局变量mem_map指向,所以3007行for (pfn = start_pfn; pfn < end_pfn; pfn++) 对每个空闲页框,都指向一系列初始化的操作,其中3019行通过宏pfn_to_page(pfn) 得到页框号对应的页描述符page的地址,并把它赋给内部变量page:
#define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
后面的代码就是对这个页描述符进行一系列的初始化。
到此为止,setup_arch()中比较重要的内容基本介绍完毕,主要涉及内存管理系统的一些底层初始化代码。经过这一番初始化后,内存初始化已经完成了特定系统的基本初始化,接下来,start_kernel开始构建内存管理的经典算法应用:伙伴系统和slab内存管理。对这两个概念不太熟悉的同学请查阅博客“伙伴系统算法”和“slab分配器”。