BUAA操作系统lab4

Cdostan MVP++

Lab4实验报告

一、思考题

Thinking 4.1

内核在保存现场的时候是如何避免破坏通用寄存器的
内存在保护现场的时候,通过将自身的局部变量、返回地址、调用函数的参数等压入栈中这样来保存此时通用寄存器中的值,放置在后续操作中这些值丢失,在返回时,再将这些值弹出栈并赋给相应的寄存器。
系统陷入内核调用后可以直接从当时的$a0-$a3参数寄存器中得到用户调用msyscall留下的信息吗
不可以,因为这四个寄存器在系统陷入内核态后可能会进行其他的操作而导致里面的值被破坏,直接访问是不安全的,应该从栈帧中来读取相关参数。
我们是怎么做到让sys开头的函数“认为”我们提供了和用户调用msyscall时同样的参数的
在用户态调用msyscall时,前四个参数会被存入$a0-$a3寄存器,同时在栈帧底部保留16字节的空间,后两个参数被存入在预留空间之上的8字节空间内,内核态中显式地从保存的用户上下文中获取这些参数值,这样就可以让sys开头的函数认为我们提供了和用户调用msyscall时同样的参数。
内核处理系统调用的过程对Trapframe做了哪些更改?这种修改对应的用户态的变化是什么
将EPC值加4以保证系统调用完成后返回到正确地址,同时将返回值存入2号寄存器。用户态会正确执行下一条指令。

Thinking 4.2

为什么 envid2env中需要判断 e->env_id != envid的情况?如果没有这步判断会发生什么情况
不同envid的后十位可能相同(二进制),所以如果不加上该判断,可能会取到错误的进程。

Thinking 4.3

请回顾 kern/env.c 文件中 mkenvid()函数的实现,该函数不会返回 0,请结合系统调用和 IPC 部分的实现与envid2env()函数的行为进行解释
首先,若mkenvid()函数会返回0,那么就会有进程的id为0,但在envid2env()的实现中,如果envid为0,则查询到的进程将会是目前的进程,这和我们的预期不符,同时也让进程间的通信发生错误,因此mkenvid()函数不会返回0。同时由于curenv是内核态的变量,用户态不能获取其id,这样实现方便用户进程将当前进程的envid传给内核。

Thinking 4.4

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

正确的是C

Thinking 4.5

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

从0到USTACKTOP的用户空间页应该映射,USTACKTOP到UTOP之间的内容是拿来处理页写入异常的,因此不需要与子进程共享,UTOP之上的内容是所有进程共享的,也不需要做父子进程之间单独的共享。

Thinking 4.6

vpt和vpd的作用是什么?怎样使用它们
vpt和vpd相当于两个数组,一个是页表项的数组,一个是页目录项的数组,只需通过页表项和页目录项的下标就可通过这两个宏来访问相应的页表项或页目录项。
从实现的角度谈一下为什么进程能够通过这种方式来存取自身的页表
页表和页目录是存放在用户空间中的,且逻辑上是连续的,因此进程可以通过这种方式来存取自身页表。
它们是如何体现自映射设计的

如图,可以发现页目录是处于页表中的,同时在页表中的位置相当于页表在整个内存中的位置,这体现了自映射设计。
进程能够通过这种方式来修改自己的页表项吗
不能,虽然该区域处于用户空间,但是对用户态只读不写,要修改必须陷入内核态。

Thinking 4.7

在 do_tlb_mod 函数中,你可能注意到了一个向异常处理栈复制 Trapframe
运行现场的过程,请思考并回答这几个问题:
这里实现了一个支持类似于“异常重入”的机制,而在什么时候会出现这种“异常重入”

出现写时复制异常时。
内核为什么需要将异常的现场 Trapframe复制到用户空间?
因为最终需要恢复到用户态后跳转到用户异常处理函数,由用户程序完成写时复制等自定义处理,这时用户异常处理函数需要相应的现场。

Thinking 4.8

在用户态处理页写入异常,相比于在内核态处理有什么优势

  • 缓解内核压力
  • 使得其更安全,防止更改不必要的页面
  • 使其更加灵活,能够完成很多自定义的处理
  • 降低异常处理错误而导致的影响

Thinking 4.9

为什么需要将 syscall_set_tlb_mod_entry的调用放置在 syscall_exofork之前
因为子进程需要修改env指针的值,这涉及到对写时复制页面的写入,因此会触发写时复制异常,而该异常需要调用syscall_set_tlb_mod_entry才能正确触发。
如果放置在写时复制保护机制完成之后会有怎样的效果
这样将会不能正确处理异常。

二、难点分析

本次实验的代码量进一步增加,且内容也很多,从异常处理到fork的实现,中间有很多需要停下来仔细思考理解的点,我认为主要有以下:

  • 对系统调用流程的理解
  • 对何时在内核态何时在用户态的辨析
  • 对Trapframe结构体的理解
  • 对写时复制机制的理解与实现
  • 对页写入异常处理流程的理解
  • 对进程的共享空间的理解
  • 对进程通信之间的实现

三、心得体会

在Lab4中,我学会了如何实现系统调用机制,并以此来实现进程创建函数fork()。整体来说,我认为本次实验还是较难的,前后关系很紧密,又引入了大量的宏以及函数,如何去使用或者实现它们是一大难点,同时由于实验代码的完成还涉及到对整个内存空间的认识,很多时候很容易分不清楚内核空间和用户空间,因此完成起来还是较为困难的。但是完成了实验之后,我对系统调用和进程创建的理解确实加深了不少,虽说仍然有一些地方存在些许疑惑,理解起来不是那么容易,担仍然感受到了操作系统设计的巧妙,同时也对整个内存空间的认识更深入。一句话总结起来就是困难但丰富。

  • Title: BUAA操作系统lab4
  • Author: Cdostan
  • Created at : 2025-05-06 17:10:00
  • Updated at : 2025-05-06 20:02:30
  • Link: https://cdostan.github.io/2025/05/06/OS/Lab4实验报告/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments