lab6——管道与Shell

思考题

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

#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 - reads from pipe */

		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]); /* Finished with pipe */

		exit(EXIT_SUCCESS);

	default: /* Parent - 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);

}

}

switch之后的部分修改为:(交换case 0 和default的内容)

switch (fork()) {

	case -1: /* Handle error */

		break;

	case 0: /* Child - write 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 - read from pipe */

		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]); /* Finished with pipe */

		exit(EXIT_SUCCESS);

}

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

dup函数原来的写法是先打开文件描述符,然后在打开缓冲区。如果在这两个操作中间被中断了,就会让另一端误以为pipe没有打开

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

原子操作的意思其实就是这个操作能够一次性完成而不被中断所打断。因为我们使用系统调用的时候关闭了对中断的响应,执行系统调用期间就不会被中断,所以系统调用是原子操作。

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

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

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

可以解决。这样先关闭了缓冲区之后,引用次数已经被修改了,即使文件描述符还没有被关闭,pipe还是会判断缓冲区关闭,从而阻止进一步的读写。

dup中也是同样的原理,建立新的管道的时候,先建立缓冲区,即使文件描述符还没有建立,读写也可以进行了

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

elf通过load加载的时候会给bss段分配空间并且把他们清零

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

因为在链接器里我们把text的段开头的偏移量都设为了相同的值

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

Note 6.3.1 我们的测试进程从 user/icode 开始执行,里面调用了 spawn(init.b), 在完成了 spawn 后,创建了 init.b 进程......

难点

Exercise 6.2 根据上述提示与代码中的注释,填写 user/pipe.c 中的 piperead、pipewrite、_pipeisclosed 函数并通过 testpipe 的测试。

注意到read可能返回0-n的任意数量,读多少返回多少;但是写只会返回0或者n

由于是多线程,所以在判断对方的管道是否关闭的时候,守护条件需要使用while不断询问而不是if

小疑问:怎么通过fd和pipe结构体找到对应的pp_ref?

user/pageref.c中定义了pageref函数,通过找到虚拟地址对应的页表项读出pp_ref

Exercise 6.5 根据上述描述以及注释,完成user/spawn.c中的spawn函数
加载elf文件内容的时候,因为不是在内核态复制内容,所以要采取不同的方式

先在父进程分配一个临时页存储内容,之后通过syscall_mem_map和syscall_mem_unmap把内容复制到子进程里。其他部分和之前写的load_elf相同

*Exercise 6.6 根据以上描述,补充完成 user/sh.c 中的 void runcmd(char s)。

注意'>'给的代码和'<'不一样!!!需要自己补充上gettoken的部分!!!

其他部分按照注释写即可

体会与感想

相比起lab4和lab4的噩梦debug,lab6可以说是相当友好了(可能也是老师助教们考虑到我们准备期末考试的压力...)感觉最难理解的部分是6.5到底要干什么,以及和之前解析elf有什么不同。

不过多谢了助教的补充指导书和学业与发展支持中心的讲解,基本上还是在相对少的时间内弄明白了本次lab的任务并且完成了debug。

这一路走来多少辛酸只有自己清楚,大部分lab都经历了痛苦的十几个小时的写代码+debug。我觉得主要有三方面的原因。

一个是每个lab增加的代码越来越多,在不了解其背后实现的逻辑的情况下,对于每次做的任务并不能做到完全了解,即使是做完之后也不敢说就了解每个文件在这次测试都起到了什么作用。这样导致的结果就是写代码的过程经常是两眼一抹黑,只能凭着自己的理解瞎写,然后出了问题再回来找问题是什么。非常感谢助教的补充指导书,对于解决这个问题提供了巨大的帮助,但是即便如此我还是花费了大量的时间去理解每次作业到底要实现什么。

第二个原因就是实现的具体细节。这种情况一般是我通过阅读指导书和代码明白了需要实现什么功能,但是我不知道要怎么实现(具体应该调用什么函数)。比如一些功能其实已经封装好了,只需要直接调用就可以,但是问题是操作系统中函数确实很多,也不是每个函数都有注释解释清楚它的功能是什么。虽然通过猜测+实验也许可以调用正确的函数,但是调用之后其实对于这个函数背后的机制,以及他的具体功能还是不了解,造成了学习效果的下降。

第三个原因就是汇编和c语言的混合。对于c语言的debug毕竟还是有一些经验,但是汇编出现问题就很难定位,也不容易设置出相应的调试输出。当这两者混合的时候,debug定位就更加陷入混乱。

课设的学习即将进入尾声,虽然一路上经历了诸多痛苦,但是自己参与了实现的过程确实还是收获颇丰。如果说有什么建议的话,希望能把陈年老代码里错误的部分改掉......遇到这种问题花费大量时间debug感觉非常不值得。

posted @ 2020-08-26 09:56  bl水滴  阅读(546)  评论(0编辑  收藏  举报