BUAA-OS-lab6-管道与shell
发表于:2023-06-10 | 分类: BUAA操作系统
字数统计: 2.5k | 阅读时长: 9分钟 | 阅读量:

BUAA-OS lab6实验报告,关于mos系统的管道通信的实现,shell的实现。

lab6_log

Pre

  • 这次实验需要我们实现管道
  • 实现shell
  • 实现shell中涉及管道的部分

Thinking

Thinking 6.1

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

  • 这个简单,父进程关掉写端1,子进程关掉读端0。

Thinking 6.2

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

  • 原理同pipe_close一致,都是在一个函数两个相关的页面map或者unmap的之间发生了时钟中断,所以会导致不一致的现象。
  • dup的作用就是将oldfdnum指向的数据复制给newfdnum,一共需要有两次map,将newfd所在的虚拟页映射到oldfd所在的物理页,将newfd的数据所在的虚拟页映射到oldfd的数据所在的物理页。这两者之间肯定是对应着的需要一起解一起连的。

Thinking 6.3

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

  • 系统调用一定是原子操作,进程切换是通过定时器产生时钟中断,触发时钟中断切换进程。但是syscall跳转到内核态时,CPU将SR寄存其的IEc置位0,关闭了时钟中断。

Thinking 6.4

Thinking 6.4 仔细阅读上面这段话,并思考下列问题
• 按照上述说法控制 pipe_close 中 fd 和 pipe unmap 的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。
• 我们只分析了 close 时的情形,在 fd.c 中有一个 dup 函数,用于复制文件内容。试想,如果要复制的是一个管道,那么是否会出现与 close 类似的问题?请模仿上述材料写写你的理解。

  • 分析得到,当对于pipe页面的引用次数小于对两个fd的和的时候,就会出现安全问题,而只要先解除fd再解除pipe,就不会出现上述问题,问题也仅仅只是拉大了二者的差距而已,对于结果没有影响。
  • 上面我们说到的dup会出现不一致的情况,但是会不会有安全问题呢?
    • 如果复制的时一个管道,也会出现这样的问题,因为这个是先将fd的映射+1,所以也会出现pipe页面的引用次数小于对两个fd的和的问题。

Thinking 6.5

Thinking 6.5 思考以下三个问题。
• 认真回看 Lab5 文件系统相关代码,弄清打开文件的过程。
• 回顾 Lab1 与 Lab3,思考如何读取并加载 ELF 文件。
• 在 Lab1 中我们介绍了 data text bss 段及它们的含义,data 段存放初始化过的全局变量,bss 段存放未初始化的全局变量。关于 memsize 和filesize ,我们在 Note1.3.4中也解释了它们的含义与特点。关于 Note 1.3.4,注意其中关于“bss 段并不在文件中占数据”表述的含义。回顾 Lab3 并思考:elf_load_seg() 和 load_icode_mapper()函数是如何确保加载 ELF 文件时,bss 段数据被正确加载进虚拟内存空间。bss 段在 ELF 中并不占空间,但 ELF 加载进内存后,bss 段的数据占据了空间,并且初始值都是 0。请回顾 elf_load_seg() 和 load_icode_mapper() 的实现,思考这一点是如何实现的?
下面给出一些对于上述问题的提示,以便大家更好地把握加载内核进程和加载用户进程的区别与联系,类比完成 spawn 函数。

  • 关于第一个问题,在 Lab3 中我们创建进程,并且通过 ENV_CREATE(…) 在内核态加载了初始进程,而我们的 spawn 函数则是通过和文件系统交互,取得文件描述块,进而找到 ELF 在“硬盘”中的位置,进而读取。
  • 关于第二个问题,各位已经在 Lab3 中填写了 load_icode 函数,实现了 ELF 可执行文件中读取数据并加载到内存空间,其中通过调用 elf_load_seg 函数来加载各个程序段。在 Lab3 中我们要填写 load_icode_mapper 回调函数,在内核态下加载 ELF 数据到内存空间;相应地,在 Lab6 中 spawn 函数也需要在用户态下使用系统调用为 ELF 数据分配空间。
  • 而bss段应该与text段data段连续的放在一起,但是ELF中没有空间,在分配映射页面时,text段与data段没有占满的空间置为0给了bss段,然后再给他另外分配的时候,只使用syscall_mem_alloc而不映射。

Thinking 6.6

Thinking 6.6 通过阅读代码空白段的注释我们知道,将文件复制给标准输入或输出,需要我们将其 dup 到 0 或 1 号文件描述符 (fd)。那么问题来了:在哪步,0 和 1 被“安排”为标准输入和标准输出?请分析代码执行流程,给出答案。

  • 在user/init.c中,打开了console的文件。
1
2
3
4
5
6
7
8
// stdin should be 0, because no file descriptors are open yet
if ((r = opencons()) != 0) {
user_panic("opencons: %d", r);
}
// stdout
if ((r = dup(0, 1)) < 0) {
user_panic("dup: %d", r);
}

Thinking 6.7

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

  • 在mos中,我们都会在执行命令前fork一下,通过子进程来完成这个命令,所以都是外部命令。
  • cd命令需要改变父进程的状态,即所在的路径,通过fork之后,只改变了子进程,所以需要是内部命令。

Thinking 6.8

Thinking 6.8 在你的 shell 中输入命令 ls.b | cat.b > motd。
• 请问你可以在你的 shell 中观察到几次 spawn ?分别对应哪个进程?
• 请问你可以在你的 shell 中观察到几次进程销毁?分别对应哪个进程?

  • 有两次spawn,分别打开了ls.b,cat.b进程
  • 有四个进程的销毁,分别是左指令的执行进程,右指令的执行进程,spawn打开的两个执行进程。

Exercise

Exercise 6.1

  • 首先是实现pipe,操作都被封装在user/lib/pipe.c中。通过pipe函数创造一个pipe,用pipe_read与pipe_write来实现向pipe中的读写(这里的读写也是阻塞的方式),用pipe_close来关闭pipe,用pipe_is_close来判断是否关闭。
  • 得理解对于pipe的共享是什么原理,其实就是读端与写端的文件描述符都映射到同一个页面,perm置位PTE_LIBRARY,这样fork的时候两个进程之间能够共享这一个管道。然后这个fd映射的页面上存了一个pipe的结构体。
  • 这里理解了pipe读写的原理,就应该不会很难,通过对于一个fd上的pipe结构体的共享来完成读写,并且是一种类似于循环队列的方式。
  • 然后还得理解关于pipe_is_close的判定方法,就是进程对于fd页面的引用与pipe页面的应用次数相等即为close。

Exercise 6.2-6.3

  • 接下来是要考虑进程安全的问题,即当读端与写端关闭管道的时候,可能发生在已经解除了对于管道的映射,但是害没有解除对于fd的映射。这就会出现问题,总映射的次数<写端次数+读端次数。
  • 安全问题的解决思考题说的很清楚了,移步观看。
  • 所以dup与close里的map与unmap交换一下顺序就可以解决这个问题。
    • 当然另一种思路肯定就是关中断。
  • 然后在判断关闭的时候要retry,这个也能解决进程安全的问题,就是说,若在判断过程中发生了中断,那就重新判断一遍。

Exercise 6.4-6.5

  • 接下来是shell的实现了,首先要实现一个spawn函数,这个函数的作用是打开文件系统中的文件并且执行。
  • 打开文件通过与文件系统进程的ipc通信,得到之后为这个二进制文件创建进程。
  • 然后关于shell的实现,我们需要填充的,只是对于命令的解析,这是一个中间步骤,解析完就可以去运行了,当然碰到< 与 > 与 | 这类特殊的符号,需要进行特别的处理。
    • 碰到< 或 >就重定向输入输出,使用dup
    • 碰到 | 就fork出一个新进程,然后建立起两者之间的管道。

总结

  • lab6 总体的实验难度不难,但是要理解整个流程还是花费了不少的时间。
  • 而且我也感觉lab6最后填空似乎也并没有做出什么东西,于是也激发了我的探索欲望,我选择了做lab6的challenge,在做challenge的过程中,感觉自己对于整个shell的实现过程的理解更加深入了。
上一篇:
BUAA-OO-JML-SocialContact
下一篇:
BUAA-OS-lab5-文件系统