操作系统实验
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) {
goto err;
}
//将newfd的数据所在的虚拟页映射到oldfd的数据所在的物理页
if (vpd[PDX(ova)]) {
for (i = 0; i < PDMAP; i += BY2PG) {
pte = vpt[VPN(ova + i)];
if (pte & PTE_V) {
// 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) {
goto err;
}
}
}
}
考虑以下情况:
//父进程
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) {
user_panic("opencons: %d", r);
}
// stdout
if ((r = dup(0, 1)) < 0) {
user_panic("dup: %d", r);
}
将 0 或 1 号文件描述符设置为标准输入或输出。
Thinking 6.7
- 在 MOS 中我们用到的 shell 命令需要 fork 一个子 shell 进行执行,所以是外部命令。
- 由于 linux 的 cd 指令的功能是进入目录,需要读取文件,陷入内核,故不需要 fork 一个子 shell,是内部命令。
Thinking 6.8
- 两次 spawn ,对应两次生成的子进程分别用于执行ls.b和cat.b。
- 两次销毁,对应销毁生成的两个子进程。
难点分析
笔者认为本次实验的难点如下:
- 判断管道关闭的控制逻辑,涉及多进程执行结果的不确定性,依靠合理安排代码块顺序进行解决,十分巧妙。
- 父子进程使用管道时共享内存的细节问题。
- spawn 函数的处理流程和细节。
思考体会
lab6 的指导书相比前两个实验要更加明晰易懂,当然这也可能是我们在 lab6 完成了较为完整的操作系统,对其有整体的把控,但一些函数的细节理解还是有一定的挑战性。总之,在课程组的引导下,一步步实现一个可以工作的操作系统是一次难得的学习经历,使我对理论课的知识有了具象化的理解。