MIT 6.S081 | Lab2: system calls
课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.html 代码地址:https://github.com/36-H/xv6-labs-2020/tree/syscall
#Lab2: system calls
##XV6系统调用全流程 在本次实验开始前,我们可能需要先去了解XV6系统调用的全流程。
user/user.h: //用户态程序调用跳板函数 trace()
user/usys.S: //跳板函数 trace() 使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c //到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c //syscall() 根据跳板传进来的系统调用编号,查询 syscalls[] 表,找到对应的内核函数并调用。
kernel/sysproc.c //到达 sys_trace() 函数,执行具体内核操作
由于内核与用户进程的页表不同,寄存器也不互通,在kernel/syscall.c中,有一系列argraw、argint函数来处理从进程的trapframe中读取用户进程寄存器中的参数。同时,页表的不同也带来了指针也不能直接互通访问的问题,也就是内核不能直接对用户态传来的指针进行解引用,而需要使用在kernel/vm.c中定义的copyin,copyout函数,才能找到对应用户态指针的物理地址
struct proc *p = myproc(); // 获取调用该 system call 的进程的 proc 结构
copyout(p->pagetable, addr, (char *)&data, sizeof(data)); // 将内核态的 data 变量(常为struct),结合进程的页表,写到进程内存空间内的 addr 地址处。
##System call tracing(moderate)
YOUR JOB 在本作业中,您将添加一个系统调用跟踪功能,该功能可能会在以后调试实验时对您有所帮助。您将创建一个新的trace系统调用来控制跟踪。它应该有一个参数,这个参数是一个整数“掩码”(mask),它的比特位指定要跟踪的系统调用。例如,要跟踪fork系统调用,程序调用trace(1 << SYS_fork),其中SYS_fork是kernel/syscall.h中的系统调用编号。如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时打印出一行。该行应该包含进程id、系统调用的名称和返回值;您不需要打印系统调用参数。trace系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。
Step 1 在kernel/proc.h中修改proc结构体的定义,增加trace_mask参数
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
uint64 trace_mask; // Mask for syscall tracing
Step 2 在kernel/proc.c中,设置trace_mask的默认参数
static struct proc*
allocproc(void)
{
struct proc *p;
······
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
//set default value to trace mask
p->trace_mask = 0;
return p;
}
Step 3 调整kernel/proc.c中freeproc函数
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
p->pagetable = 0;
p->sz = 0;
p->pid = 0;
p->parent = 0;
p->name[0] = 0;
p->chan = 0;
p->killed = 0;
p->xstate = 0;
p->state = UNUSED;
p->trace_mask = 0; //free mask
}
Step 4 修改kernel/proc.c中的fork函数,使得子进程可以继承父进程的trace_mask
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
np->parent = p;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);
safestrcpy(np->name, p->name, sizeof(p->name));
pid = np->pid;
np->state = RUNNABLE;
//copy the parent's trace_mask to child
np->trace_mask = p->trace_mask;
release(&np->lock);
return pid;
}
Step 5 创建sys_trace系统调用 在kernel/sysproc.c中
uint64
sys_trace(void){
int mask;
//读取trapframe 获取mask参数
if(argint(0,&mask) < 0){
return -1;
}
myproc()->trace_mask = mask;
return 0;
}
syscall.h 中加入新 system call 的序号
····
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_trace 22
在kernel/syscall.c,用 extern 全局声明新的内核调用函数,并且在 syscalls 映射表中,加入从前面定义的编号到系统调用函数指针的映射
······
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
······
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
在user/usys.pl加入用户态到内核态的跳板函数
entry("sbrk");
entry("sleep");
entry("uptime");
entry("trace");
在user/user.h加入定义,使得用户态程序可以找到这个跳板入口函数。
······
char* sbrk(int);
int sleep(int);
int uptime(void);
int trace(int);
最后在kernel/syscall.c中进行判断
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]();
//判断trace
if((p->trace_mask >> 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;
}
}
我们还需要增加一个数组来存储系统调用的名称字符串,在kernel/syscall.c中
const 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",
};
###Sysinfo(moderate)
YOUR JOB 在这个作业中,您将添加一个系统调用sysinfo,它收集有关正在运行的系统的信息。系统调用采用一个参数:一个指向struct sysinfo的指针(参见kernel/sysinfo.h)。内核应该填写这个结构的字段:freemem字段应该设置为空闲内存的字节数,nproc字段应该设置为state字段不为UNUSED的进程数。我们提供了一个测试程序sysinfotest;如果输出“sysinfotest: OK”则通过。
Step 1 获取空余内存 在kernel/defs.h定义函数count_free_mem(void);
······
void* kalloc(void);
void kfree(void *);
void kinit(void);
uint64 count_free_mem(void);
在kernel/kalloc.c
uint64
count_free_mem(void){
//防竞态
acquire(&kmem.lock);
uint64 free_mem_size = 0;
struct run *r = kmem.freelist;
// 统计空闲页数,乘上页大小 PGSIZE 就是空闲的内存字节数
while(r){
free_mem_size += PGSIZE;
r = r->next;
}
//释放锁
release(&kmem.lock);
return free_mem_size;
}
Step 2 获取运行的进程数 在kernel/defs.h声明函数count_nproc(void);
······
int either_copyout(int user_dst, uint64 dst, void *src, uint64 len);
int either_copyin(void *dst, int user_src, uint64 src, uint64 len);
void procdump(void);
uint64 count_nproc(void);
在kernel/proc.c中实现
uint64
count_nproc(void){
uint64 count= 0;
for(struct proc *p = proc; p < &proc[NPROC]; p++) {
// 判断进程块状态
if(p->state != UNUSED) {
count++;
}
}
return count;
}
Step 3 实现sysinfo系统调用 关键代码: 在kernel/sysproc.c
//为什么不能传参呢,因为这涉及到用户态和内核态的切换,那么从用户态过来的参数,
//我们应该从a0寄存器取出
//也就是 argraw(int n) 当n=0时会取出a0寄存器的值
uint64
sys_sysinfo(void){
//读入用户态参数
uint64 addr;
if(argaddr(0,&addr) < 0){
return -1;
}
struct sysinfo info;
info.freemem = count_free_mem();
info.nproc = count_nproc();
//使用 copyout,通过当前进程的页表,获得进程传进来的指针(逻辑地址)对应的物理地址
//然后将 &info 中的数据复制到该指针所指位置
if(copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0){
return -1;
}
在user/user.h
int sleep(int);
int uptime(void);
int trace(int);
//声明sysinfo 结构,供用户态使用。
struct sysinfo;
int sysinfo(struct sysinfo *);