Linux内核初始化高端内存的过程
内核在start_kernel()函数中调用了mem_init()来做所有与内存初始化相关的工作。与初始化高端内存相关的工作在函数set_highmem_pages_init()中完成。下面我们来详细分析一下这个过程。
109 void __init set_highmem_pages_init(void)
110 {
111 struct zone *zone;
112 int nid;
113
114 for_each_zone(zone) {
115 unsigned long zone_start_pfn, zone_end_pfn;
116
117 if (!is_highmem(zone))
118 continue;
119
120 zone_start_pfn = zone->zone_start_pfn;
121 zone_end_pfn = zone_start_pfn + zone->spanned_pages;
122
123 nid = zone_to_nid(zone);
124 printk(KERN_INFO "Initializing %s for node %d(%08lx:%08lx)\n",
125 zone->name, nid, zone_start_pfn, zone_end_pfn);
126
127 add_highpages_with_active_regions(nid, zone_start_pfn,
128 zone_end_pfn);
129 }
130 totalram_pages += totalhigh_pages;
131 }
这个函数首先通过for_each_zone(zone)遍历所有的zone,以找到高端内存管理器。117行的is_highmem(zone)判定zone是否是高端内存。如果是则继续执行120行之后的代码,否则继续判断下一个zone。变量zone_start_pfn与zone_end_pfn表示高端管理区的首位pfn。123行的zone_to_nid(zone)返回zone所在的节点号,即nid。124行输出高端内存相关的信息。在130行,将初始化后的高端内存页面计入到全局变量totalram_pages中。totalram_pages表示系统中所有的物理页面数。函数add_highpages_with_active_regions()将完成进一步的工作。
455 void __init add_highpages_with_active_regions(int nid, unsigned long start_pfn,
456 unsigned long end_pfn)
457 {
458 struct add_highpages_data data;
459
460 data.start_pfn = start_pfn;
461 data.end_pfn = end_pfn;
462
463 work_with_active_regions(nid, add_highpages_work_fn, &data);
464 }
这个函数将高端管理区的始末pfn封装在结构add_highpages_data中,然后调用函数work_with_active_regions完成进一步的工作。这里提出了一个active range的概念,可以将其译为活动内存区。active range是为了描述地址空间中的可用区域,排除了地址空间中的空洞。active range存储在数组early_node_map[MAX_ACTIVE_REGIONS]中。该数组中的每个元素保存了该active range的始末pfn,以及所属节点的节点号。函数work_with_active_regions(nid, add_highpages_work_fn, &data)节点nid中的每个active range均调用一次函数add_highpages_work_fn(),目的是排除高端管理区中的空洞(hole),即那些不可使用的页面。work_with_active_regions的实现如下:
3427 void __init work_with_active_regions(int nid, work_fn_t work_fn, void *data)
3428 {
3429 int i;
3430 int ret;
3431
3432 for_each_active_range_index_in_nid(i, nid) {
3433 ret = work_fn(early_node_map[i].start_pfn,
3434 early_node_map[i].end_pfn, data);
3435 if (ret)
3436 break;
3437 }
3438 }
宏for_each_active_range_index_in_nid即用来遍历节点nid中的所有active range。early_node_map[i].start_pfn与early_node_map[i].end_pfn表示了当前active range的始末pfn。
函数add_highpages_work_fn()则对高端内存落在当前active range中的页面进行初始化,其实现如下:
428 static int __init add_highpages_work_fn(unsigned long start_pfn,
429 unsigned long end_pfn, void *datax)
430 {
431 int node_pfn;
432 struct page *page;
433 unsigned long final_start_pfn, final_end_pfn;
434 struct add_highpages_data *data;
435
436 data = (struct add_highpages_data *)datax;
437
438 final_start_pfn = max(start_pfn, data->start_pfn);
439 final_end_pfn = min(end_pfn, data->end_pfn);
440 if (final_start_pfn >= final_end_pfn)
441 return 0;
442
443 for (node_pfn = final_start_pfn; node_pfn < final_end_pfn;
444 node_pfn++) {
445 if (!pfn_valid(node_pfn))
446 continue;
447 page = pfn_to_page(node_pfn);
448 add_one_highpage_init(page, node_pfn);
449 }
450
451 return 0;
452
453 }
438与439行求出了当前active range与高端管理区(由data表示)的交集。443行开始的for循环,遍历了这个交集中的每个页面。在验证该页面的有效性之后,调用add_one_highpage_init()函数将该页面释放到伙伴系统中。add_one_highpage_init()函数的实现很简单:
415 static void __init add_one_highpage_init(struct page *page, int pfn)
416 {
417 ClearPageReserved(page);
418 init_page_count(page);
419 __free_page(page);
420 totalhigh_pages++;
421 }
ClearPageReserved清除了该页面flag中的reserved标志,表示该页面属于动态内存。init_page_count(page)将该页面的引用计数初始化为1。__free_page(page)将该页面真正释放到伙伴系统中。该函数的第二个参数node_pfn实际上并没有被用到,这算是该版本内核(2.6.32.2)的一个Bug吧。最新的内核版本中已经没有这个参数了。