6.s081 - Lab02 - System calls
lab02的学习记录和题解
About
System calls
在这次实验中主要实现的部分转为了系统调用,这次的两个应用程序已经在
user中实现,我们要做的是实现并配置这个应用程序调用的系统调用
在这里可以先解释一下各个文件主要实现的内容:
usys.pl 脚本文件,生成usys.S汇编文件,很好的实现了简便性和一致性
usys.S 汇编文件,是连接用户空间和内核系统调用机制的实际桥梁代码,当应用程序调用一个函数如fork()的时候,会将这个调用链接到usys.S对应的标签处,并通过ecall来真正的和内核交互(ecall的详情在课程后面更新)
sysproc.c 没有对应头文件,真正实现了sys_系列的系统调用中的进程相关函数
sysfile.c 没有对应头文件,真正实现了sys_系列的系统调用中的文件相关函数
user/user.h 用户空间C函数声明的合集,在用户空间中的实现在usys.S中,通过ecall进入内核中真正的实现sys_xxx
syscall.h 包含了各种系统调用对应的编号,其目的在于配合ecall的使用(且只包含这些)
syscall.c 具体实现在后面更新,大致理解为用于沟通用户空间C函数和内核空间系统调用
proc.c proc.h 实现了一些进程有关的结构体和函数,在本章节暂时只用到proc这一个结构体(定义在proc.h中),具体内容在Lab中解释
System call tracing
给多个参数,其中第二个参数是整数
mask,意为掩码,借助它可凭借仅一个整数即可表示多个进程编号,如32即表示100000即表示编号为5的系统调用read,所以若给出参数为32即要求追踪某进程中read调用的调用情况,若是如10101,则是追踪编号为0、2和4的系统调用。</br> 再后面的参数就是一个具体的命令,我们要追踪的也将是这一个命令产生的子进程。
让我们暂且梳理一下全过程,当我们输入命令trace后,我们将调用应用程序trace,其具体实现已经写好,但其中要调用trace函数,因此我们需要补充这个函数,在user/user.h中添加trace函数声明
1
int trace(int mask);
但是其具体实现实则是在kernel中的sys_trace中实现的,我们想将二者联系起来就需要一个汇编程序usys.S,而为了保持一致性和简洁性我们采用脚本文件将其生成,故在user/usys.pl中添加系统调用存根
1
entry("trace");
再在kernel/syscall.h中添加对应的系统调用序号
1
#define SYS_trace 22
这样就可以完成编译了,但我们尚未实现关键的sys_trace函数,这里按照文档要求,在kernel/proc.h中定义的struct proc中先添加字段mask来表示掩码,其值的是否存在也可以用来表示该进程是否正在被追踪,然后在kernel/sysproc.c中实现sys_trace函数
1
2
3
4
uint64 sys_trace(void) {
argint(0, &myproc()->mask);
return 0;
}
接下来修改fork()函数,因为多了一个mask字段,这也是进程中需要复制的成分
1
2
3
4
5
int fork(void) {
...
np->mask = p->mask;
return 0;
}
然后需要修改的是syscall()函数,要保证在mask这个字段存在的时候输出追踪内容,而这里设计到两个数组,分别是系统给定的syscallis[]数组以及我们自己实现的syscall_names[]数组,这是syscalls[]数组的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
这是定义了一个函数数组,这样就可以通过syscalls[i]的形式来定位到函数入口的地址然后进行调用,下面的[] sys的形式也是进行了元素的定义,因为如SYS_fork这样的是一种宏定义,在syscall.h中定义了,这样的写法也是一种索引的定义,注意要加上我们自己写的sys_trace,但这个时候sys_trace系统调用没有调用过来,所以要在前面的一堆extern中加上一句
1
extern uint64 sys_trace(void);
接下来就能完成最后syscall()函数的完善了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static char* syscall_names[] = {"",
"fork", "exit", "wait", "pipe", "read", "kill",
"exec", "fstat", "chdir", "dup", "getpid", "sbrk",
"sleep", "uptime", "open", "write", "mknod", "unlink",
"link", "mkdir", "close", "trace", "sysinfo"};
void syscall(void) {
int num;
struct proc* p = myproc();
num = p->trapframe->a7;
// num = * (int*)0;
if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call
// it, and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
// 在系统调用返回后添加打印信息的逻辑代码
if ((1 << num) & p->mask) {
printf("%d: syscall %s -> %d\n", p->pid,
syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
具体的解释在后面的陷阱(trap)机制中有所涉及,这里主要理解其p->trapframe->a0代表的是其追踪的系统调用的返回值,如read返回的读取的字节数以及fork返回的pid值等
最后运行
1
./grade-lab-syscall trace
即可完成测试,若结果全为OK则通过测试