操作系统实验报告(lab6)


操作系统实验

Lab6


目录

1.思考题解答
2.难点分析
3.思考体会


思考题解答

Thinking 6.1

交换管道操作的顺序即可。

char buf[100];
int status;

int main(){

    status = pipe(fildes);

    if (status == -1 ) {
        printf("error\n");
    }

    switch (fork()) {
        case -1:
            break;

        case 0: /* 子进程 - 作为管道的写者 */
            close(fildes[0]); /* 关闭不用的读端 */
            write(fildes[1], "Hello world\n", 12); /* 向管道中写数据 */
            close(fildes[1]); /* 写入结束,关闭写端 */
            exit(EXIT_SUCCESS);

        default: /* 父进程 - 作为管道的读者 */
            close(fildes[1]); /* 关闭不用的写端 */
            read(fildes[0], buf, 100); /* 从管道中读数据 */
            printf("parent-process read:%s",buf); /* 打印读到的数据 */
            close(fildes[0]); /* 读取结束,关闭读端 */
            exit(EXIT_SUCCESS);
    }
}

Thinking 6.2

dup 函数的主要工作如下:

    //将newfd所在的虚拟页映射到oldfd所在的物理页
    if ((r = syscall_mem_map(0, oldfd, 0, newfd, vpt[VPN(oldfd)] & (PTE_D | PTE_LIBRARY))) <
        0) &#123;
        goto err;
    &#125;
    //将newfd的数据所在的虚拟页映射到oldfd的数据所在的物理页
    if (vpd[PDX(ova)]) &#123;
        for (i = 0; i < PDMAP; i += BY2PG) &#123;
            pte = vpt[VPN(ova + i)];

            if (pte & PTE_V) &#123;
                // should be no error here -- pd is already allocated
                if ((r = syscall_mem_map(0, (void *)(ova + i), 0, (void *)(nva + i),
                             pte & (PTE_D | PTE_LIBRARY))) < 0) &#123;
                    goto err;
                &#125;
            &#125;
        &#125;
    &#125;

考虑以下情况:

    //父进程
    dup(p[0], newfd);
    write(p[1], "Hello", 5);
    //子进程
    read(p[0], buf, sizeof(buf));
  • 若子进程先执行,还未进入 read 函数时发生进程切换,切换至父进程执行;
  • 父进程执行 dup 函数时,对 p[0] 进行映射而未来得及对 pipe 进行映射就发生进程切换;
  • 再次回到子进程时,ref(p[0]) == ref(pipe) == 2,即认为写进程关闭,出错!

Thinking 6.3

系统调用一定是原子操作:

/* disable interrupts */
    mtc0    zero, CP0_STATUS

因为系统调用时禁用了中断,防止了中途退出该进程。

Thinking 6.4

  • 可以解决,因为ref(p[0]) <= ref(pipe)恒成立,交换解除顺序后 ref(p[0]) 在相同时刻要更小,所以一定不会出现这种问题。
  • 如 Thinking 6.2 中所描述的,先对 p[0] 进行映射再对 pipe 进行映射,若中断条件发生在两类映射之间则存在使得ref(p[0]) == ref(pipe)的情况;也可以通过交换顺序解决。

Thinking 6.5

  • 打开文件的过程
    • 用户态调用 file.c 下的 open 函数;
    • open 函数调用 fsipc.c 下的 fsipc_open 函数;
    • fsipc_open 调用 fsipc.c 下的 fsipc 函数向内核态发生请求并接收反馈;
    • 内核态调用 serv.c 下的 serve_open 函数处理请求;
    • serve_open 调用 fs.c 下的 file_open 函数;
    • file_open 函数调用 fs.c 下的 walk_path 函数对磁盘块进行检索。
  • Lab3 中填写的 load_icode 函数,实现了 ELF 可执行
    文件中读取数据并加载到内存空间,其中通过调用elf_load_seg 函数来加载各个程序段。
    在 Lab3 中填写的 load_icode_mapper 回调函数,在内核态下加载 ELF 数据到内存空间。
  • 对于 bss 段中和其他段的数据在一个页面的部分,调用函数进行清零;对于其他部分,在存入内存时仅进行页面分配而不进行页面映射;使得其占据了空间且初值为0。

Thinking 6.6

在 user/init.c 的 main 函数中:

    // stdin should be 0, because no file descriptors are open yet
    if ((r = opencons()) != 0) &#123;
        user_panic("opencons: %d", r);
    &#125;
    // stdout
    if ((r = dup(0, 1)) < 0) &#123;
        user_panic("dup: %d", r);
    &#125;

将 0 或 1 号文件描述符设置为标准输入或输出。

Thinking 6.7

  • 在 MOS 中我们用到的 shell 命令需要 fork 一个子 shell 进行执行,所以是外部命令。
  • 由于 linux 的 cd 指令的功能是进入目录,需要读取文件,陷入内核,故不需要 fork 一个子 shell,是内部命令。

Thinking 6.8

  • 两次 spawn ,对应两次生成的子进程分别用于执行ls.b和cat.b。
  • 两次销毁,对应销毁生成的两个子进程。

难点分析

笔者认为本次实验的难点如下:

  • 判断管道关闭的控制逻辑,涉及多进程执行结果的不确定性,依靠合理安排代码块顺序进行解决,十分巧妙。
  • 父子进程使用管道时共享内存的细节问题。
  • spawn 函数的处理流程和细节。

思考体会

lab6 的指导书相比前两个实验要更加明晰易懂,当然这也可能是我们在 lab6 完成了较为完整的操作系统,对其有整体的把控,但一些函数的细节理解还是有一定的挑战性。总之,在课程组的引导下,一步步实现一个可以工作的操作系统是一次难得的学习经历,使我对理论课的知识有了具象化的理解。


文章作者: Argithun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Argithun !
 上一篇
北航计算机学院面向对象(2023 第四单元) 北航计算机学院面向对象(2023 第四单元)
本文将以笔者学习过程中的思考感悟为基础,对2023北航计算机学院面向对象课程第四单元(作业13~15)的架构搭建和程序设计思路做简明的描述;如有不同见解,欢迎学习交流。
2023-06-01
下一篇 
北航计算机学院面向对象(2023 第三单元) 北航计算机学院面向对象(2023 第三单元)
本文将以笔者学习过程中的思考感悟为基础,对2023北航计算机学院面向对象课程第三单元(作业9~11)的架构搭建和程序设计思路做简明的描述;如有不同见解,欢迎学习交流。
2023-05-01