得到了总的页面数max_pfn和高端页面数highmem_pages之后,来到setup_arch的947行,调用init_memory_mapping()函数来建立系统初始化阶段的临时分页体系,传入的参数意义代表从0~max_low_pfn对应的32位物理地址(低12位全为0,也就是页面对齐),在函数init_memory_mapping函数中先后调用下面的几个函数来设置内存相关数据(因为bootmem此时没有初始化):
find_early_table_space()
kernel_physical_mapping_init()
early_ioremap_page_table_range_init()
load_cr3()
reserve_early()
其中,首先find_early_table_space 所实现的功能是相当重要的:
32static void __init find_early_table_space(unsigned long end, int use_pse, 33 int use_gbpages) 34{ 35 unsigned long puds, pmds, ptes, tables, start; 36 37 puds = (end + PUD_SIZE - 1) >> PUD_SHIFT; 38 tables = roundup(puds * sizeof(pud_t), PAGE_SIZE); 39 40 if (use_gbpages) { 41 unsigned long extra; 42 43 extra = end - ((end>>PUD_SHIFT) << PUD_SHIFT); 44 pmds = (extra + PMD_SIZE - 1) >> PMD_SHIFT; 45 } else 46 pmds = (end + PMD_SIZE - 1) >> PMD_SHIFT; 47 48 tables += roundup(pmds * sizeof(pmd_t), PAGE_SIZE); 49 50 if (use_pse) { 51 unsigned long extra; 52 53 extra = end - ((end>>PMD_SHIFT) << PMD_SHIFT); 54#ifdef CONFIG_X86_32 55 extra += PMD_SIZE; 56#endif 57 ptes = (extra + PAGE_SIZE - 1) >> PAGE_SHIFT; 58 } else 59 ptes = (end + PAGE_SIZE - 1) >> PAGE_SHIFT; 60 61 tables += roundup(ptes * sizeof(pte_t), PAGE_SIZE); 62 63#ifdef CONFIG_X86_32 64 /* for fixmap */ 65 tables += roundup(__end_of_fixed_addresses * sizeof(pte_t), PAGE_SIZE); 66#endif 67 68 /* 69 * RED-PEN putting page tables only on node 0 could 70 * cause a hotspot and fill up ZONE_DMA. The page tables 71 * need roughly 0.5KB per GB. 72 */ 73#ifdef CONFIG_X86_32 74 start = 0x7000; 75#else 76 start = 0x8000; 77#endif 78 e820_table_start = find_e820_area(start, max_pfn_mapped<<PAGE_SHIFT, 79 tables, PAGE_SIZE); 80 if (e820_table_start == -1UL) 81 panic("Cannot find space for the kernel page tables"); 82 83 e820_table_start >>= PAGE_SHIFT; 84 e820_table_end = e820_table_start; 85 e820_table_top = e820_table_start + (tables >> PAGE_SHIFT); 86 87 printk(KERN_DEBUG "kernel direct mapping tables up to %lx @ %lx-%lx/n", 88 end, e820_table_start << PAGE_SHIFT, e820_table_top << PAGE_SHIFT); 89} |
我们看到它确定了PUD和PMD以及PTE、固定内存映射等所有的选项所使用的内存空间的大小;之后调用find_e820_area函数从e820.map[]数组中寻找到一块能够容纳所有页表项的内存段:
743u64 __init find_e820_area(u64 start, u64 end, u64 size, u64 align) 744{ 745 int i; 746 747 for (i = 0; i < e820.nr_map; i++) { 748 struct e820entry *ei = &e820.map[i]; 749 u64 addr; 750 u64 ei_start, ei_last; 751 752 if (ei->type != E820_RAM) 753 continue; 754 755 ei_last = ei->addr + ei->size; 756 ei_start = ei->addr; 757 addr = find_early_area(ei_start, ei_last, start, end, 758 size, align); 759 760 if (addr != -1ULL) 761 return addr; 762 } 763 return -1ULL; 764} |
find_e820_area中检测e820.map的每一个元素,这个元素代表的内存区必须是E820_RAM,然后调用find_early_area获得tables的首地址:
539u64 __init find_early_area(u64 ei_start, u64 ei_last, u64 start, u64 end, 540 u64 size, u64 align) 541{ 542 u64 addr, last; 543 544 addr = round_up(ei_start, align); 545 if (addr < start) 546 addr = round_up(start, align); 547 if (addr >= ei_last) 548 goto out; 549 while (bad_addr(&addr, size, align) && addr+size <= ei_last) 550 ; 551 last = addr + size; 552 if (last > ei_last) 553 goto out; 554 if (last > end) 555 goto out; 556 557 return addr; 558 559out: 560 return -1ULL; 561} |
这个find_early_area函数我们要好好说道说道。早系统初始化时,什么内存管理器、内存模型这些东西都没有建立,那么获得内存的最低级函数就是这个find_early_area函数。它接收6个参数:ei_start和ei_last表示分配范围,我们看到这个范围不能超出某个e820.map[i]元素代表的范围;start和end代表期望分配的范围;size为期望分配大小;align表示对齐方式。我们看到,如果addr >= ei_last,或者last > ei_last,或者last > end都会分配失败。这个条件看上去很苛刻,不过e820.map[]数组有那么多元素,总有一个元素会满足的,不然所有的Linux都无法运行了。
round_up以及还有一个双胞胎弟弟round_down的定义如下:
#define __round_mask(x, y) ((__typeof__(x))((y)-1))
#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)
#define round_down(x, y) ((x) & ~__round_mask(x, y))
获得了获得页表tables的首地址之后,find_early_table_space就用它设置e820_table_start、e820_table_end和e820_table_top的值。这三个全局变量相当的重要,分别表示这个将要容纳所有内核页表的空间的起始、结束和顶部页对齐地址头20位(此时e820_table_start和e820_table_end是相等的),在后面的页表初始化阶段将会使用到这三个变量。
看了这么多代码,很多同学可能有些迷糊了。为啥我们在arch/x86/kernel/head_32.S中已经初始化了分页环境,建立了一个临时页表了,为啥这里还要建一个呢?其实,这就是ULK-3书中提到的内核临时页表和最终内核页表的概念。我在博客“高端内存映射”http://blog.csdn.net/yunsongice/archive/2010/01/26/5258589.aspx中也提到过这一点。
大家好好回忆一下,当时建立的临时页表个数只取决于_end的位置,也就是把解压缩后的内核代码映射出来了。而这里,是要把所有可用的RAM(注意这里是包括了已经映射了的内核代码、页目录)以页为单位分成多个页,每个页一个比特,提供一个初始阶段内存的分配和释放管理平台。
目前我的电脑,在arch/x86/kernel/head_32.S里只是映射了大概4M的vmlinux代码。内核尺寸在4M左右(不压缩),一般需要连续映射3个页面表。现在要把所有RAM映射到内核空间。那么内核要根据e820物理内存的布局,也就是RAM的结点布局对多个结点及结点的管理区作初化,最终把除去内核之外所有剩余的页交给页框分配器,同时也完成了页框分配器的初始化。