MIT_xv6 学习笔记_Lab1.3

内核

操作系统的内核往往运行在高的虚拟地址空间, 使低的地址空间留给用户程序.上一节我们知道, 内核运行的入口物理地址是 0x0010000c , 这个地址是在 0~ 4MB 地址空间范围内的, 这个空间完全足够内核开始运行. 内核的虚拟地址是内核希望执行的地址, 但是内存并没有那么大的空间, 所以内核实际执行的地址是物理地址. 内核的虚拟地址是一个高地址, 是怎么映射到 0x000000000x00400000 这个低的物理地址空间的呢? 我们知道, boot loader 也是在实模式下运行的, 在 ./boot/boot.S 中启用了保护模式, 但是 boot loader 的物理地址与虚拟地址是相同的,

这里使用的是 kern/entrypgdir.c 中的静态映射表, 将虚拟地址映射到这个地址空间上的. 这个静态映射表只能映射一部分的内存空间, 也就是将 0xf00000000xf04000000x00000000 through 0x00400000 都映射到物理地址为 0x00000000 through 0x00400000 的地址空间中. 所以我们在 ./kern/entry.S 中要做的就是确定页目录的物理地址与, 将这个地址存入 CR3 寄存器, 这个寄存器的作用就是存储页目录的物理地址, 然后开启页表机制, 这样就可以使用 kern/entrypgdir.c 中的 entry_pgtable 将 4MB 的虚拟地址映射到物理地址上, 那么 kern/entrypgdir.c 又是如何实现的呢?

#include <inc/mmu.h>
#include <inc/memlayout.h>

pte_t entry_pgtable[NPTENTRIES];
// 页表, 表示的是从  0x00000000 到 0x00400000 这 4MB 物理内存对应的页表

__attribute__((__aligned__(PGSIZE)))
pde_t entry_pgdir[NPDENTRIES] = {
    // Map VA's [0, 4MB) to PA's [0, 4MB)
    [0] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P,
    // Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
    [KERNBASE>>PDXSHIFT] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W
    // KERNBASE>>PDXSHIFT是得到页目录号, 该页目录号对应的页表也是 entry_ptable这个页表
    // 所以, 页目录号为 0, 与页目录号为 3C00 对应的页表都是 entry_pgdir, 最后是加上写使能, 与页表中的存在标志
};

// Entry 0 of the page table maps to physical page 0, entry 1 tophysical page 1, etc.
// 页表的项到物理页的地址, 静态声明的页表
__attribute__((__aligned__(PGSIZE)))
pte_t entry_pgtable[NPTENTRIES] = {
    0x000000 | PTE_P | PTE_W,
}

我们在回过头来看 ./kern/entry.S 的内容就十分明显了:

.globl      _start
_start = RELOC(entry)
# 将 entry 的虚拟地址变成物理地址, entry 本身也是个虚拟地址,

# Load the physical address of entry_pgdir into cr3.  entry_pgdir is defined in entrypgdir.c.
movl    $(RELOC(entry_pgdir)), %eax
# 需要注意的是这里, entry_pgdir 只有虚拟地址, 并且他不在 boot loader里面, 而是在内核里面, 对应的虚拟地址是在 0xf0000000 之后
movl    %eax, %cr3
# 将 页目录的物理地址存入 cr3寄存器

# Turn on paging.
movl    %cr0, %eax
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
movl    %eax, %cr0

# Now paging is enabled, but we're still running at a low EIP
# (why is this okay?).  Jump up above KERNBASE before entering
# C code.

Exercise 7. Use QEMU and GDB to trace into the JOS kernel and stop at the movl %eax, %cr0. Examine memory at 0x00100000 and at 0xf0100000. Now, single step over that instruction using the stepi GDB command. Again, examine memory at 0x00100000 and at 0xf0100000. Make sure you understand what just happened.

What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren't in place? Comment out the movl %eax, %cr0 in kern/entry.S, trace into it, and see if you were right.

根据上面的代码, 这个问题就很显然了, 在 movl %eax, %cr0 之前, 未启用页表机制, 0xf0100000 的数据要么内存没有那么大, 要么未初始化, 启用页表机制后, 我们在查询内存时候的 memory references 就是虚拟地址通过页表机制将其转化为的物理地址, 此时 0xf0100000 和 0x00100000 地址处的数据变成相同的了.

控制台格式化输出

上一篇:XV6实验环境的搭建


下一篇:开发者必读 · 周报 | 002期