0%
Theme NexT works best with JavaScript enabled
lab1 syscall
作业链接
https://pdos.csail.mit.edu/6.828/2020/labs/syscall.html
xv6-RISCV 的系统调用
首先用户态程序通过系统调用 ecall
进入内核态,硬件做好一些简单的环境配置,然后进入
trampoline.S/uservec()
程序段,接着通过
syscall()
函数选择具体的系统调用,接着是硬件实现或者软件实现(我们这里做的功能)具体功能。
实验 1: 添加 trace 系统调用
1. 配置
在 Makefile
中把 $U/_trace
添加到
UPROGS
里
2. 用户态准备工作
2.1 user/user.h
2.2 user/usys.pl
usys.pl
的作用是生成 usys.S
汇编代码
生成用户态的系统调用汇编代码
1 2 3 4 5 .global trace trace: li a7, SYS_trace ecall ret
2.3 kernel/syscall.h
在 kernel/syscall.h
中添加系统调用号
3. 内核代码准备工作
3.1 kernel/syscall.c
1 2 3 4 5 6 extern uint64 sys_trace (void ) ;static uint64 (*syscalls[]) (void ) = { [SYS_trace] sys_trace, };
3.2 kernel/sysproc.c
1 2 3 4 5 6 7 8 uint64 sys_trace (void ) { struct proc *p = myproc(); printf ("syscall: trace(pid: %d)\n" , p->pid); return 0 ; }
1 $ trace 32 grep hello README
4. 内核代码功能实现
4.1 实现方式
在调用 trace
的时候,在进程的 proc
结构中保存一个新的变量
trace_mask
,从而在该进程进行系统调用的产生系统调用的输出
4.2 kernel/proc.h
4.3 kernel/sysproc.c
1 2 3 4 5 6 7 8 9 uint64 sys_trace (void ) { struct proc *p = myproc(); int trace_mask; if (argint(0 , &trace_mask) < 0 ) return -1 ; p->trace_mask = trace_mask; return 0 ; }
4.4 kernel/syscall.c
1 2 3 4 5 static char * syscall_names[] = { [SYS_fork] "fork" , [SYS_trace] "trace" , };
1 2 3 4 5 6 7 8 9 10 11 12 13 void syscall (void ) { if (num > 0 && num < NELEM(syscalls) && syscalls[num]) { p->trapframe->a0 = syscalls[num](); if ((1 << num) & p->trace_mask) { printf ("%d: syscall %s -> %d\n" , p->pid, syscall_names[num], p->trapframe->a0); } } else { } }
4.5 kernel/proc.c
1 2 3 4 5 6 7 int fork (void ) { np->trace_mask = p->trace_mask; release(&np->lock); return pid; }
freeproc() 函数中要把 p->trace_mask 置为 0
5. 一些问题
怎么保证了初始化的结果为全 0
怎么保证 trace_mask 初始化的时候为 0
实验 2: 添加 sysinfo 系统调用
1. 配置
在 Makefile
中把 $U/_sysinfotest
添加到
UPROGS
里
2. 用户态准备工作
2.1 user/user.h
1 2 struct sysinfo ;int sysinfo (struct sysinfo *) ;
2.2 user/usys.pl
2.3 kernel/syscall.h
在 kernel/syscall.h
中添加系统调用号
3. 内核代码准备工作
3.1 kernel/syscall.c
1 2 3 4 5 6 extern uint64 sys_sysinfo (void ) ;static uint64 (*syscalls[]) (void ) = { [SYS_sysinfo] sys_sysinfo, };
3.2 kernel/sysproc.c
1 2 3 4 uint64 sys_sysinfo (void ) { return 0 ; }
4. 内核代码功能实现
4.1 kernel/sysproc.c
主要逻辑就是下列代码注释中所讲的
通过调用 kernel/kalloc.c
中的函数 get_freemen()
得到空闲的内存大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "sysinfo.h" uint64 sys_sysinfo (void ) { uint64 addr; if (argaddr(0 , &addr) < 0 ) return -1 ; struct sysinfo info ; info.freemem = get_freemen(); info.nproc = get_nproc(); struct proc * p = myproc(); if (copyout(p->pagetable, addr, (char *)(&info), sizeof (info)) < 0 ) return -1 ; return 0 ; }
4.2 kernel/defs.h
1 2 uint64 get_freemen (void ) ; uint64 get_nproc (void ) ;
4.2 kernel/kalloc.c
实现函数 get_freemen(),获取当前空闲内存的字节数
1 2 3 4 5 6 7 8 9 10 11 uint64 get_freemen () { uint64 num = 0 ; struct run *r ; r = kmem.freelist; while (r){ ++num; r = r->next; } return num * PGSIZE; }
struct kmem 记录当前进程的空闲内存信息
struct run 是一个链表,记录空闲的内存
1 2 3 4 5 6 7 8 struct run { struct run *next ; }; struct { struct spinlock lock ; struct run *freelist ; } kmem;
4.3 kernel/proc.c
实现函数 get_nproc(),获取当前状态为 UNUSED
的进程数
1 2 3 4 5 6 7 8 9 10 11 12 13 uint64 get_nproc (void ) { uint num = 0 ; uint i = 0 ; for (; i < NPROC; ++i) { acquire(&(proc[i].lock)); if (proc[i].state != UNUSED) { ++num; } release(&(proc[i].lock)); } return num; }
实验结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ make qemu-gdb trace 32 grep: OK (11.5s) == Test trace all grep == $ make qemu-gdb trace all grep: OK (1.7s) == Test trace nothing == $ make qemu-gdb trace nothing: OK (1.8s) == Test trace children == $ make qemu-gdb trace children: OK (21.7s) == Test sysinfotest == $ make qemu-gdb sysinfotest: OK (5.1s) == Test time == time: OK Score: 35/35
遇到的困难以及收获
做完这个 lab 之后对系统调用的认识更加深入了
困难主要是在于第一次写
lab,刚开始读源代码还是有点吃力,但是读完了收获还是很大的
对课程或 lab 的意见和建议
mit 的 lab 想到写的很清晰,循循善诱,让我们一步一步深入 xv6
的内核设计
希望中文版也能跟上,毕竟我们也是自己开课的
参考文献
https://pdos.csail.mit.edu/6.828/2019/xv6/book-riscv-rev0.pdf