Lab-6实验报告

Lab-6实验报告

20373915-朱文涛

实验思考

  • Thinking 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 ) {
        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);    
    }
}

 

  • Thinking 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),误认为是写者全部结束,于是错误地返回了。

 

  • Thinking 6.3

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

 

系统调用一定是原子操作,因为在通过系统调用陷入内核态时汇编代码关闭了时钟中断,此时其余的进程是无法运行的,保证了使用系统调用的进程可以占据整个操作系统直到系统调用结束。

 

  • Thinking 6.4

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

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

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

     

  • 可以解决。最初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相似的问题,需要调整映射的顺序。

 

  • Thinking 6.5

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

 

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

 

  • Thinking 6.6

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

 

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

 . = 0x00400000;

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

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

 

  • Thinking 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);

 

实验难点


spawn过程

image

这次我的spawn采用系统调用来加载程序,有效复用了Lab-3的代码.

 

体会与感想

本次实验难度一般,难点在于pipe在多进程并发环境下可能出现的一些问题和spawn函数的设计,其中后者我利用系统调用巧妙地避开了。

实验内容方面,真正测试shell才发现我们的小系统还是有太多这样那样的不完善之处,但是这样能让一个小系统跑起来,也是一个很大的收获。

lab6的结束意味着操作系统实验课程的阶段性结束,os理论仍在路上!



posted @ 2022-07-06 22:31  `Demon  阅读(6)  评论(0编辑  收藏  举报