BUAA操作系统lab6
Lab6实验报告
一、思考题
Thinking 6.1
示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改
代码如下:
1 |
|
Thinking 6.2
上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/lib/fd.c 中的dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup函数中为什么会出现预想之外的情况?
在dup函数的实现中,我们可以发现其先实现对文件描述符的映射,再实现对内容的映射,这里的内容及对应缓存区也即是pipe,在创建子进程后,考虑子进程使用dup函数将p[1]复制到另一个文件描述符,且在两次map的间隔发生了进程切换,那么到父进程时,有pageref(p[0])=1,pageref[p[1]]=2,pageref[pipe]=2,这便会导致父进程认为子进程已经关闭了读端,导致父进程退出。
Thinking 6.3
阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析说明。
系统调用设计为原子操作主要是为了保证安全性与正确性。系统调用是用户使用操作系统功能的唯一渠道,因此系统调用一定要保证用户使用之后能够得到预期的结果,假如其不是原子操作,那么允许在执行过程中被打断,则很可能会导致相应关键变量的更新出错,最后导致不可预期的结果。
Thinking 6.4
按照上述说法控制 pipe_close中 fd和 pipe unmap的顺序,是否可以解决上述场
景的进程竞争问题?给出你的分析过程。
改变二者unmap的顺序是可以解决上述场景的进程竞争问题的。由上文的等式:pageref(p[0]) + pageref(p[1]) = pageref(pipe),可以知道一定有pageref(p[0]) , pageref(p[1]) <= pageref(pipe),当我们先对fd进行unmap时,假设对p[0]进行unmap,就算这时切换,也一定有pageref(p[1]) < pageref(pipe),所以不会影响判断。
我们只分析了 close时的情形,在 fd.c中有一个 dup函数,用于复制文件描述符。
试想,如果要复制的文件描述符指向一个管道,那么是否会出现与 close类似的问
题?请模仿上述材料写写你的理解。
很可能会出现上述类似的问题,在Thinking 6.2中已经有所解释。
Thinking 6.5
回看Lab5文件系统相关代码,弄清打开文件的过程
打开文件调用user/lib/files.c中的open函数,该函数调用fsipc_open函数,这个函数主要功能是让当前进程与文件系统服务进程函数进行通信,让服务进程调用serve_open函数进一步调用file_open函数真正打开文件,并实现和用户进程对于文件描述符的共享。
回顾Lab1与Lab3,思考如何读取并加载ELF文件
通过load_icode函数,首先解析ELF文件头,如果有效,通过elf_load_seg依次将ELF文件中可加载的段加载出来,实现加载ELF文件。
在Lab1 中我们介绍了 data text bss 段及它们的含义,data 段存放初始化过的全局变量,bss段存放未初始化的全局变量。关于memsize和filesize,我们在Note1.3.4中也解释了它们的含义与特点。关于Note1.3.4,注意其中关于“bss段并不在文件中占数据”表述的含义。回顾Lab3并思考:elf_load_seg()和load_icode_mapper()函数是如何确保加载ELF文件时,bss段数据被正确加载进虚拟内存空间。bss段在ELF中并不占空间,但ELF加载进内存后,bss段的数据占据了空间,并且初始值都是0。请回顾elf_load_seg() 和 load_icode_mapper() 的实现,思考这一点是如何实现的?
在ELF文件中,由于.bss段保存的是未初始化的全局变量,不需要初始化成特定数据,因此在ELF文件中不需要有相应的数据,文件中只需记录这一段需要占用内存即可。在Lab3中,我们曾经了解过elf_load_seg()这个函数的实现过程,就是简单来讲就是按页加载文件所有内容,如果该段在内存中大小大于其在文件中大小,则向内存中填0直到其达到预期的大小,这其实就是在将.bss段加载进内存。
Thinking 6.6
通过阅读代码空白段的注释我们知道,将标准输入或输出定向到文件,需要我们将其dup到0或1号文件描述符(fd)。那么问题来了:在哪步,0和1被“安排”为标准输入和标准输出?请分析代码执行流程,给出答案。
在user/init.c中:
所以是在shell初始的时候标准输入和标准输出就已经被安排为0和1。
Thinking 6.7
在 shell 中执行的命令分为内置命令和外部命令。在执行内置命令时shell不需要fork 一个子 shell,如 Linux 系统中的 cd 命令。在执行外部命令时 shell 需要 fork一个子shell,然后子 shell 去执行这条命令。
据此判断,在MOS 中我们用到的 shell 命令是内置命令还是外部命令?请思考为什么Linux 的 cd 命令是内部命令而不是外部命令?

由上图可知,在shell中执行正常命令的时候都会创建出一个子进程,然后在子进程中runcmd(),因此MOS中我们使用的是外部命令。
在linux系统中,cd命令是拿来切换当前工作目录的,一方面,这是用户经常会使用的命令,如果设置为外部命令,涉及到频繁的进程创建和切换,效率不高;另一方面,作为内部命令,其可以迅速完成,以确保用户能很好地使用系统功能。
Thinking 6.8
在你的 shell 中输入命令 ls.b | cat.b > motd。
• 请问你可以在你的shell 中观察到几次spawn?分别对应哪个进程?
• 请问你可以在你的shell 中观察到几次进程销毁?分别对应哪个进程?

有两次spawn,对应的是id为14341和16390的两个进程。
有四次进程销毁,对应0x2803,0x3805,0x3004,0x4006这四个进程。
二、难点分析
本次实验让我们实现管道和shell,最终能够调出shell界面并在里面运行相关的可执行文件。本次实验的难点我认为最主要的其实在于对文件系统的理解,因为管道和shell的实现很大程度上都依赖于文件系统。总的来说,我认为本次实验有以下难点:
- 对管道读写机制的理解
- 对进程间同步机制对具体实现的影响的认识
- 对shll的实现,包括函数调用流程的理解
三、心得体会
Lab6是操作系统的最后一节,而实现Lab6中的内容要大量运用到前面的Lab中的知识。管道和shell理解起来其实还是挺有难度的,东西很多也比较散,尤其是shell的实现,但当我最终看到能够成功显示shell界面,像我无数次在跳板机或虚拟机或是cmd一样在里面输入指令并成功运行的时候,内心的感触还是很深的。而完成了Lab6,其实也大概对整个实验进行了一遍回顾,思考题的设计也是有意让我们回顾整个实验的,我会发现那些在以前Lab1,Lab3或者Lab5等等实验中一些不解的点渐渐化开了,越来越感受到操作系统设计的精巧。
- Title: BUAA操作系统lab6
- Author: Cdostan
- Created at : 2025-06-05 19:00:00
- Updated at : 2025-06-05 20:08:12
- Link: https://cdostan.github.io/2025/06/05/OS/Lab6实验报告/
- License: This work is licensed under CC BY-NC-SA 4.0.