lab6-实验报告
Thinking
Thinking 6.1
示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想 让父进程作为“读者”,代码应当如何修改?
1 |
|
Thinking 6.2
上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/lib/fd.c 中 的dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup函数中为什么会出 现预想之外的情况?
在fdc/dup函数中,依次实现了以下两个过程
- 将newfd文件描述符所在的虚拟页映射到oldfd所在的物理页(文件描述符的映射,类似于读写文件描述符的映射)
- 将newfd的文件数据对应的虚拟页映射到oldfd的文件数据对应的物理页(类似于pipe的映射)
1 | pipe(p); |
假设执行过程如下:
- fork执行结束后,子进程先执行。时钟中断发生在close(p[1])与read之间,父进程开始执行
- 父进程在dup(p[0],newfd)过程中,先增加了对p[0]的映射,还没有来得及增加对pipe的映射中断便已经发生,进程调度后子进程执行
- 此时pageref(pipe)=3,而pageref(p[0])=3,错误地判断了写端已经关闭
Thinking 6.3
**阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是 所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析说明。 **
所有的系统调用均为原子操作。我们在执行具体的异常处理程序前均进行了关中断操作,防止系统调用操作被打断
1 | exc_gen_entry: |
Thinking 6.4
**仔细阅读上面这段话,并思考下列问题 **
- 按照上述说法控制 pipe_close中 fd和 pipeunmap的顺序,是否可以解决上述场 景的进程竞争问题?给出你的分析过程。
- 我们只分析了 close时的情形,在 fd.c中有一个 dup函数,用于复制文件描述符。 试想,如果要复制的文件描述符指向一个管道,那么是否会出现与 close类似的问 题?请模仿上述材料写写你的理解。
可以解决上述问题中的竞争问题
pageref(p[0])本就小于pageref(pipe),如果先减去pageref(p[0]),则不会出现由于
pageref(p[0]) == pageref(pipe)
短暂成立而导致的问题了
dup函数会出现类似close的问题
pageref(p[0])本就小于pageref(pipe),在dup中如果先增加pageref(p[0]),则会出现由于
pageref(p[0]) == pageref(pipe)
短暂成立而导致的端口关闭错判问题,依旧可以通过调整顺序,先增加pageref(pipe)来加大两者间的差距而避免这种错误
Thinking 6.5
bss段 在ELF中并不占空间,但ELF加载进内存后,bss段的数据占据了空间,并且初始 值都是0。请回顾elf_load_seg() 和 load_icode_mapper() 的实现,思考这一点 是如何实现的?
当文件大小小于内存空间大小时,对于空缺处(即bss字段)进行了0填充
1 | // lib/elfloader.c中的elf_load_seg函数中进行了bss段0填充 |
Thinking 6.6
通过阅读代码空白段的注释我们知道,将标准输入或输出定向到文件,需要 我们将其dup到0或1号文件描述符(fd)。那么问题来了:在哪步,0和1被“安排”为 标准输入和标准输出?请分析代码执行流程,给出答案
在user/init.c
中
1 | // stdin should be 0, because no file descriptors are open yet |
Thinking 6.7
在 shell 中执行的命令分为内置命令和外部命令。在执行内置命令时shell不 需要fork 一个子 shell,如 Linux 系统中的 cd 命令。在执行外部命令时 shell 需要 fork 一个子shell,然后子 shell 去执行这条命令。 据此判断,在MOS 中我们用到的 shell 命令是内置命令还是外部命令?请思考为什么 Linux 的 cd 命令是内部命令而不是外部命令?
在MOS中使用的shell命令是外置命令,会通过fork产生子进程shell来运行该进程
Linux中的cd用于改变工作目录。如果cd作为一个外部命令在子进程中运行,并不会改变父进程shell的工作目录,这不符合cd命令的目的。
Thinking 6.8
在你的 shell 中输入命令 ls.b | cat.b > motd。
- 请问你可以在你的shell 中观察到几次spawn?分别对应哪个进程?
- 请问你可以在你的shell 中观察到几次进程销毁?分别对应哪个进程?
直接在shell中运行命令可以得到以下结果
1 | ls.b | cat.b > motd |
在spawn.c
中最后添加如下信息:
1 | debugf("spawn: father %x, child %x\n", syscall_getenvid(), child); |
可得到如下运行信息:
1 | ls.b | cat.b > motd |
可以看到,shell进行了2次spawn,生成的子进程用于执行ls.b与cat.b
进行了四次进程销毁,分别销毁了
- 由fork创建的
ls.b | cat.b > motd
代表的子进程 - spawn创建的完成ls.b的子进程
- fork创建的
cat.b > motd
子进程 - spawn创建的cat.b的子进程
学习难点
学习过程中,lab6的spawn
函数理解起来较难,具体流程如下:
- 调用open函数打开shell指令指定的文件
- 通过readn读取该可执行文件的ehdr,
- 通过系统调用
syscall_exofork
创建子进程 - 调用
init_stack
初始化子进程栈空间 - 遍历elf文件的程序头表将段数据加载到子进程内存空间
- 正确设置子进程的栈指针与epc
- 将父进程的共享页面映射至子进程地址空间
- 设置子进程状态为可运行
shell函数的调用关系
实验体会
在Lab6中我们实现了简单的匿名管道与shell机制,需要我们填写完成的部分较为简单。但是想要对于诸如shell的处理机制有深入认识与了解的话还是要进一步仔细阅读代码。
感觉突然就最后一个lab了,感觉还是稀里糊涂~~~