Part 3_The Kernel
我们现在将开始更详细地研究JOS内核。(最后你会写一些代码!)。与引导加载程序一样,内核从一些汇编语言代码开始,这些代码设置可以使C语言代码正确执行。
使用虚拟内存来解决位置依赖问题
操作系统内核通常被链接到非常高的虚拟地址(例如0xf0100000)下运行,以便留下处理器虚拟地址空间的低地址部分供用户程序使用。 在下一个lab中,这种安排的原因将变得更加清晰。
许多机器在地址范围无法达到0xf0100000
,因此我们无法指望能够在那里存储内核。相反,我们将使用处理器的内存管理硬件将虚拟地址0xf0100000
(内核代码期望运行的链接地址)映射到物理地址0x00100000
(引导加载程序将内核加载到物理内存中)。
现在,我们只需映射前4MB的物理内存,这足以让我们启动并运行。 我们使用kern/entrypgdir.c
中手写的,静态初始化的页面目录和页表来完成此操作。 现在,你不必了解其工作原理的细节,只需注意其实现的效果。
实现虚拟地址,有一个很重要的寄存器CR0-PG
;
PG:CR0的位31是分页(Paging)标志。当设置该位时即开启了分页机制;当复位时则禁止分页机制,此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启PE标志。即若要启用分页机制,那么PE和PG标志都要置位。
Exercise 7
1.使用QEMU和GDB跟踪到JOS内核并停在
movl%eax,%cr0
。 检查内存为0x00100000和0xf0100000。 现在,使用stepi GDB命令单步执行该指令。 再次检查内存为0x00100000和0xf0100000。 确保你了解刚刚发生的事情。
在地址0x100020处,将第31位PG置为1,后写入cr0
中
0x100015: mov $0x118000,%eax
0x10001a: mov %eax,%cr3
0x100020: or $0x80010001,%eax
0x100025: mov %eax,%cr0
在0x00100000内存处没有变化
执行前:
(gdb) x/8x 0x100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x8000b812 0x220f0011 0xc0200fd8
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100010 <entry+4>: 0x00000000 0x00000000 0x00000000 0x00000000
执行后
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0xf0100010 <entry+4>: 0x34000004 0x8000b812 0x220f0011 0xc0200fd8
可以发现,两者内容完全一致,虚拟地址0xf0100000
已经被映射到0x00100000
处了,为什么会出现这种变化?
在修改cr0之前修改了cr3寄存器。将地址0x118000
写入了页目录寄存器,页目录表应该就是存放在地址0x118000
处。其他操作应该是由entry_pgdir
的// Map VA’s [KERNBASE, KERNBASE+4MB) to PA’s [0, 4MB),完成了映射。使得再读取0xf0100000
地址时,自动映射到了0~4M
的某个位置(暂时不清楚)。
CR3是页目录基址寄存器,保存页目录表的物理地址,页目录表总是放在以4K字节为单位的存储器边界上,因此,它的地址的低12位总为0,不起作用,即使写上内容,也不会被理会。
在entry.S
中说:
The kernel (this code) is linked at address ~(KERNBASE + 1 Meg),
在程序编译后,被链接到高地址处。在kernel.ld
链接脚本文件里指定了。
/* Link the kernel at this address: "." means the current address */
. = 0xF0100000;
但是bootloader 实际把kernel加载到了0x100000的位置
2.注释掉
kern/entry.S
中的movl %eax