北航操作系统lab6实验报告

OS lab6实验报告

实验思考题

Thinking 6.1

示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改?

父进程先关闭写通道即可。

 father_process
     close(fildes[1]); /* Write end is unused */
     read(fildes[0], buf, 100); /* Get data from pipe */
     printf("father-process read:%s",buf); /* Print the data */
     close(fildes[0]);
     exit(EXIT_SUCCESS);

 

Thinking 6.2

上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/fd.c 中的dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup 函数中为什么会出现预想之外的情况?

dup函数的功能是将一个文件描述符(例如fd0)所对应的内容映射到另一个文件描述符(例如fd1)中。这个函数最终会将fd0pipe的引用次数都增加1,将fd1的引用次数变为fd0的引用次数。若在复制了文件描述符页面后产生了时钟中断,pipe的引用次数没来的及增加,可能会导致另一进程调用pipeisclosed,发现pageref(fd[0]) = pageref(pipe),误以为读/写端已经关闭。

 

Thinking 6.3

阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析。

在进行系统调用时,系统陷入内核,会关闭时钟中断。

 .macro CLI
     mfc0 t0, CP0_STATUS 
     li t1, (STATUS_CU0 | 0x1) 
     or t0, t1 
     xor t0, 0x1 
     mtc0 t0, CP0_STATUS 
 .endm

 

Thinking 6.4

仔细阅读上面这段话,并思考下列问题

• 按照上述说法控制 pipeclose 中 fd 和 pipe unmap 的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。

• 我们只分析了 close 时的情形,那么对于 dup 中出现的情况又该如何解决?请模仿上述材料写写你的理解。

可以解决,若在pageref(pipe) > pageref(fd)的情况下便没有问题,而如果pageref(pipe) == pageref(fd)的话,那么当读缓冲区为空,写缓冲区为满时会再次循环直到进程切换两者全部unmap为止。

dup也会出现同样的问题,先对pipe进行map,再对fd进行map即可。

 

Thinking 6.5

bss 在 ELF 中并不占空间,但 ELF 加载进内存后,bss 段的数据占据了空间,并且初始值都是 0。请回答你设计的函数是如何实现上面这点的?

当加载到bin_size~sgsize之间的数据时,就知道新入了bss端,使用bzero函数赋值为0,不需要再读取ELF的数据。

 

Thinking 6.6

为什么我们的 *.b 的 text 段偏移值都是一样的,为固定值?

user/user.lds文件中约定了text段地址为0x00400000

 

Thinking 6.7

在 shell 中执行的命令分为内置命令和外部命令。在执行内置命令时 shell 不需要 fork 一个子 shell,如 Linux 系统中的 cd 指令。在执行外部命令时 shell 需要 fork 一个子 shell,然后子 shell 去执行这条命令。据此判断,在 MOS 中我们用到的 shell 命令是内置命令还是外部命令?请思考为什么 Linux 的 cd 指令是内部指令而不是外部指令?

我们用到的shell命令是外部命令,因为我们的user文件夹中有cat.c ls.c文件,Linux下的cd指令没有对应的文件,使用时也不需要单独的创建一个子进程。cd 所做的是改变 shell 的 PWD。 因此倘若 cd 是一个外部命令,那么它改变的将会是子 shell 的 PWD,也不会向父 shell 返回任何东西。所以,当前 shell 的 PWD 就不会做任何改变。所有能对当前 shell的环境作出改变的命令都必须是内部命令。 因此如果我们将 cd 做成外部命令,就无法像原来一样改变当前目录了。

 

Thinking 6.8

在哪步,0 和 1 被 “安排” 为标准输入和标准输出?请分析代码执行流程,给出答案。

user/init函数中可以看到如下步骤,它将0映射在1上,相当于就是把控制台的输入输出缓冲区当做管道。

 if ((r = dup(0, 1)) < 0)
         user_panic("dup: %d", r);

 

Thinking 6.9

在你的 shell 中输入指令 ls.b | cat.b > motd。

• 请问你可以在你的 shell 中观察到几次 spawn?分别对应哪个进程?

• 请问你可以在你的 shell 中观察到几次进程销毁?分别对应哪个进程?

两次,分别对应[00001c03] SPAWN: ls.b[00002404] SPAWN: cat.b

四次,分别对应[00003406] destroying 00003406[00002c05] destroying 00002c05[00002404] destroying 00002404[00001c03] destroying 00001c03

 

实验难点展示

管道

pipe函数对两个文件描述符的操作十分重要,两个文件描述符本身是不同的,但他们却指向的是同一个管道,先syscall_mem_alloc,再syscall_mem_map,通过fd_omode限定该文件描述符的读写。

_pipeisclosed函数里,我们发现了env_runs这个从lab 3就一直跟随着我们的属性的价值,这或许也可以作为其他函数用于判断是否是类原子操作的一个方法。

shell

load_icode_mapper函数已经写过了,这次需要在用户态下实现这个函数,用户态实现二进制文件的加载不像内核态的调用那样简单,但大体的思路差不多,也会用到user_bcopyuser_bzerospawn函数的填写更是需要看一下ELF文件格式才好理解。而解析shell命令的runcmd函数相对就简单一点。

判断当前的字符是否是特殊符号可以有一个更快捷的方法,这样可以避免书写过长的switch。

 #define SYMBOLS "<|>&;()"
     if(strchr(SYMBOLS, *s)) {
         /* do something */
     }

在lab6的验证里又发现了以前lab的bug,是跟sched_yield函数相关的bug。这个bug导致了父进程调用pipe()函数时,会创建两个管道。

体会与感想

个人感觉,lab6难度比lab5小一点,需要仔细阅读的代码量也比较小,但需要在理解lab5的基础上才能快速上手。其实自从lab5开始,我就觉得自己脑子没有那么灵光了,可能是没有花太多时间总结的缘故。

OS的实验课就到此结束了,学了很多理论知识,也看了很多代码,具体的函数实现也没有想着能够一直记下来,大概能有个印象我觉得就行了。在我看来,这门课学到的其实也不止这些代码(因为感觉以后大概率也用不上),主要是学会了很多有用的工具,把c语言的应用也提升了一个档次,这就足够啦。

posted @ 2022-06-10 20:44  南风北辰  阅读(611)  评论(0编辑  收藏  举报