携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
这是我对MIT操作系统课程6.s081中的Lab的完成记录。
参考资料
- 官网实验要求:pdos.csail.mit.edu/6.S081/2021…
- csdiy.wiki/%E6%93%8D%E… 中提到的所有参考博客
实验准备
一定要认真看官方实验要求,特别是给出的hint!!!能解决90%的问题!!
切换到当前任务实验环境
$ git fetch
$ git checkout syscall
我记得我当时用的是git switch syscall。
上一个实验也提到过的make clean 操作:
make clean
(确实是这样,过程中某次make qemu之前忘记了make clean,结果失败,clean一下再试就可以了)
sytemcall tracing
已有条件
系统已配备用户级别的trace程序(代码见user/trace.c)
我们要完成的任务就是将这个用户级别的程序与底层联系起来,使得trace程序能被调用,借此理解系统调用的过程。
我们要完成的任务
在如下文件中添加东西:(基本上就是打开文件,上面几行代码怎么写,就依葫芦画瓢)
-
在Makefile文件的UPROGS中加入
$U/_trace -
在user/user.h的syscall中增加原型
- 在user/usys.pl中添加存根
该文件编译的时候会产生user/usys.S文件,这是一个汇编文件,和trace有关的文件长这样:
其中的ecall指令会将系统调用的存根(stub)传递到内核。
- 在
kernel/syscall.h中指明trace功能对应数字22
接下来是内核层面的操作:
- 在kernel/sysproc.c中添加一个sys_trace函数。 理论上这个sys_trace函数可以添加到kernel的任何地方,只要能找到就行(所以我看樊潇大佬就是新建了一个sys_trace.c文件然后把函数放进去),但是由于这个函数是和进程相关,为了方便理解,就放进sysproc.c里面去。
在vim中按shift+g直达文件末尾,添加如下代码:
sys_trace(void)函数解析:
- 先看实验指导说明:
这里的variable需要自己在kernel/proc.h中指定,如下图所示:
也对应于上面那段函数的syscallnum。
argint()用于获取这个mask,并且将其暂存入变量n后传给syscallnum。
- 关于函数中的myproc(),我看过kernel/proc.c相应代码,和syscallnum没有直接的关系。
应该是在后续修改fork()相关代码,创建子进程的时候 有关联。
继续刚才的实验步骤哈~ - 在kernel/proc.c中的fork()函数中,添加如下一行:
使得trace mask能从父进程传到子进程。
-
打印trace结果:修改
kernel/syscall.c内容(有几处需要修改)- 添加和trace相关的extern:
我的理解是,要告知内核,有这么个外部函数会被调用(备案的意思)
- 相应的,下面这个也得增加一行:
- 修改syscall()函数的内容,使其能打印trace的结果:
(就是在原来的基础上多加了个if)
- 函数的syscall_name是自己设的一个映射表,可以是数组,也可以采用上面用到的形式(听说c++语法不支持后者),这里采用的是后者。
在sys_call函数之前添加如下代码:
至此,所有的处理工作就完成了。
编译运行
make qemu
附上一张效果对比图:
一个编译通过但无法通过评测的解决方案:
在评测的时候遇到了一点问题:
但我确信不是我的问题,因为我在自己编译运行并输入
trace 2 usertests forkforkfork
的时候最后是出现了 ALL TESTS PASSED的。
这说明可能是运行时间太长,导致评测被强行终止了。
所以我修改了评测文件的超时限制,如下图所示:
打开gradelib.py文件,查找timeout关键词
(vim操作回顾:在normal模式下输入/后接你想查找的关键词,回车确定,之后再按n表示查找下一个,N表示查找上一个)
将timeout从30修改到更大的数值,我直接改成100了。
之后就舒服了:
sysinfo
已有条件
和trace一样,在user下已经有了一个sysinfotest.c的文件。 我们需要让内核感知到它的存在,让系统能顺利调用它。
命令说明
程序有一个参数,是一个指向struct sysinfo的指针。
需要完成的工作:
- Makefile中添加$U/_sysinfotest字段
- 仿照trace的做法在相应文件添加字段。
- 在user/user.h中声明原型前,还要先声明结构体,也就是总共添加这两行。
struct sysinfo;
int sysinfo(struct sysinfo *);
以上步骤完成后,程序还未能进入内核态。
- sysinfo需要复制一份struct sysinfo回内核态。可参照kernel/sysfile.c中的sys_fstat()和kernel/file.c中的filestat(),看看它们是怎么用copyout()的。 (笔者注:是这么用的)
举一反三,可在kernel/sysproc.c中添加如下代码:
在kernel/sysproc.c头部记得include "sysinfo.h"
这个文件也是系统准备好了的,打开看到长这样,已经为我们定义好了freemem和nproc
- 在kernel/kalloc.c中添加函数,freemem表示可用内存的字节数。
- 在kernel/proc.c中添加函数,nproc表示状态为UNUSED(未被使用)的字节数。
编译时 无法识别proc.c中的freememsize()和nproc_active()的解决方案:
在kernel/defs.h 中加入对这两个函数的声明。 如图所示:
这个defs.h定义了一些内模块接口。
运行结果:
make qemu:
评测:
总结:
- 这个lab让我对操作系统区分内核态、用户态,以及它们的交互过程有了更深的理解。
- 我也从实际代码层面加深了对程序在系统中的调用过程的理解。
附录:一些概念笔记
以下是我在阅读xv6相关部分使用说明的时候的一些概念摘录: 书籍链接:pdos.csail.mit.edu/6.S081/2021…
管道相比临时文件的优势:
- echo hello world | wc 等价于 echo hello world >/tmp/xyz; wc </tmp/xyz ,但是前者可以被自动清除,后者,tmp下的文件虽然最终会被删除,但是没那么及时
- 管道可以传输任意长的数据流,而文件重定向需要硬盘上有足够多的空余空间去存储数据。
- 管道允许并行执行流水线,而文件的方法需要等一个进程完成后才能开始第二个进程。
- 进程间通信方面,管道比临时文件更高效。
操作系统应有的三大特点:
- 多路复用
- 隔离性
- 交互性
用户模式、超级模式、机器模式
内核组织
整核(monolithic kernel)
整个操作系统都置于内核之中 缺点:错误是致命的,任何错误都会引起整个操作系统崩溃 事实上很多Linux系统都是这种设计
微核(microkernel):
最小化以超级模式运行的数量,并在用户态中执行大部分操作系统功能(区分user space和kernel space正式这种设计)
实验相关
图:进程虚拟地址空间的层次划分