跳转到虚拟内存

现在几乎到了整个 stext 过程的末尾,开始执行内核了。

首先调用“procinit”函数,这个函数对于每个CPU类型都不一样。这是一段由C和汇编写成的底层CPU管理代码,位于arch/arm/mm/proc-*.S 中。例如,大多数 v7 CPU的初始化代码都在 proc-v7.S 中,而ARM920的初始化代码在 proc-arm920.S中。稍后这些代码会有用,但通常“procinit”的调用都是空的,只有XScale的函数才有实际操作,用于处理引导程序初始状态中的bug。

procinit函数通过传统的 ret lr 返回,意味着连接寄存器(lr)中的值会赋给程序计数器(pc)。

在进入procinit函数之前, 我们将 lr 设置为标签 1: 的物理地址,从而会相对分支到符号 _enablemmu。我们还给 r13 赋值为 _mmapswitched 的地址,该符号的地址是编译时确定的,是在MMU启用之后的下一个执行指针处的非相对虚拟地址。我们已经接近相对代码构建的末尾了。

接下来跳转到 _enablemmu。r4 包含初始页表的地址。我们利用一条特殊的 CP15 指令将物理内存中的页表指针加载到 MMU 中:

mcr    p15, 0, r4, c2, c0, 0

到目前为止还没发生任何事情。页表地址设置到了MMU中,但还没有在物理地址和虚拟地址之间进行转译。接下来跳转到 turn_mmu_on。这里会发生许多神奇的事情。turn_mmu_on 被编译到 .idmap.text 节中,意味着它的物理地址和虚拟地址是一样的。接下来启用MMU:

 mcr    p15, 0, r0, c1, c0, 0        @ write control reg


mrc    p15, 0, r3, c0, c0, 0        @ read id reg

现在MMU启用了。下一条指令(正好是清除指令缓存的指令)将从虚拟内存中执行。最初不会注意到任何东西,但实际上是在虚拟内存中执行的。在跳转到 r13中的地址以执行返回时,我们会进入该函数的虚拟内存地址中的 _mmapswitched,它位于 PAGE_OFFSET(通常为 0xC0nnnnnn)下方的某处。现在可以进行绝对寻址了,内核会按照预期的方式执行。

08_内核 - 图1
图:从物理内存执行切换到虚拟内存执行时,程序计数器上发生的跳转。

现在已经成功地启动了初始页表,终于可以在C编译器认为内核应该在的位置上执行内核了。