【OS】BUAA-OS-Lab6-实验报告
OS Lab实验报告
思考题
Thinking 6.1
示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改?
答:交换case和default即可
1 | |
Thinking 6.2
上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/lib/fd.c 中的 dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup 函数中为什么会出现预想之外的情况?
答:在以下这种情况下:
1 | |
可能会出现以下问题:
父进程执行 dup(p[0], newfd), pageref(p[0]) 变为2,尚未更新管道数据块的 pp_ref。子进程执行 read(p[0], buf, sizeof(buf)) ,发现 pageref(p[0])为2,子进程退出。父进程继续执行 write(p[1], "a", 5),子进程退出 read,父进程 write 可能永远无法结束。
问题主要是由于修改pp_ref不是原子操作。
Thinking 6.3
阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析说明。
答:MOS中系统调用是原子的,在系统调用前关闭了中断,同时其他进程也无法进入内核,所以系统调用是原子的
Thinking 6.4
仔细阅读上面这段话,并思考下列问题
- 按照上述说法控制
pipe_close中fd和pipeunmap的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。 - 我们只分析了
close时的情形,在fd.c中有一个dup函数,用于复制文件描述符。试想,如果要复制的文件描述符指向一个管道,那么是否会出现与close类似的问题?请模仿上述材料写写你的理解。
答: - 可以,先
unmap fd,减少pageref(fd),再unmap pipe,减少pageref(pipe),保证pageref(pipe) >= pageref(fd)始终成立。其他进程不会观察到pageref(fd) > pageref(pipe),不会误判管道状态。 - 会出现类似问题,
dup先增加pageref(fd),后增加pageref(pipe),中间状态可能出现pageref(fd) > pageref(pipe)。其他进程如果观察到这一状态,会误判管道的一端已关闭。
Thinking 6.5
- 认真回看 Lab5 文件系统相关代码,弄清打开文件的过程。
- 回顾 Lab1 与 Lab3,思考如何读取并加载 ELF 文件。
- 在 Lab1 中我们介绍了
datatextbss段及它们的含义,data段存放初始化过的全局变量,bss段存放未初始化的全局变量。关于memsize和filesize,我们在 Note 1.3.4 中也解释了它们的含义与特点。关于 Note 1.3.4,注意其中关于“bss段并不在文件中占数据”表述的含义。回顾 Lab3 并思考:elf_load_seg()和load_icode_mapper()函数是如何确保加载ELF文件时,bss段数据被正确加载进虚拟内存空间。bss段在 ELF 中并不占空间,但 ELF 加载进内存后,bss段的数据占据了空间,并且初始值都是0。请回顾elf_load_seg()和load_icode_mapper()的实现,思考这一点是如何实现的?下面给出一些对于上述问题的提示,以便大家更好地把握加载内核进程和加载用户进程的区别与联系,类比完成
spawn函数。关于第一个问题,在 Lab3 中我们创建进程,并且通过
ENV_CREATE(...)在内核态加载了初始进程,而我们的 spawn函数则是通过和文件系统交互,取得文件描述块,进而找到 ELF 在“硬盘”中的位置,进而读取。关于第二个问题,各位已经在 Lab3 中填写了
load_icode函数,实现了 ELF 可执行文件中读取数据并加载到内存空间,其中通过调用elf_load_seg函数来加载各个程序段。在 Lab3 中我们要填写load_icode_mapper回调函数,在内核态下加载 ELF 数据到内存空间;相应地,在 Lab6 中spawn函数也需要在用户态下使用系统调用为 ELF 数据分配空间。
答: - 先调用
open()函数,然后调用fdipc_open()联系文件服务进程,文件服务进程接收到消息后调用serve_open(),创建文件,打开文件,传递回FileFd。 - 解析ELF头获取程序头表位置,读程序头表定位LOAD段,遍历LOAD段,调用
elf_load_seg(),调用load_icode_mapper()分配物理页;文件数据段:复制内容到内存;BSS段:分配零初始化页。 - BSS中
filesize=0,memsize>0。elf_load_seg()发现bin_size < mem_size时,调用load_icode_mapper(NULL, size),进行page_insert,保证有足够空间放置bss,同时设置src = NULL得到设置初始值为0。
Thinking 6.6
通过阅读代码空白段的注释我们知道,将标准输入或输出定向到文件,需要我们将其 dup 到 0 或 1 号文件描述符(fd)。那么问题来了:在哪步,0 和 1 被“安排”为标准输入和标准输出?请分析代码执行流程,给出答案。
答:
1 | |
fd = 0: 打开控制台设备,返回文件描述符,未打开任何fd,opencons()返回最小可用fd,验证返回值为0,确保fd = 0指向控制台输入。fd = 1: 复制fd = 0文件表项到fd = 1,使fd = 1与fd = 0指向同一控制台设备,将标准输出定向到控制台。
Thinking 6.7
在 shell 中执行的命令分为内置命令和外部命令。在执行内置命令时 shell 不需要 fork 一个子 shell,如 Linux 系统中的 cd 命令。在执行外部命令时 shell 需要 fork 一个子 shell,然后子 shell 去执行这条命令。
据此判断,在 MOS 中我们用到的 shell 命令是内置命令还是外部命令?请思考为什么 Linux 的 cd 命令是内部命令而不是外部命令?
答:内置命令是shell的功能,不需要创建新的进程;外部命令是磁盘上的程序,需要创建新的进程来执行。MOS中的shell为外部命令。在sh.c的主循环中,无论何种命令,shell都会先执行fork()创建子进程,再通过runcmd()执。这说明所有命令都在子进程中运行,是外部命令。cd是内置命令,如果是外部命令,在子进程中执行,目录变更不会影响父shell。
Thinking 6.8
在你的
shell中输入命令ls.b | cat.b > motd。
- 请问你可以在你的
shell中观察到几次spawn?分别对应哪个进程? - 请问你可以在你的
shell中观察到几次进程销毁?分别对应哪个进程?
答:观察到两次 spawn ,第一次生成运行ls.b的进程;第二次生成运行cat.b的进程。观察到四次进程销毁。
难点分析
我认为本次实验并不简单,难点主要是管道关闭存在的竞态条件,但是通过对其解决方案的学习可以帮助理解这些问题。同时Shell的实现也需要仔细思考,理解好ELF文件的加载。
实验体会
终于完成了操作系统课程的最后一个Lab,顿感身心轻松,通过本学习的学习,我对于操作系统这座大厦如何拔地而起有了更深刻的了解,也不禁为其中的设计智慧而感叹。虽然课程已经结束,但是对于操作系统的学习不会停止,继续努力!
原创性说明
本报告为本人原创