操作系统 - lab5 Demand Paging¶
Abstract
一些踩过的坑,以及一个 kernel 执行流程总结
kernel 执行流程¶
kernel 执行流程
- opensbi 执行完毕
- _start: 完成 stvec, sie, mtimecmp, sstatus 和栈的设置,然后依次调用以下函数
- setup_vm: 填写页表
- relocate: 用设置好的页表修改 satp, 启动虚拟内存
- mm_init: 完成内存分配函数的初始化
- setup_vm_final: 切换到新的页表
- task_init: 初始化进程
- 对于 idle 以外的线程,在 task_struct 中添加 stack 和 segment-01(1)对应的两段 vma
- start_kernel: 不进入 test 等时钟中断,而是直接调用 schedule 调度走
- schedule: 根据 policy 选择下一个要调度的线程,调用 switch_to 至该线程
- switch_to: 获取先后线程的 PCB 地址,调用__switch_to
- __switch_to: 当前上下文存入 PCB,加载下一个进程的 PCB
- __dummy: 切用户栈,sret 返回用户段,而 sepc 是我们初始化的时候就指定的的 ENTRY
- ENTRY: PC 跳转至 ENTRY,但是此时该页的映射并未完成,PAGE FAULT
- _traps: PAGE FAULT,保存上下文进入 trap_handler
- trap_handler: 确认是 PAGE FAULT 后,调用 do_page_fault
- do_page_fault: 根据 current 的 vma 确认是段错误、文件页缺页还是匿名页缺页,分配新页,填写内容(2),填写页表,返回
- _traps: 恢复上下文返回,此后对应页已有映射
- ENTRY: 恢复执行,此后调度或者遇到新的缺页均同上处理
- 也即 readelf 时得到的 segment-01,包括.text .rodata .bss
- 清零或者拷贝文件内容
栈切换问题¶
文档中指出,由用户发起的中断,我们需要在_traps
开头切换到内核栈,但是对于内核发起的中断就不需要。而要判断中断是否由内核发起就要判断sscratch
是否为 0,在 lab4 中我把 t0 压栈后用 t0 来检验。
但是在 lab5 中这一方法行不通,因为当我们试图把 t0 压栈的时候我们的栈可能根本就是没分配的用户栈,那么这个压栈的行为只会引发新的 PAGE FAULT,如此永远循环。
为了寻找解决方法可以先试图去查看 linux 时如何解决这一问题的,这是对应的代码
可以看到 linux 使用了 tp(thread pointer)以及指令csrwr
,前者倒不是必要的使用,我们主要关注到csrwr
指令可以实现 csr 与其他寄存器的交换,从而我们 sp 与sscratch
的切换就不需要其他寄存器了,这就是正确的解决方式。
vmas¶
在 kernel 中,我们分配了一整页,其中的低地址放了个 task_struct,高地址就是栈顶(如下图,来自实验文档)。所以我们要稍微限制一下 vmas 的大小,不能把分配的一页全给用了,还要留点给栈用。