着手建立内核永久页表
来源:互联网 发布:mac摔了下屏幕不亮 编辑:程序博客网 时间:2024/05/21 11:22
5.2.3 着手建立内核永久页表
得到了总的页面数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的结点布局对多个结点及结点的管理区作初化,最终把除去内核之外所有剩余的页交给页框分配器,同时也完成了页框分配器的初始化。
- 着手建立内核永久页表
- 内核临时页表建立
- 临时内核页表的建立过程
- 临时内核页表的建立过程
- 学习日志:内核临时页表的建立
- 建立内核
- Opencv2.4.9+win7+VS2012一次性配置的方法--通过建立属性表永久配置
- 火星人永久家园建立在何处?
- linux 高端内存页框管理:永久内核映射、临时内核映射以及非连续内存分配
- linux高端内存页框管理:永久内核映射、临时内核映射以及非连续内存分配
- Linux0.11内核--启动代码分析setup.s 建立页目录和页表
- linux进程创建与守护;exec 进程内核页表建立
- linux进程创建与守护;exec 进程内核页表建立
- ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立)
- arm-linux内存管理学习笔记(2)-内核临时页表的建立
- 建立linux内核阅读器
- 内核树及其建立
- Ubuntu 内核树建立
- Java语言中容易被人忽视的细节
- 拷贝可用内存区信息
- 获得总页面数
- HTML rel 属性详解
- binary search
- 着手建立内核永久页表
- Mime 类型列表
- 第二次启动分页管理
- Linux系统限制
- 乱七八糟
- ARM7异常
- 如何使用GDAL重采样图像
- Tomcat部署Web应用方法总结
- 建立内存管理架构