操作系统lab6实验报告
实验文档-lab6
一、思考题汇总
思考1:
示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改?
答:在fork函数中,返回后会先执行父进程,再执行子进程,所以父进程执行写入操作。因此在父进程开始运行时,先切换到子进程执行,就能让子进程作为写者,父进程作为读者。
#include <stdlib.h>
#include <unistd.h>
int fildes[2];
/* buf size is 100 */
char buf[100];
int status;
int main()
{
status = pipe(fildes);
if (status == -1) {
/* an error occurred */
printf("error\n");
}
switch (fork()) {
case -1: /* Handle error */
break;
case 0: /* Child - writes to pipe */
close(fildes[0]); /* Read end is unused */
write(fildes[1], "Hello world\n", 12); /* Write data on pipe */
close(fildes[1]); /* Child will see EOF */
exit(EXIT_SUCCESS);
default: /* Parent - reads from pipe */
yield();
close(fildes[1]); /* Write end is unused */
read(fildes[0], buf, 100); /* Get data from pipe */
printf("child-process read:%s", buf); /* Print the data */
close(fildes[0]);
}
}
思考2:
上面这种不同步修改pp_ref而导致的进程竞争问题在user/fd.c中的dup函数中也存在。请结合代码模仿上述情景,分析一下我们的dup函数中为什么会出现预想之外的情况?
答:dup函数的功能是将一个文件描述符所对应的内容映射到另一个文件描述符中。当我们将一个管道的读/写端对应的文件描述符(记作fd[0])映射到另一个文件描述符。在进行映射之前,f[0],f[1]与pipe的引用次数分别为1,1,2。按照dup函数的执行顺序,会先将fd[0]引用次数+1,再将pipe引用次数+1,如果fd[0]的引用次数+1后恰好发生了一次时钟中断,进程切换后,另一进程调用_pipeisclosed函数判断管道写端是否关闭,此时pageref(fd[0]) = pageref(pipe) = 2,所以会误认为写端关闭,从而出现判断错误。
思考3:
阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析。
答:在进行系统调用时,系统陷入内核,会关闭时钟中断,以保证系统调用不会被打断,因此系统调用都是原子操作。
.macro CLI
mfc0 t0, CP0_STATUS
li t1, (STATUS_CU0 | 0x1)
or t0, t1
xor t0, 0x1
mtc0 t0, CP0_STATUS
.endm
思考4:
我们考虑之前那个预想之外的情景,它出现的最关键原因在于:pipe 的引用次数总比 fd 要高。当管道的 close 进行到一半时,若先解除 pipe 的映射,再解除 fd 的映射,就会使得 pipe 的引用次数的-1 先于 fd。这就导致在两个 unmap 的间隙,会出现pageref(pipe) == pageref(fd) 的情况。那么若调换 fd 和 pipe 在 close 中的 unmap 顺序,能否解决这个问题呢?
仔细阅读上面这段话,并思考下列问题:
- 按照上述说法控制
pipeclose中fd和pipe unmap的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。 - 我们只分析了
close时的情形,在fd.c中有一个dup函数,用于复制文件内容。试想,如果要复制的是一个管道,那么是否会出现与close类似的问题?请模仿上述材料写写你的理解。
答:可以解决。如果程序正常运行,pipe的pageref是要大于fd的,在执行unmap操作时,优先解除fd的映射,这样就可保证严格大于关系恒成立,即使发生了时钟中断,也不会出现运行错误。
dup同理。
思考5:
bss在 ELF 中并不占空间,但 ELF 加载进内存后,bss段的数据占据了空间,并且初始值都是 0。请回答你设计的函数是如何实现上面这点的?
答:处理bss段的函数是lab3中的load_icode_mapper。在这个函数中,我们要对bss段进行内存分配,但不进行初始化。当bin_size < sgsize时,会将空位填0,在这段过程中为bss段的数据全部赋上了默认值0。
思考6:
为什么我们的 *.b 的 text 段偏移值都是一样的,为固定值?
答:在user的link script文件中对.text段的地址做了统一的约定。
思考7:
在哪步,0 和 1 被 “安排” 为标准输入和标准输出?请分析代码执行流程,给出答案。
答:在user\init.c中。
二、实验难点图示
难点1:填写有关管道的函数,即piperead,pipewrite和_pipeisclosed函数。
_pipeisclosed:这是一个判断函数,这个函数的判断机制主要是看pageref(fd)和pageref(page)的大小,为保证这两个值在读取过程中没有切换,需要不断进行env_runs的判断,直到稳定为止。piperead:在p_rpos >= p_wpos时不能立刻返回,而是应该根据_pipeisclosed()的返回值判断管道是否关闭,若未关闭,应执行进程切换。pipewrite:在p_wpos - p_rpos >= BY2PIPE时不能立刻返回,而是应该根据_pipeisclosed()的返回值判断管道是否关闭,若未关闭,应执行进程切换。
难点2:spawn函数。
此函数的填写从理解上来说十分复杂,个人认为是整个OS课设中最难理解的一个。中间用到了lab3的那个复杂的load_icode_mapper所对应的系统调用,在此过程中,需要利用一个临时页面以支持在用户态进行二进制文件的加载。
三、体会与感想
关于lab6
lab6的内容相对较少,但理解上的难度较高,很多函数都给人一种难以下手的感觉,在弄懂函数原理的过程中,花费了很多的功夫。
在lab6中依然少不了每次必有的祖传bug挖掘环节,这次找到了一个在lab5中的bug,即在fsformat.c中的create_file内没有遍历充分。
此外,lab6的代码给人一种OO第二单元多线程的感觉,在许多地方都要考虑由于进程切换所造成的影响等,这些可能的情况指导书基本已经给出,需要详尽的考虑。
关于OS课程
OS的实验终于在这里告一段落,这应该是我接触计算机以来,第一次接触有关内核的知识,第一次去一步步探索计算机的底层代码如何执行,虽然说中间不管是课下实验还是课上实验都遇到了些许困难,但最终的收获还是很大的。

浙公网安备 33010602011771号