跳转至

ucore lab2 总结

概述

回顾实验 1,我觉得需要学习的知识有:

  1. gdb工具的使用
  2. ELF文件的格式,以及 readelf, objdump, objcopy 工具的使用(查看各个 section,查看符号表)
  3. x86 汇编基础(通用寄存器,段寄存器,EIP,EFLAG 等),GCC内联汇编格式(AT&T 格式)
  4. x86 内存分段机制(GDT 表,段描述符)
  5. x86中断处理机制(IDT 表,中断向量 -> 中断处理程序)
  6. 操作系统启动过程(BIOS->bootloader(第一扇区)->Kernel 各阶段做的事情)

而做完实验 2 后,我觉得需要学习的知识有:

  1. BIOS 探测物理内存,Kernel 打开分页时自带一个页表
  2. 二级页表进行虚拟地址转换
  3. 物理内存管理 之 空闲块(以页为单位) 管理(Page 数据结构和物理页的对应,空闲块链表 free_list,FirstHit 分配连续的物理页)
  4. 虚拟内存管理 之 有效虚拟页(表示一个程序可能使用的虚拟地址段,vma, mm 数据结构)
  5. 导致PageFault的原因(1. pte=0 即从来没有被加载过,2. 被 swap out, 3. 访问权限错误)
  6. FIFO 页替换算法实现(page struct 增加 pra_page_link, pra_vaddr,mm->sm_priv(pra_list_header) 记录最近使用的页序列;swap_manager)

以下介绍一些难点

流程

启动时的页表

由于 bootloader 加载内核时,将地址与上了一个 0xfff_ffff,即抹去了最高位。而内核的虚地址为 0xc010_0000 ~ 0xc012_4118。因此线性地址与物理地址的映射关系为:

线性地址 = 物理地址 + 0xc000_0000

内核自带一个页目录表 (4KB) 和一个页表 (4KB) 用于完成该映射(映射了 4M 的空间)

x86 打开分页机制,只需提前将页目录表的起始地址加载到 cr3 寄存器,然后置上 cr3 的某一位即可 之后,cpu 每次发出的地址都会被硬件转换为物理地址

自映射

主要作用是通过访问地址VPT+offset,便可以直接访问任意一个页表项(4G 虚拟地址空间最多有 1M 个页表项),而不用先访问一次页目录项,转换成虚拟地址后再访问页表项。

VPT 是一个 4M 对齐的地址(低 22 位为 0)

boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W
pte_t * const vpt = (pte_t *) VPT
pde_t * const vpd = (pde_t *) PGADDR((PDX(VPT), PDX(VPT), 0)

经过上面的步骤后考虑访问虚拟地址 vpt 和 vpd 会发生什么? 访问 vpt 时,经过页目录表的映射,映射到 boot_pgdir 自身,再经过一层映射(boot_pgdir 作为页表),访问了 boot_pgdir[0]对应的页表的第一个页表项。 依次访问 vpt 后的 4M 地址空间,便可以访问整个 1M 个页表项 pte 访问 vpd 时,vpd 则被映射到 boot_pgdir 自身所在页的起始地址,因此访问 vpd 后的 4KB 空间,可访问所有的页目录项。

-------------------- BEGIN -------------------- PDE(0e0) c0000000-f8000000 38000000 urw |-- PTE(38000) c0000000-f8000000 38000000 -rw PDE(001) fac00000-fb000000 00400000 -rw |-- PTE(000e0) faf00000-fafe0000 000e0000 urw |-- PTE(00001) fafeb000-fafec000 00001000 -rw --------------------- END ---------------------

print_pgdir 的作用是输出页目录表和页表的整体情况,按照被连续映射的虚拟地址块的形式输出。

print_pgdir 先通过 vpd 访问 boot_pgdir,找到一个被连续映射的虚拟地址块(以 4M 为单位),接着进一步通过 vpt 访问页表项找到更精确的连续虚拟地址块(以 4KB 为单位)。(上面已经说明可以通过 vpt 访问整个 1M 项 pte)

image-20200913225912132

PageFault 处理

pagefault发生 -> (vector14->__all_traps->trap)->trap_dispatch->pgfault_handler->do_pgfault

最关键的为 do_pgfault 函数,位于 vmm.c 中

主要过程如下:

  1. 调用 find_vma,检查引起异常的是否为有效的虚拟地址
  2. 检查权限(没太看懂作用)
  3. 调用 get_pte,如果 pte 全为 0,表示第一次访问,调用 pgdir_alloc_page 随机分配一个物理页
  4. 如果 pte 为一个 swap 项(之前被换出过),则调用swap_in函数(调用 alloc_page 分配一个物理页,再调用 swapfs_read 函数将页从磁盘换入到内存(该物理页))。最后更新页表项,让其指向该物理页。然后调用 swap_map_swappable 将该物理页添加到 FIFO 中(swap manager 维护的链表)

注:

  1. 其中的 pgdir_alloc_page 函数只在这里使用到了。作用是调用 alloc_page 分配一个物理页,并将其添加到 FIFO 中
  2. alloc_page 函数在空闲块不够时,便会调用swap_out函数。swap_out 过程如下:
    1. 调用 swap_out_victim 函数找到被替换的页 page(FIFO 结尾)
    2. 调用 swapfs_write 函数将该物理页从内存写到磁盘(扇区号对应 page->pra_vaddr)
    3. 通过 page->pra_vaddr 查找页表,并更新对应 pte 为 swap 项(这里如果遇到多个虚拟地址对应一个物理地址的情况怎么办呢?)

存在调用链 kern_init()->vmm_init()->check_pgfault()

其中 check_pgfault 函数在访问地址 0x100 时会触发 pte 全 0 的 pagefault,分配物理页后便不会再产生了。

(后面分析 check_swap 会产生触发 swap 的 page fault)

FIFO 页替换过程 (check_swap 过程)

存在调用链 kern_init()->swap_init()->check_swap()

下面分析 check_swap 的整个过程

  1. 设置环境,最终:1. 空闲块链表包含 4 个物理页。2. 设置 vma,0x1000~0x5000 都是有效的虚拟地址
  2. 调用 check_content_set(),依次写了 0x1000, 0x2000, ..., 0x4000 四个地址(分别用 a, b, c, d 表示),触发四次初始 page fault。此时已经没有空闲页了
  3. 调用 check_content_access,依次访问 c, a, d, b, e, b, a, b, c, d, e
    1. 访问 c, a, d, b
    2. 访问 e 时 pgfault,pte 全 0,调用 alloc_page 导致 swap out 出 a 对应的物理页,标记 a 的页表项为 swap 项
    3. 访问 b
    4. 访问 a 时 pgfault,pte 为 swap 项,调用 swap in,swap in 调用 alloc_page 又导致 swap out 出 b 对应的物理页,标记 b 的页表项为 swap 项,更新 a 的页表项为对应物理页。
    5. 访问 b 时 pgfault...
    6. ...

输出如下:

check_vma_struct() succeeded!
page fault at 0x00000100: K/W [no page found].
check_pgfault() succeeded!
check_vmm() succeeded.
ide 0:      10000(sectors), 'QEMU HARDDISK'.
ide 1:     262144(sectors), 'QEMU HARDDISK'.
SWAP: manager = fifo swap manager
BEGIN check_swap: count 1, total 31964
setup Page Table for vaddr 0X1000, so alloc a page
setup Page Table vaddr 0~4MB OVER!
set up init env for check_swap begin!
page fault at 0x00001000: K/W [no page found].
page fault at 0x00002000: K/W [no page found].
page fault at 0x00003000: K/W [no page found].
page fault at 0x00004000: K/W [no page found].
set up init env for check_swap over!
write Virt Page c in fifo_check_swap
write Virt Page a in fifo_check_swap
write Virt Page d in fifo_check_swap
write Virt Page b in fifo_check_swap
write Virt Page e in fifo_check_swap
page fault at 0x00005000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x1000 to disk swap entry 2
write Virt Page b in fifo_check_swap
write Virt Page a in fifo_check_swap
page fault at 0x00001000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x2000 to disk swap entry 3
swap_in: load disk swap entry 2 with swap_page in vadr 0x1000
write Virt Page b in fifo_check_swap
page fault at 0x00002000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x3000 to disk swap entry 4
swap_in: load disk swap entry 3 with swap_page in vadr 0x2000
write Virt Page c in fifo_check_swap
page fault at 0x00003000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x4000 to disk swap entry 5
swap_in: load disk swap entry 4 with swap_page in vadr 0x3000
write Virt Page d in fifo_check_swap
page fault at 0x00004000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x5000 to disk swap entry 6
swap_in: load disk swap entry 5 with swap_page in vadr 0x4000
write Virt Page e in fifo_check_swap
page fault at 0x00005000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x1000 to disk swap entry 2
swap_in: load disk swap entry 6 with swap_page in vadr 0x5000
write Virt Page a in fifo_check_swap
page fault at 0x00001000: K/R [no page found].
swap_out: i 0, store page in vaddr 0x2000 to disk swap entry 3
swap_in: load disk swap entry 2 with swap_page in vadr 0x1000
count is 0, total is 7
check_swap() succeeded!