本文主要参考《深入分析linux内核》,配图都来自这本书,加入了一些自己的理解。
页目录项的位定义
页表项定义
本文只会对0,1,2位解释,0位present表示是否在内存中
1 /* 2 * .org new-lc,fill,这条汇编程序指令将本节的位置计数器提前至new-lc(New Location Counter)处,中间的字节被填充fill值,默认为0 3 * .fill repeat,size,value 如果size大于8也只会取8,以size为一组,重复repeat次,填充value值 4 * 只能在本节中往前,计数器只能增加,所以第一条将位置计数器变为0xc000000+0x100000+0x1000,因为编译后这些地址都是虚拟地址,所以 5 * 为内核地址需要加上__PAGE_OFFSET,而内核前1M有特殊用途,所以从1M之后开始初始化内存 6 * swapper_pg_dir为全局页目录起始地址,第0,1项是映射的pg0和pg1,共8M,第3-767项为空,第768,769项也为pg0和pg1,第770-1023项为空 7 * 所以0x1000手动完成了全局页目录的初始化,用户区和内核区的开始两项的映射相同 8 */ 9 .org 0x1000 10 ENTRY(swapper_pg_dir) 11 .long 0x00102007 12 .long 0x00103007 13 .fill BOOT_USER_PGD_PTRS - 2, 4, 0 14 /* default: 766 entries */ 15 .long 0x00102007 16 .long 0x00103007 17 /* default: 254 entries */ 18 .fill BOOT_KERNEL_PGD_PTRS - 2, 4, 0 19 20 /* 21 * The page tables are initialized to only 8MB here - the final page 22 * tables are set up later depending on memory size. 23 * 后文会介绍完成对这两个页表的初始化 24 */ 25 .org 0x2000 26 ENTRY(pg0) 27 28 .org 0x3000 29 ENTRY(pg1) 30 31 /* 32 * empty_zero_page must immediately follow the page tables ! (The 33 * initialization loop counts until empty_zero_page) 34 * 0页表是不允许访问的,全是0 35 */ 36 37 .org 0x4000 38 ENTRY(empty_zero_page) 39 40 .org 0x5000 41 42 /* 43 * Real beginning of normal "text" segment 44 * 这里开始存放内核的代码段 45 */ 46 ENTRY(stext) 47 ENTRY(_stext) 48 49 /* 50 * This starts the data section. Note that the above is all 51 * in the text section because it has alignment requirements 52 * that we cannot fulfill any other way. 53 * 内核data段 54 */ 55 .data 56 57 ALIGN 58 59 60 /* 61 * Initialize page tables 62 * 开始初始化pg0和pg1两组页表,pg0- __PAGE_OFFSET就是pg0的起始物理地址0x102000 63 */ 64 movl $pg0 - __PAGE_OFFSET, %edi /* initialize page tables */ 65 movl $007, %eax 66 /* "007" doesn't mean with right to kill, but PRESENT+RW+USER 67 */ 68 2: 69 /* 将eax中的值存入edi指定的位置中,因而第一次回在pg0第0项处存入0x007,对应的页就是起始物理地址为0的4KB物理内存,edi自动增加4,一项为4个byte*/ 70 stosl 71 /* eax+4K,代表下一页*/ 72 add $0x1000, %eax 73 /* 如果edi还未到0x4000,继续循环,即初始化了pg0和pg1两张页表 */ 74 cmp $empty_zero_page - __PAGE_OFFSET, %edi 75 jne 2b 76 77 /* 78 * Enable paging 79 */ 80 3: 81 movl $swapper_pg_dir - __PAGE_OFFSET, %eax 82 movl %eax, %cr3 /* set the page table pointer.. */ 83 movl %cr0, %eax 84 orl $0x80000000, %eax 85 /* 此处启用cr0的最高位PG允许位,表示开启分页机制,但因为eip中存储的仍为内存的物理地址,所以cpu对eip中的地址通过分页机制解析出来将是用户区 86 * 的,如果不映射到内存开始8M位置,那儿eip解析出来的物理地址就是无效的,后面的指令就无法执行了,而我们需要的是按当前顺序继续执行下去,完成从 87 * 实模式到保护模式的平稳过渡,内核初始化完成之后会删除用户区的这两个映射 88 * ..and set paging (PG) bit 89 */ 90 movl %eax, %cr0 91 /* flush the prefetch-queue,这是intel所建议的操作,逻辑上无作用,但可以将流水线上其他指令丢弃,避免乱序执行的影响 */ 92 jmp 1f 93 1: 94 /* 上面的jmp是短跳转,EIP中仍然是物理地址,所以需要手动去完成EIP刷新,下一个1:在编译生成的时候采用的是虚拟地址,所以跳转到这个虚拟地址就 95 * 真正的完成了EIP的虚拟地址更新,也就完成了实模式到保护模式的过渡 96 */ 97 movl $1f, %eax 98 jmp *%eax /* make sure eip is relocated */ 99 1 : 100 /* Set up the stack pointer */ 101 lss stack_start, %esp
此时内存映射关系
之后linux会根据开机加电后的BIOS探测到的物理内存信息进行内存的初始化,生成一张内存构成图,下一节将会是这些内容。