Lab 2:system calls
课程地址:https://pdos.csail.mit.edu/6.828/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.828/2020/labs/syscall.html
代码仓库:https://github.com/ajackchan/my-xv6-riscv/tree/syscall
System-call-tracing
实现一个新的系统调用
trace
,用于跟踪指定的系统调用,并在每次调用后输出当前进程ID、系统调用的名称和返回值。
1. 定义新的 trace
系统调用
目标:实现一个新的
trace
系统调用,用于追踪进程中的系统调用。实现代码:
在
sysproc.c
中实现系统调用sys_trace
,根据用户传递的mask
设置当前进程的syscall_trace
字段,用来表示要跟踪哪些系统调用。1
2
3
4
5
6uint64 sys_trace(void) {
int mask;
if (argint(0, &mask) < 0) return -1;
myproc()->syscall_trace = mask;
return 0;
}
2. 系统调用的注册
在
syscall.h
中为SYS_trace
分配编号:1
#define SYS_trace 22
在
syscall.c
中将sys_trace
添加到系统调用表中: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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48// kernel/syscall.c
extern uint64 sys_chdir(void);
extern uint64 sys_close(void);
extern uint64 sys_dup(void);
extern uint64 sys_exec(void);
extern uint64 sys_exit(void);
extern uint64 sys_fork(void);
extern uint64 sys_fstat(void);
extern uint64 sys_getpid(void);
extern uint64 sys_kill(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_mknod(void);
extern uint64 sys_open(void);
extern uint64 sys_pipe(void);
extern uint64 sys_read(void);
extern uint64 sys_sbrk(void);
extern uint64 sys_sleep(void);
extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void); // HERE
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, // AND HERE
};
3. 用户态跳板函数生成
在
usys.pl
中添加trace
,生成用户态到内核态的跳板函数。1
entry("trace");
在
user.h
中声明系统调用接口:1
int trace(int mask);
4. 修改进程结构
在
proc.h
中为proc
结构添加syscall_trace
字段,用来存储系统调用跟踪的mask
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// kernel/proc.h
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
struct proc *parent; // Parent process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
uint64 syscall_trace; // Mask for syscall tracing (新添加的用于标识追踪哪些 system call 的 mask)
};在
proc.c
中初始化syscall_trace
,确保每个新进程的syscall_trace
初始值为 0。1
p->syscall_trace = 0;
修改
fork()
函数,使子进程继承父进程的syscall_trace
。1
np->syscall_trace = p->syscall_trace;
5. 跟踪系统调用
在
syscall()
函数中检查进程是否需要追踪某个系统调用。如果设置了syscall_trace
,在系统调用执行后,打印出进程ID、系统调用名称和返回值。1
2
3if ((p->syscall_trace >> num) & 1) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}添加
syscall_names[]
数组,用于将系统调用编号映射到系统调用名称:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const char *syscall_names[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
[SYS_wait] "wait",
[SYS_pipe] "pipe",
[SYS_read] "read",
[SYS_kill] "kill",
[SYS_exec] "exec",
[SYS_fstat] "fstat",
[SYS_chdir] "chdir",
[SYS_dup] "dup",
[SYS_getpid] "getpid",
[SYS_sbrk] "sbrk",
[SYS_sleep] "sleep",
[SYS_uptime] "uptime",
[SYS_open] "open",
[SYS_write] "write",
[SYS_mknod] "mknod",
[SYS_unlink] "unlink",
[SYS_link] "link",
[SYS_mkdir] "mkdir",
[SYS_close] "close",
[SYS_trace] "trace",
};
6. 系统调用的完整流程
用户程序调用
trace()
,触发跳板函数,通过系统调用表找到内核中的sys_trace
,最终设置进程的syscall_trace
。系统调用到达内核时,进入
syscall()
函数,根据syscall_trace
字段决定是否打印系统调用的追踪信息。
7. 小结
- 添加新的
trace
系统调用。 - 更新
proc
结构,使每个进程可以记录需要追踪的系统调用。 - 修改
fork()
函数,使得子进程继承父进程的追踪信息。 - 在
syscall()
函数中实现追踪逻辑,打印进程的系统调用信息。 - 使用
syscall_names[]
字符串数组,将系统调用编号与名称映射起来,便于打印。
Sysinfo
实现一个新的系统调用
sysinfo
,该调用会收集系统信息,包括空闲内存的字节数和非UNUSED
状态的进程数。
1. 在 kernel/syscall.h
中新增宏定义
为
sysinfo
系统调用分配系统调用编号,添加以下宏定义:1
#define SYS_sysinfo 23
2. 修改 user/usys.pl
,新增 sysinfo
的 entry
在
usys.pl
文件中,新增一条 entry,用于生成sysinfo
的用户态跳板函数:1
entry("sysinfo");
3. 在 user/user.h
中新增 sysinfo
结构体及函数声明
定义
sysinfo
结构体,用于保存系统信息(例如空闲内存和进程数目)。声明
sysinfo
系统调用的用户接口。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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45struct stat;
struct rtcdate;
struct sysinfo; // here!
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
int trace(int);
int sysinfo(struct sysinfo *); // here!
// ulib.c
int stat(const char*, struct stat*);
char* strcpy(char*, const char*);
void *memmove(void*, const void*, int);
char* strchr(const char*, char c);
int strcmp(const char*, const char*);
void fprintf(int, const char*, ...);
void printf(const char*, ...);
char* gets(char*, int max);
uint strlen(const char*);
void* memset(void*, int, uint);
void* malloc(uint);
void free(void*);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
4. 在 kernel/syscall.c
中新增 sys_sysinfo
函数定义
在
syscall.c
文件中,声明sys_sysinfo
系统调用:1
extern uint64 sys_sysinfo(void);
5. 在 kernel/syscall.c
中函数指针数组新增 sysinfo
名称
在
syscalls[]
函数指针数组中,新增sys_sysinfo
的映射:1
[SYS_sysinfo] sys_trace,
在
syscall_names[]
数组中,为sysinfo
添加名称:1
2
3
4const char *syscall_names[] = {
...
[SYS_sysinfo] "sysinfo",
};
6. 在 kernel/kalloc.c
中新增 freemem_size
函数
实现
freemem_size
函数,获取当前系统的空闲内存量:1
2
3
4
5
6
7
8
9
10
11int
freemem_size(void) {
struct run *r;
int num = 0;
for(r = kmem.freelist; r; r = r->next) {
num++;
}
return num * PGSIZE;
}
7. 在 kernel/proc.c
中新增 proc_num
函数
实现
proc_num
函数,计算当前非UNUSED
状态的进程数目:1
2
3
4
5
6
7
8
9
10
11
12
13int
proc_num(void) {
struct proc *p;
uint64 num = 0;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state != UNUSED) {
num++;
}
}
return num;
}
8. 在 kernel/defs.h
中新增函数声明
在
defs.h
文件中声明新增的两个函数freemem_size
和proc_num
:1
2
3
4
5// kalloc.c
void* kalloc(void);
void kfree(void *);
void kinit(void);
int freemem_size(void); // here!1
2
3
4
5
6// proc.c
int cpuid(void);
void exit(int);
int either_copyin(void *dst, int user_src, uint64 src, uint64 len);
void procdump(void);
int proc_num(void); // here!
9. 在 kernel/sysproc.c
中新增 sysinfo
的实现
在
sysproc.c
文件中包含sysinfo.h
头文件,并实现sysinfo
系统调用:1
#define "sysinfo.h"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19uint64
sys_sysinfo(void) {
struct sysinfo info;
uint64 addr;
struct proc *p = myproc();
if(argaddr(0, &addr) < 0) {
return -1;
}
info.freemem = freemem_size();
info.nproc = proc_num();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) {
return -1;
}
return 0;
}