uCore Lab Documents

系统执行中地址映射的四个阶段

原理课上讲到了页映射,段映射,以及段页式映射关系,但对如何建立段页式映射关系没有详说。其实,在lab1和lab2中都会涉及如何建立映射关系的操作。在lab1中,我们已经碰到到了简单的段映射,即对等映射关系,保证了物理地址和虚拟地址相等,也就是通过建立全局段描述符表,让每个段的基址为0,从而确定了对等映射关系。在lab2中,由于在段地址映射的基础上进一步引入了页地址映射,形成了组合式的段页式地址映射。这种方式虽然更加灵活了,但实现稍微复杂了一些。在lab2中,为了建立正确的地址映射关系,ld在链接阶段生成了ucore OS执行代码的虚拟地址,而bootloader与ucore OS协同工作,通过在运行时对地址映射的一系列“腾挪转移”,从计算机加电,启动段式管理机制,启动段页式管理机制,在段页式管理机制下运行这整个过程中,虚地址到物理地址的映射产生了多次变化,实现了最终的段页式映射关系:

 virt addr = linear addr = phy addr + 0xC0000000

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

ENTRY(kern_init)

SECTIONS {
            /* Load the kernel at this address: "." means the current address */
            . = 0x100000;

            .text : {
                       *(.text .stub .text.* .gnu.linkonce.t.*)
            }

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

 lab1: virt addr = linear addr = phy addr

在lab2中:

ENTRY(kern_entry)

SECTIONS {
            /* Load the kernel at this address: "." means the current address */
            . = 0xC0100000;

            .text : {
                        *(.text .stub .text.* .gnu.linkonce.t.*)
            }

这意味着lab2中通过ld工具形成的ucore的起始虚拟地址从0xC0100000开始,注意:这个地址也是虚拟地址。入口函数为kern_entry函数(在kern/init/entry.S中)。这与lab1有很大差别。但其实在lab1和lab2中,bootloader把ucore都放在了起始物理地址为0x100000的物理内存空间。这实际上说明了ucore在lab1和lab2中采用的地址映射不同。lab2在不同阶段有不同的虚拟地址,线性地址以及物理地址之间的映射关系。

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

 lab2 stage 1: virt addr = linear addr = phy addr

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

 lab2 stage 2: virt addr - 0xC0000000 = linear addr = phy addr

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

第三个阶段从enable_page函数开始,到执行gdt_init函数(在kern/mm/pmm.c中)之前,启动了页映射机制,但没有第三次更新段映射。这时的虚拟地址,线性地址以及物理地址之间的映射关系比较微妙:

 lab2 stage 3:  virt addr - 0xC0000000 = linear addr  = phy addr + 0xC0000000 # 物理地址在0~4MB之外的三者映射关系
                virt addr - 0xC0000000 = linear addr  = phy addr # 物理地址在0~4MB之内的三者映射关系

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

 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)清零来取消临时的页映射关系。这时形成了我们期望的虚拟地址,线性地址以及物理地址之间的映射关系:

 lab2 stage 4: virt addr = linear addr = phy addr + 0xC0000000