Post

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,则是追踪编号为024的系统调用。</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则通过测试

Sysinfo

This post is licensed under CC BY 4.0 by the author.