Lab链接:Lab: System calls
Lab源码:momo/MIT-6S081/syscall - Gitee.com
1 System call tracing
在操作系统开发与调试过程中,对程序进行系统调用进行跟踪有助于深入了解程序运行时的系统行为
题目
创建一个新的系统调用 trace
,该系统调用接收一个整数参数 mask
。通过对 mask
二进制位的设置,指定需要跟踪的系统调用。例如,若要跟踪 fork
系统调用,程序需调用 trace(1 << SYS_fork)
,其中 SYS_fork
是在 kernel/syscall.h
中定义系统调用编号。当每个系统调用即将返回时,若该系统调用的编号在 mask
中被设置,则打印一行信息,包含进程 ID、系统调用名称以及返回值,无需打印系统调用的参数。
trace
系统调用应仅对调用它的进程及其后续通过 fork
创建的子进程启用跟踪功能,而不影响其他进程。
(一)部分系统调用跟踪
执行 trace 32 grep hello README
,其中 32
对应 1<<SYS_read
,表示仅跟踪 grep
执行过程中 read
系统调用,输出:
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
(二)全系统调用跟踪
执行 trace 2147483647 grep hello README
,2147483647
的低 31 位全为 1,即跟踪 grep
执行过程中所有系统调用,输出:
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
(三)无跟踪情况:执行 grep hello README
,此程序未启用跟踪,因此无跟踪输出。
(四)特定程序的系统调用跟踪
执行 trace 2 usertests forkforkfork
,将跟踪 usertests
中 forkforkfork
测试的所有后代进程的 fork
系统调用,输出示例:
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
思路
创建一个新的系统调用,包含两部分:在用户态提供系统调用入口 + 在内核态实现系统调用函数
在用户态提供系统调用入口
- 在
user/user.h
文件中,声明系统调用函数的原型 - make 编译时会将
user/usys.pl
文件中的内容转化为user/usys.S
,这个汇编文件声明了用户调用系统函数时实际执行的汇编指令,如下图所示:

可以看出,系统调用与普通函数虽然表面上都是通过函数名进行调用,但两者在调用方式上具有本质区别:
- 系统调用实际上是通过将系统调用编号存入指定寄存器,再执行 ecall 指令来实现调用
- 普通函数的调用则依赖于栈帧结构,在调用栈中完成参数传递和返回地址管理
内核态实现trace系统调用
- 在
kernel/syscall.h
文件中,声明新系统调用编号 - 在
kernel/syscall.c
文件中,声明新系统调用函数
本题目的实现思路:
- 在
user/user.h
、kernel/syscall.h
、kernel/syscall.c
中添加新系统调用相关字段、函数原型声明 - 在
kernel/sysproc.c
,新增sys_trace
函数,实现trace系统调用执行逻辑:从寄存器中获取跟踪的系统调用的mask值,并在调用的进程结构体中将该字段赋值给 - 修改
kernel/syscall.c
中的syscall
函数,在系统调用结束前打印相关信息,如果没有待跟踪的系统调用,在用户态直接调用trace 0
命令即可,系统调用结束前不会打印信息(if mask)
源码
kernel/proc.h:设置进程trace系统调用的mask参数字段
struct proc:
int tracemask; // System Trace Call mask
kernel/sysproc.c:将trace系统调用参数mask放入进程结构体的tracemask字段中
uint64 sys_trace(void) {
int mask;
if(argint(0, &mask) < 0) {
return -1;
}
struct proc *p = myproc();
p->tracemask = mask;
return 0;
}
kernel/syscall.c:系统调用时,判断当前进程traceemask字段是否把包含该系统调用,如果包含打印调用信息
static char* syscalls_name[] = {
"",
"fork",
"exit",
"wait",
.....
"trace",
};
int syscall(void) {
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
if(((p->tracemask >> num) & 1) == 1)
printf("%d: syscall %s -> %d\n", p->pid, syscalls_name[num], p->trapframe->a0);
} else {
printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
kernel/proc.c:子进程复制父进程mask参数
void fork():
// copy trace mask
np->tracemask = p->tracemask;
2 Sysinfo
题目
添加一个新的系统调用 sysinfo
,旨在为用户态程序提供便捷获取系统运行信息的途径,有助于系统监控、调试以及性能优化等操作
该系统调用需接收一个参数,该参数是指向 struct sysinfo
结构体的指针。此结构体在 kernel/sysinfo.h
中定义,用于存储系统信息。
- 参数设定:新增的
sysinfo
- 信息填充:内核在处理
sysinfo
系统调用时,需对struct sysinfo
结构体的特定字段进行填充。其中,freemem
字段要设置为系统当前空闲内存的字节数,这反映了系统当前可用于分配的内存资源量;nproc
字段需设置为状态不为UNUSED
的进程数量,以此体现系统中正在运行或处于就绪等活跃状态的进程总数。
实验提供了测试程序 sysinfotest
,用于验证 sysinfo
系统调用的正确性。若 sysinfotest
程序最终输出 “sysinfotest: OK”,则表明实验成功完成。
思路
sysinfo 系统调用实现
实现获取系统非空闲进程数的函数
proccnt
和系统空闲内存大小的函数kfreecnt
在
kernel/sysproc.c
中,新增sys_sysinfo
函数:- 获取用户空间接受sysinfo数据的地址
- 调用
proccnt
、kfreecnt
获取当前系统运行状态信息 - 调用
copyout
将内核空间的系统数据写回n
源码
kernel/proc.c:返回当前系统中,状态不为UNUSED的进程的数目
uint64 proc_cnt(void) {
uint64 cnt = 0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state != UNUSED)
++cnt;
}
return cnt;
}
kernel/kalloc.h:返回当前系统中,空闲内存大小(空闲页数 * 页大小)
uint64 kcnt(void) {
uint64 cnt = 0;
struct run *r;
r = kmem.freelist;
while(r != 0) {
++cnt;
r = r->next;
}
return cnt * PGSIZE;
}
kernel/sysinfo.c:实现sysinfo系统调用
uint64 sys_sysinfo(void) {
uint64 addr;
argaddr(0, &addr);
struct sysinfo info;
struct proc *p = myproc();
info.freemem = kcnt();
info.nproc = proc_cnt();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}