lab4实验报告
思考题
4.1
思考并回答下面的问题:
- 内核在保存现场的时候是如何避免破坏通用寄存器的?
- 系统陷入内核调用后可以直接从当时的\(a0-\)a3 参数寄存器中得到用户调用msyscall 留下的信息吗?
- 我们是怎么做到让sys 开头的函数“认为”我们提供了和用户调用msyscall 时同样的参数的?
- 内核处理系统调用的过程对Trapframe 做了哪些更改?这种修改对应的用户态的变化是?
- 保存现场过程中,
k0
寄存器暂存sp
栈指针的值,k1
帮助更新sp
的值,v0
用于帮助存储非通用寄存器的值。且k
寄存器在MIPS
中为暂时随便使用的寄存器,v0
为存放函数返回值,不会造成影响。 - 可以
- 人工将参数加载到了sys开头函数认为的位置,保存在
a0-a3
寄存器以及内核栈指针的24字节的后8字节。 - 对
Trapframe
结构体中的cp0_epc
的值增加了4,并将sys开头函数的返回值存入v0
寄存器,保证了系统调用后用户态进程可以获得正确的返回值,并且从触发系统调用的下一条指令继续执行。
4.2
思考下面的问题,并对这两个问题谈谈你的理解:
- 子进程完全按照fork() 之后父进程的代码执行,说明了什么?
- 但是子进程却没有执行fork() 之前父进程的代码,又说明了什么?
- 说明了子进程和父进程具有相同的代码段。
- 说明创建子进程时,保存了父进程的上下文、PC值以及进程状态。
4.3
关于fork 函数的两个返回值,下面说法正确的是:
A. fork 在父进程中被调用两次,产生两个返回值
B. fork 在两个进程中分别被调用一次,产生两个不同的返回值
C. fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值
D. fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值
答案为C
4.4
如果仔细阅读上述这一段话, 你应该可以发现, 我们并不是对所有的用户空间页都使用duppage 进行了保护。那么究竟哪些用户空间页可以保护,哪些不可以呢,请结合include/mmu.h 里的内存布局图谈谈你的看法。
UTOP以上的空间为内核相关的页表,所有的用户进程都相同,也无权进行更改,因此不需要用duppage进行保护。
USTACKTOP到UXSTACKTOP之间,为Invalid memory和用户进程的异常栈。Invalid memory不会用到为空闲区,不需要保护;异常栈是进行异常处理的地方,不应该受到写时复制机制的保护。
综上,需要保护从UTEXT到USTACKTOP的页表。
4.5
在遍历地址空间存取页表项时你需要使用到vpd 和vpt 这两个“指针的指针”,请思考并回答这几个问题:
vpt 和vpd 的作用是什么?怎样使用它们?
从实现的角度谈一下为什么能够通过这种方式来存取进程自身页表?
它们是如何体现自映射设计的?
进程能够通过这种存取的方式来修改自己的页表项吗?
-
vpt
中存放的为二级页表数组,vpd
存放的为一级页表数组。下标代表第几个页表,例如虚拟地址va
的二级页表为(*vpt)[va >> 12]
,一级页表为(*vpd)[va >> 22]
。 - 观察
entry.S
中的代码:
.globl vpt
vpt:
.word UVPT
.globl vpd
vpd:
.word (UVPT+(UVPT>>12)*4)
进程页表的位置为UVPT,则vpt
指向UVPT;vpd
指向(UVPT+(UVPT>>12)*4)
,不难看出是自映射机制。
- 用户进程无权修改自己以及内核的页表项。
4.6
page_fault_handler 函数中,你可能注意到了一个向异常处理栈复制Trapframe 运行现场的过程,请思考并回答这几个问题:
- 这里实现了一个支持类似于“中断重入”的机制,而在什么时候会出现这种“中断重入”?
- 内核为什么需要将异常的现场Trapframe 复制到用户空间?
- 当用户写时复制产生缺页中断进行处理的时候,可能会再次发生缺页中断,即会出现“中断重入”。
- 用户进程在中断结束恢复现场需要Trapframe,则需要写入用户空间,方便用户获取自己当前进程的上下文信息。
4.7
到这里我们大概知道了这是一个由用户程序处理并由用户程序自身来恢复运行现场的过程,请思考并回答以下几个问题:
用户处理相比于在内核处理写时复制的缺页中断有什么优势?
从通用寄存器的用途角度讨论用户空间下进行现场的恢复是如何做到不破坏通用寄存器的?
- 将操作交与用户自己完成,简化了操作系统的复杂度,符合微内核设计理念
- 通过sp寄存器来恢复通用寄存器,最后恢复sp寄存器,保证通用寄存器值得正确性。
4.8
请思考并回答以下几个问题:
- 为什么需要将set_pgfault_handler 的调用放置在syscall_env_alloc 之前?
- 如果放置在写时复制保护机制完成之后会有怎样的效果?
- 子进程需不需要对在entry.S 定义的字__pgfault_handler 赋值?
- 在父进程调用env_alloc过程中可能也需要进行缺页处理。
- 写时复制保护机制可能会引发这种缺页异常,而异常处理未设置好,则不能正常处理。
- 不需要,子进程完全继承自父进程,复制了父进程中__pgfault_handler变量值。
实验难点
本次实验的难点一共有两处,一处是在系统调用前对传递参数的存取,一处是fork中在不同进程中函数返回值不同机制的理解。
V0和V1会存储函数返回值;
A0 - A3会存储函数传入值(剩余传值在栈里)
S0 - S7在调用函数前后不变
SP存放栈值
RA存放返回地址
在函数调用前后,t0 - t7
都有可能发生改变,因此不能用这些寄存器保存栈指针
在fork函数中,对子进程的Trapframe
中的v0寄存器设置为0,来使得当子进程进入调度后,从syscall_env_alloc
中返回值为0。以及子进程和父进程一些信息的保护和设置。
体会和感想
- 许多地方没有思考准确
fork创建子进程时,何时将子进程加入调度队列,其实跟自己编写的调度算法有关,因为调度算法是不会对ENV_NOT_RUNNABLE的进程进行调度,所以可以在创建子进程时就将其加入,但为了将函数的功能分离,而且有些时候创建子进程时并不想让其加入调度队列,最后还是需要在set_env_status中区实现。 - C语言省去了很多对寄存器值调用的步骤
函数调用时对寄存器的保存以及传值、恢复等,写C时候完全不可见。但与汇编相比较起来,就会感受到高级语言的美好。