【OS】BUAA-OS-Lab4-实验报告

OS Lab4实验报告

思考题

Thinking4.1

思考并回答下面的问题: •内核在保存现场的时候是如何避免破坏通用寄存器的? •系统陷入内核调用后可以直接从当时的$a0-$a3参数寄存器中得到用户调用msyscall 留下的信息吗? •我们是怎么做到让sys开头的函数“认为”我们提供了和用户调用msyscall时同样 的参数的? •内核处理系统调用的过程对Trapframe做了哪些更改?这种修改对应的用户态的变化是什么?
答:当发生系统调用或中断时,CPU会从用户态切换到内核态。为了确保用户态的执行现场能够被完整保存,内核在处理系统调用前将通用寄存器的值保存到内核栈中。
不能直接使用$a0-$a3,因为在陷入内核时,这些寄存器的值可能已经被破坏或修改。而是应该到Trapframe来获取存储的上下文。
内核会从保存的Trapframe中读取用户态的$a0-$a3寄存器值,并将它们作为参数传递给sys_函数,并且sys_函数的参数列表和用户态调用msyscall时的参数顺序完全一致。
内核修改Trapframe的$v0(返回值)、$a3(错误标志)和epc(下一条指令),用户态恢复后会看到这些变化。

Thinking 4.2

思考 envid2env函数: 为什么 envid2env中需要判断 e->env_id != envid 的情况?如果没有这步判断会发生什么情况?
答:这是为了防止进程ID被复用后,旧envid错误地访问到新进程的Env结构。如果不进行判断,那么可能会出现利用旧进程访问新进程信息的恶意攻击。

Thinking 4.3

思考下面的问题,并对这个问题谈谈你的理解:请回顾 kern/env.c 文件 中 mkenvid() 函数的实现,该函数不会返回 0,请结合系统调用和 IPC 部分的实现与 envid2env() 函数的行为进行解释。
答:envid=0默认表示的为当前进程,mkenvid()不返回0是为了保留envid=0的特殊语义(表示当前进程),避免二义性和安全问题。在系统调用和IPC中,这一默认值可以便于快速对当前进程进行操作,在envid2env()中也不需要对当前进程进行不必要的权限检查。如果可以返回0,那么envid2env()必须检查是当前进程还是进程0,很不方便。

Thinking 4.4

关于 fork 函数的两个返回值,下面说法正确的是: A、fork 在父进程中被调用两次,产生两个返回值 B、fork 在两个进程中分别被调用一次,产生两个不同的返回值 C、fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值 D、fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值
答:C

Thinking4.5

我们并不应该对所有的用户空间页都使用duppage进行映射。那么究竟哪 些用户空间页应该映射,哪些不应该呢?请结合kern/env.c中env_init函数进行的页 面映射、include/mmu.h里的内存布局图以及本章的后续描述进行思考。
答:内核管理的区域如:UPAGESUVPTUTOP上的内核区,和无效页。

Thinking4.6

在遍历地址空间存取页表项时你需要使用到vpd和vpt这两个指针,请参 考user/include/lib.h中的相关定义,思考并回答这几个问题: •vpt和vpd的作用是什么?怎样使用它们? •从实现的角度谈一下为什么进程能够通过这种方式来存取自身的页表? •它们是如何体现自映射设计的? •进程能够通过这种方式来修改自己的页表项吗?
答:
vpt指向进程的页表项数组,通过它可以直接以虚拟地址形式访问所有页表项。

1
pte_t pte = vpt[VPN(va)];  // 获取va对应的PTE

vpd指向进程的页目录项数组,通过它可以直接访问页目录项。

1
pde_t pde = vpd[PDX(va)];  // 获取va对应的PDE

由于自映射的存在,将页表映射到固定虚拟地址 UVPT,vpt和vpd都可以据此提供给用户访问页表的只读接口,由于只有在内核态下有写权限,所以不能通过这种方式修改页表项。

Thinking 4.7

在 do_tlb_mod 函数中,你可能注意到了一个向异常处理栈复制 Trapframe 运行现场的过程,请思考并回答这几个问题: • 这里实现了一个支持类似于“异常重入”的机制,而在什么时候会出现这种“异常重 入”? • 内核为什么需要将异常的现场 Trapframe复制到用户空间?
答:处理一个异常(如 TLB 修改异常)的过程中,又触发了新的相同或不同的异常。如TLB缺失嵌套、页错误嵌套、系统调用嵌套时都会出现异常重入。
内核将异常现场复制到用户空间可以避免在异常嵌套下内核栈的溢出,并且支持用户级的异常处理,并且可以避免异常嵌套导致的死锁。

Thinking 4.8

在用户态处理页写入异常,相比于在内核态处理有什么优势?
答:可以减少内核开销,支持自定义异常处理策略,并且具有更好的隔离性和安全性。

Thinking 4.9

请思考并回答以下几个问题: • 为什么需要将 syscall_set_tlb_mod_entry的调用放置在 syscall_exofork之前? • 如果放置在写时复制保护机制完成之后会有怎样的效果?
答:这是为了子进程正确继承父进程的异常处理入口,如果syscall_set_tlb_mod_entry放在syscall_exofork之后,可能子进程遇到了异常但是还不知道还应该到哪里处理异常。
如果 syscall_set_tlb_mod_entry 放置在 COW 保护机制完成之后,子进程可能提前触发 COW,但无法正确处理父进程的 COW 处理也可能受影响。

难点分析

进程的fork是个比较难的点,还有操作是在用户态还是内核态下的判断也是个要点。

实验体会

这一次比上一次的实验难了许多,主要还是前面的知识没有掌握扎实,要抓紧巩固。

原创性分析

本文为本人原创


【OS】BUAA-OS-Lab4-实验报告
http://example.com/2025/05/09/【OS】BUAA-OS-Lab4-实验报告/
作者
mRNA
发布于
2025年5月9日
许可协议