刚才的那张页表实在是太粗糙了,我想可能是有什么历史原因导致的吧。到了真正的内核,怎么也得换个漂亮点的页表。
这次的页表就在arch/x86/kernel/head_64.S里面咯~
从cr3开始
在x86平台上保存页表起始地址的就是这个cr3寄存器了,那就先看看这个寄存器变成了谁呗。
movq $(early_level4_pgt - __START_KERNEL_map), %rax
/* Setup early boot stage 4 level pagetables. */
addq phys_base(%rip), %rax
movq %rax, %cr3
嗯,这个东西稍微有点绕。phys_base是一个偏移,我们暂且认为就是0吧。所以这次加载到cr3中的地址是early_level4_pgt - __START_KERNEL_map。这里稍微解释一下下,如果实在理解不了,暂时先跳过。
加载到cr3的是一个物理地址,而我们编译出的内核vmlinux是一个ELF文件且最终会在虚拟地址空间运行,所以所有的符号都保存的是虚拟地址。上面这两个动作就是得到early_level4_pgt这个符号在运行时的物理地址。
不管怎么样知道加载到cr3的地址是指向early_level4_pgt的就好~
early_level4_pgt的容貌
刚才最简单的页表也有三层了,内核中的页表也不例外。
early_level4_pgt:
NEXT_PAGE(early_level4_pgt)
.fill 511,8,0
.quad level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE
level3_kernel_pgt
NEXT_PAGE(level3_kernel_pgt)
.fill L3_START_KERNEL,8,0
/* (2^48-(2*1024*1024*1024)-((2^39)*511))/(2^30) = 510 */
.quad level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE
.quad level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
level2_kernel_pgt
NEXT_PAGE(level2_kernel_pgt)
/*
* 512 MB kernel mapping. We spend a full page on this pagetable
* anyway.
*
* The kernel code+data+bss must not be bigger than that.
*
* (NOTE: at +512MB starts the module area, see MODULES_VADDR.
* If you want to increase this then increase MODULES_VADDR
* too.)
*/
PMDS(0, __PAGE_KERNEL_LARGE_EXEC,
KERNEL_IMAGE_SIZE/PMD_SIZE)
是不是又看得头晕了? 嗯,不着急,再来看一张图~
所以这其实是一张编译时就写好的页表。这样看,是不是简单了些?
映射关系
细心的朋友可能发现了,这张页表和之前的页表最后一个层级差不多,就是少了点。最大的差别是第一层上我们使用了最后一级的表项,而第一章表中使用的是第一级。
对了,我想你猜到了。这就是虚拟地址和物理地址的映射了。
那我们来算一下这次映射关系究竟是什么样子的。
(511) << 39 | (510) << 30
= FFFF 8000 0000
再来看一下变量__START_KERNEL_map的定义
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
x86上只使用了48bit虚拟地址空间,而不是64bit。所以这两个地址就是等价的。(或许还漏了点什么,以后理解了再来补吧~)
经过计算证实了这次映射的就是内核代码空间的页表。
启用虚拟地址
正如之前看到的,虽然使用了页表,但是页表中的物理地址和虚拟地址是一模一样的。经过了这次页表的改造,就有了真正的地址转换了。
代码很有意思
/* Ensure I am executing from virtual addresses */
movq $1f, %rax
jmp *%rax
1:
就是取到下一个lable的地址,然后跳过去~