Lab: system calls
该Lab需要为xv6实现几个系统调用。
System call tracing
trace系统调用,为每个进程设定一个标记为mask,根据mask决定trace哪些系统调用。
系统调用整个流程如下:
- user/user.h:用户态程序调用跳板函数
- user/usys.S:跳板函数的实现为脚本生成的汇编,内部调用CPU的ecall指令,进入内核态
- kernel/syscall.c:进入内核态的统一处理函数syscall(),在这里根据系统调用编号,查找syscalls表,获取对应的函数指针
- kernel/sysproc.c:这里会进入sys_trace()函数,因为与进程有关,所以实现在sysproc.c里,最终就会跳转到这并执行
所以添加一个系统调用的流程如下:
- 在user.user.h中注册
- 在user/usys.pl中增加系统调用存根 entry("XXXX")
- 在kernel/syscall.h中定义系统调用号 #define SYS_XXXXX XX
- 在kernel/syscall.c中使用extern定义系统调用函数,并将其调用号-调用函数指针的映射关系加入syscalls中
- 在kernel中实现系统调用
- 若在用户态存在对应用户程序的,需在user下定义XX.c并加入Makefile
根据这个步骤完成trace,有如下改动:
为进程结构体增加标记位,以标记需要trace的调用号
//kernel/proc.h
struct proc {
...
uint64 syscall_trace; // Mask syscall trace
};
增加trace系统调用号定义
//kernel/syscall.h
#define SYS_trace 22
在syscall.c中添加trace打印时所需的检索数组,声明sys_trace函数,并将其函数指针与调用号的映射关系加入syscalls中,并且由于trace的功能是要求我们打印所有被标记的系统调用,需要在调用处实现,在syscall函数中添加打印逻辑
//kernel/syscall.c
const char *syscall_names[] = { /* trace打印时检索的调用名称 */
[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",
[SYS_sysinfo] "sysinfo",
};
extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
...
[SYS_sysinfo] sys_sysinfo,
};
void syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if (num > 0 && num < NELEM(syscalls) && syscalls[num])
{
p->trapframe->a0 = syscalls[num]();
if (p->syscall_trace >> num & 1)
{
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;
}
}
usys.pl中添加存根
// user/usys.pl
entry("trace");
user.h中添加定义
// user/user.h
int trace(int);
在进程创建时,为其syscall_trace赋初值0
// kernel/proc.c
static struct proc *
allocproc(void)
{
...
p->syscall_trace = 0;
return p;
}
在syspro.c中实现sys_trace
// kernel/sysproc.c
// trace
uint64
sys_trace(void)
{
int mask;
if (argint(0, &mask) < 0)
return -1;
myproc() -> syscall_trace = mask;
return 0;
}
在fork中,使子进程可以继承父进程的syscall_trace
// kernel/proc.c
int fork(void)
{
...
np->syscall_trace = p->syscall_trace;
...
return pid;
}
Makefile
$U/_trace\
修改完成,编译测试,结果如下:
$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 968
3: syscall read -> 235
3: syscall read -> 0
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 968
4: syscall read -> 235
4: syscall read -> 0
4: syscall close -> 0
...
Sysinfo
创建系统调用,返回空闲内存和已创建的进程数量。
整体步骤与前一个实验相同,这里单独创建2个系统函数用于获取空闲内存数量和进程数量。
在kernel/defs.h中添加定义
uint64 count_free_mem(void);
uint64 count_process(void);
分别在kalloc.c和proc.c中实现
// kernel/kalloc.c
uint64 count_free_mem(void)
{
acquire(&kmem.lock); /* 获取内存管理的锁 */
uint64 mem_bytes = 0; /* 统计空闲页数,乘以页大小即空闲内存字节数 */
struct run *r = kmem.freelist;
while (r)
{
mem_bytes += PGSIZE;
r = r->next;
}
release(&kmem.lock); /* 释放内存管理的锁 */
return mem_bytes;
}
// kernel/proc.c
uint64
count_process(void)
{
uint64 cnt = 0;
for (struct proc *p = proc; p < &proc[NPROC]; p++)
{
if (p->state != UNUSED)
cnt++;
}
return cnt;
}
实现sys_info(其他步骤省略)
// kernel/sysproc.c
uint64
sys_sysinfo(void)
{
uint64 addr;
if(argaddr(0, &addr) < 0)
return -1;
struct sysinfo sinfo;
sinfo.freemem = count_free_mem();
sinfo.nproc = count_process();
if(copyout(myproc()->pagetable, addr, (char*)&sinfo, sizeof(sinfo)) < 0)
return -1;
return 0;
}
Optional challenge exercises TODO
未做
Print the system call arguments for traced system calls TODO
Compute the load average and export it through sysinfo TODO
实验完成,make grade验证:
Score: 34/35