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吧。最新的核心版本中已經沒有這個參數了。