下面,我们来看看这是如何一步一步实现的。观察一下链接脚本,即tools/kernel.ld文件在lab1和lab2中的区别。在lab1中:

    1. SECTIONS {
    2. /* Load the kernel at this address: "." means the current address */
    3. .text : {
    4. *(.text .stub .text.* .gnu.linkonce.t.*)
    5. }

    这意味着在lab1中通过ld工具形成的ucore的起始虚拟地址从0x100000开始,注意:这个地址是虚拟地址。但由于lab1中建立的段地址映射关系为对等关系,所以ucore的物理地址也是0x100000,而ucore的入口函数kern_init的起始地址。所以在lab1中虚拟地址,线性地址以及物理地址之间的映射关系如下:

      在lab2中:

      第一个阶段是bootloader阶段,即从bootloader的start函数(在boot/bootasm.S中)到执行ucore kernel的kern_\entry函数之前,其虚拟地址,线性地址以及物理地址之间的映射关系与lab1的一样,即:

      第二个阶段从从kern_\entry函数开始,到执行enable_page函数(在kern/mm/pmm.c中)之前再次更新了段映射,还没有启动页映射机制。由于gcc编译出的虚拟起始地址从0xC0100000开始,ucore被bootloader放置在从物理地址0x100000处开始的物理内存中。所以当kern_entry函数完成新的段映射关系后,且ucore在没有建立好页映射机制前,CPU按照ucore中的虚拟地址执行,能够被分段机制映射到正确的物理地址上,确保ucore运行正确。这时的虚拟地址,线性地址以及物理地址之间的映射关系为:

      1. lab2 stage 2 virt addr - 0xC0000000 = linear addr = phy addr

      注意此时CPU在寻址时还是只采用了分段机制。最后后并使能分页映射机制(请查看lab2/kern/mm/pmm.c中的enable_paging函数),一旦执行完enable_paging函数中的加载cr0指令(即让CPU使能分页机制),则接下来的访问是基于段页式的映射关系了。

      请注意pmm_init函数中的一条语句:

      1. boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)];

      就是用来建立物理地址在0~4MB之内的三个地址间的临时映射关系virt addr - 0xC0000000 = linear addr = phy addr

      第四个阶段从gdt_init函数开始,第三次更新了段映射,形成了新的段页式映射机制,并且取消了临时映射关系,即执行语句“boot_pgdir[0] =
      0;”把boot_pgdir[0]的第一个页目录表项(0~4MB)清零来取消临时的页映射关系。这时形成了我们期望的虚拟地址,线性地址以及物理地址之间的映射关系: