BUAA_OS lab6实验报告

一、思考题

1. 思考6.1

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

修改后代码如下(交换case 0 和default部分的代码):

 #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 - 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);        
  default: /* Parent - reads from pipe */
  close(fildes[1]); /* Write end is unused */
  read(fildes[0], buf, 100); /* Get data from pipe */
  printf("parent-process read:%s",buf); /* Print the data */
  close(fildes[0]); /* Finished with pipe */
  exit(EXIT_SUCCESS);
  }
 }

2. 思考6.2

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

考虑这样的伪代码:

 if(fork() == 0) {
  dup(p[1]);
  read(p[0]);
 }
 else {
  dup(p[0]);
  write(p[1]);
 }

注意到在dup函数中,先对fd执行syscall_mem_map,后对data执行syscall_mem_map。

子进程先开始执行,执行到dup和read之间时发生时钟中断,此时pageref(p[1]) == 1,pageref(pipe) == 1;父进程开始执行,在dup函数中执行到fd的map之后,data的map之前,此时pageref(p[0]) == 1,pageref(pipe) == 1;时钟中断,子进程再次开始执行,此时执行read函数,判断发现pageref(p[0]) == pageref(pipe),误认为是写者全部结束,于是错误地返回了。

3. 思考6.3

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

系统调用一定是原子操作,因为在通过系统调用陷入内核态时汇编代码关闭了时钟中断。

4. 思考6.4

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

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

• 我们只分析了 close 时的情形,在 fd.c 中有一个 dup 函数,用于复制文件内容。试想,如果要复制的是一个管道,那么是否会出现与 close 类似的问题?请模仿上述材料写写你的理解。

  • 可以解决。最初pageref(p[0]) = 2,pageref(p[1]) = 2,pageref(pipe) = 4;仍考虑子进程先运行,对p[1]执行close函数时先解除了对写端fd的映射,之后发生时钟中断,此时pageref(p[0]) = 2,pageref(p[1]) = 1,pageref(pipe) = 4;父进程执行完close(p[0])后,pageref(p[0]) = 1,pageref(p[1]) = 1,pageref(pipe) = 3,父进程开始运行时仍不满足写端关闭的条件,因此竞争问题没有出现。

  • 在程序执行时,总满足pageref(p[0]) ≤ pageref(pipe),如果在dep中先对fd进行映射,此时fd的rep先增加,可能填补了二者的空隙,造成读端已满的假象(写端同理),因此也会出现与close相似的问题,需要调整映射的顺序。

5. 思考6.5

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

与lab3实现几乎完全相同,当系统调用执行sys_mem_alloc函数时进一步调用了page_alloc函数,其中有bzero使新分配的页面内容全部为0,只要不向数据段错误地复制内容,即可保证该段初始值为0。

6. 思考6.6

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

观察user/user.lds文件内容,有如下代码:

 . = 0x00400000;
 
  _text = .;                   /* Text and read-only data */
  .text : {
        *(.text)
        *(.fixup)
        *(.gnu.warning)
        }

这说明所有文件在链接时都以此规定相同的起始地址。

7.思考6.7

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

在user/init.c中,有如下代码,在此处将0和1安排为了标准输入输出:

 close(0);
         if ((r = opencons()) < 0)
                 user_panic("opencons: %e", r);
         if (r != 0)
                 user_panic("first opencons used fd %d", r);
         if ((r = dup(0, 1)) < 0)
                 user_panic("dup: %d", r);

二、实验难点

1. 管道与进程切换

由于时钟中断可能在函数执行中间发生,而有时需要保证同步性,因此需要注意判断是否在同一时间片内。_pipeisclosed函数中就需要运用env_runs变量计数进程执行次数,只有在同一次数内才能保证pageref的同步性。

2. spawn

在加载ELF文件内容时,需要调用自己写的usr_load_elf函数,这与lab3中的load_icode_mapper相似,不同之处在于本次位于用户态下,而要实现两个进程间内容的传递,需要通过系统调用,将文件系统进程的一个暂时页面映射到子进程所需页面,之后文件系统进程在该页面的具体位置复制内容,子进程相应位置也就有了信息,最后文件系统进程再解除工具页面的映射,开始下一个位置的映射。

三、体会与感想

本次lab用时13小时,相比于前几个lab用时少了很多,也可能是因为到了考期,而且没有了课上测试的要求,有些代码没有很认真地理解,做得有些潦草。计划考期结束后重新过一遍,加深对管道的理解。

实现完文件系统和管道后,操作系统似乎更加有意思了起来,原来只是对着一块黑黑的屏幕写一些十分抽象的代码,运行结果也只是一些printf的输出,而现在可以自己创建文件,可以实现控制台中断,与控制台交互等,随便敲敲都会有显示,于是像个没见过世面的小孩子,觉得惊喜又有趣。

四、残留难点

对填写范围以外的官方代码理解还不透彻,需要更细致的学习。

posted @ 2021-06-28 10:46  菠菜白菜花菜  阅读(754)  评论(0编辑  收藏  举报