Lab6实验报告
思考题
Thinking 6.1
更改前的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21int main()
{
switch (fork())
{
case -1:
break;
case 0: /* 子进程 - 作为管道的读者 */
close(fildes[1]); /* 关闭不用的写端 */
read(fildes[0], buf, 100); /* 从管道中读数据 */
printf("child-process read:%s",buf); /* 打印读到的数据 */
close(fildes[0]); /* 读取结束,关闭读端 */
exit(EXIT_SUCCESS);
default: /* 父进程 - 作为管道的写者 */
close(fildes[0]); /* 关闭不用的读端 */
write(fildes[1], "Hello world\n", 12); /* 向管道中写数据 */
close(fildes[1]); /* 写入结束,关闭写端 */
exit(EXIT_SUCCESS);
}
}更改后的代码:将子进程与父进程部分调换即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21int main()
{
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("child-process read:%s",buf); /* 打印读到的数据 */
close(fildes[0]); /* 读取结束,关闭读端 */
exit(EXIT_SUCCESS);
}
}
Thinking 6.2
- 关系式
pageref(rfd) + pageref(wfd) = pageref(pipe)
在非原子的close
函数调用时不能保证成立;作为非原子操作,在该函数进行时,有可能出现时钟中断,更换进程的问题,从而导致函数出现问题。
Thinking 6.3
系统调用一定是原子操作。在系统调用
syscall
对应的异常处理程序handle_sys
中,使用了汇编宏定义CLI
来禁用全局中断,因此系统调用时不会被中断,是原子操作。CLI
宏定义具体代码如下:1
2
3
4
5
6
7CLI
mfc0 t0, CP0_STATUS
li t1, (STATUS_CU0 | 0x1)
or t0, t1
xor t0, 0x1
mtc0 t0, CP0_STATUS
.endm
Thinking 6.4
- 可以解决。关系式
pageref(rfd) + pageref(wfd) = pageref(pipe)
中,ref(pipe)
先减小,ref(fd)
后减小,因此如果先解除p[0]
的映射,将会永远达不到取等的临界条件。 dup
函数是类似的,不过变为增加,也会出现与close
类似的问题,pipe
的引用次数总比fd
要高。当管道的dup
进行到一半时, 若先映射fd
,再映射pipe
,就会使得fd
的引用次数的加一先于pipe
。这就导致在两个map
的间隙,会出现pageref(pipe) == pageref(fd)
的情况。这个问题也可以通过调换两个map的顺序来解决。
Thinking 6.5
- 整体流程:分配文件标识符
fd
、按照路径打开文件、编辑文件标识符、匹配存储单元、返回文件标识符特征值。 - 读取加载方法:对于普通文件,我们需要编译形成
ELF
文件,之后才可以运行。 - 实现方式:对于bss段中和text&data段共同占据一个页面的部分,分配页面并设置初值为0;对于bss段其它部分,仅使分配页面而不映射到任何内容。
Thinking 6.6
在
user/init.c
中完成,代码如下: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
- shell命令是外部命令,在执行shell命令时,进程通过
fork()
函数产生一个子进程,即子shell,之后子shell运行该命令所对应的可执行文件。 - linux中的内部命令实际上是shell程序的一部分,其中包含一些比较简单的linux系统命令。这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留在系统内存中。
Thinking 6.8
- shell中进行了2次
spawn
,分别用于执行ls.b
和cat.b
; - shell中进行了2次进程销毁,这2个进程分别是shell执行
ls.b
和cat.b
时通过spawn
生成的子进程。
难点分析
本次实验中,我遇到的难点主要有以下几点:
pipe
相关的内存使用:本次实验中一个重点是管道的实现。出现管道时,为当前进程分出子进程,用父进程处理管道左端,用子进程处理管道右端。子进程和父进程的内存空间中分别为写端fd
和读端fd
分配了一个虚拟页,由于两个进程的fork
关系,子进程和父进程的读端所在的虚拟页映射到了同一个物理页,同样他们的写端所在的虚拟也也都映射到了同一个物理页,因此可以实现读写的统一性。spawn
函数:在该函数中,主要有6部分,分别是读取文件描述符fd
、读取ELF
文件头部、使用fork
生成子进程、初始化子进程的数据栈、读取文件中的项目头部、读取并匹配文件的ELF
数据、加载segment
。各部分均需实现报错后的逐层输出。难点主要在于各部分函数的使用以及整体的实现逻辑。
实验体会
Lab6主要让我们学习有关管道和shell
的问题,了解MIPS系统中虚拟地址与物理地址的对应关系、多文件的读取与更改的内容。本次实验对比前几次lab内容较少,同时由于指导书和注释的详细,完成难度不大。但是由于前面lab遗留的前后程序不适配问题,导致出现bug且较难查明的问题,耗时较久。十分感谢助教学长的帮助和那些写博客的学长(虽然发现了两篇有所不同的博客,也没有尝试是否都是正确的),看了他们的博客后真的是茅塞顿开。作为最后一个常规lab,整个os学习也算是有始有终地完成了。整个学期下来困难很多,但是学到了很多知识和算法,提升也很大。