3.2: 内核地址空间(Kernel address space)

xv6 为每个进程维护独立的用户空间页表,并为内核维护一个单独的页表,内核通过直接映射的方式访问物理内存和设备寄存器。对于内核栈,xv6 使用高内存映射并在栈的下方设置保护页,防止栈溢出覆盖内核内存。此外,内核通过设置适当的页表权限来控制对内存的访问。

内核地址空间映射规则

  • 每个进程的页表: xv6 为每个进程维护一个页表,该页表描述了进程的用户地址空间。同时,xv6 还维护了一个页表来描述内核的地址空间。内核通过配置其地址空间的布局来确保能够访问物理内存和各种硬件资源,并且这些资源有固定的虚拟地址。

  • 内核地址空间的布局: xv6 内核的地址空间布局如图所示 xv6 内核的地址空间

展示了内核虚拟地址是如何映射到物理地址的。文件 kernel/memlayout.h 声明了 xv6 内核内存布局的常量。

  • QEMU 模拟环境: QEMU 模拟了一个计算机系统,其中物理内存(RAM)从物理地址 0x80000000 开始,直到至少 0x88000000(xv6 称其为 PHYSTOP)。QEMU 模拟环境还包括 I/O 设备,如磁盘接口。QEMU 通过内存映射的控制寄存器向软件暴露设备接口,这些控制寄存器位于物理地址空间的 0x80000000 以下。内核可以通过读取或写入这些特殊的物理地址与设备硬件进行交互,而不是与 RAM 进行交互。第 4 章将详细解释 xv6 如何与设备进行交互。

  • 内核的直接映射: 内核通过“直接映射”来访问 RAM 和内存映射的设备寄存器,即将这些资源映射到与物理地址相等的虚拟地址。例如,内核本身在虚拟地址空间和物理内存中都位于 KERNBASE=0x80000000 位置。直接映射简化了内核代码,因为内核可以直接使用这些虚拟地址来读写物理内存。例如,当 fork 为子进程分配用户内存时,分配器返回该内存的物理地址;然后 fork 在复制父进程的用户内存到子进程时,直接将物理地址作为虚拟地址来使用。

  • 内核虚拟地址的特殊映射: 内核有一些虚拟地址并非直接映射:

    • 跳板页(trampoline page): 该页被映射到虚拟地址空间的顶部,用户页表也有相同的映射。跳板页的作用在第 4 章将被详细讨论,但这里值得注意的是,跳板页的物理页面在内核的虚拟地址空间中被映射了两次:一次是虚拟地址空间顶部的映射,另一次是直接映射。

    • 内核栈页(kernel stack pages): 每个进程都有自己的内核栈,这些栈被映射到较高的内存位置,这样在其下方可以放置一个未映射的保护页(guard page)。保护页的 PTE 是无效的(即 PTE_V 未设置),因此如果内核栈溢出,通常会触发一个异常并导致内核崩溃。没有保护页的话,栈溢出可能会覆盖其他内核内存,导致不正确的操作。与其发生错误,更倾向于让内核崩溃来避免错误的进一步扩展。

    • 内核栈的映射: 内核通过高内存映射访问栈,同时这些栈也可以通过直接映射的地址访问。如果只有直接映射存在,使用这些栈时就会依赖于直接映射的地址。在这种设计下,提供保护页的方式是将本应指向物理内存的虚拟地址取消映射,这将导致内存的使用变得复杂。

  • 内核页表的权限设置:

    • 内核将跳板页和内核代码页映射为具有 PTE_R(可读)和 PTE_X(可执行)权限,以便读取和执行这些页面中的指令。

    • 其他内核页面则映射为具有 PTE_R(可读)和 PTE_W(可写)权限,以便读取和写入这些页面中的内存。

    • 保护页的映射是无效的,即 PTE 没有设置有效位 PTE_V。

问题

  • 0x80000000地址的虚拟地址的(4个字节,32位),0100 0000 0000 0000 0000 0000 0000 0000