- 阅读xv6 book 章节2、4.3、4.4
- 熟悉系统调用用户空间代码 user/user.h 和 user/usus.pl
- 熟悉系统调用内核空间代码 kernel/syscall.h 和 kernel/syscall.c
- 熟悉进程相关代码 kernel/proc.h 和 kernel/proc.c
实现一个系统调用 trace(uint64 mask)
若mask的二进制表示中第n位为1, 则表示第n号系统调用将被追踪,如trace(1<
则表示追踪 系统调用fork 追踪打印 PID: sys_$name(arg0) -return_value
完成trace的编写后,在xv6中应该看到如下的输出
[cs@localhost xv6-labs-2020] $ make qemu
……
xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
$
$ trace 32 grep hello README
3: sys_read(3) -> 1023
3: sys_read(3) -> 966
3: sys_read(3) -> 70
3: sys_read(3) -> 0
接下来根据 hints 一步一步实现
在Makeifle 中的UPROGS下新增 $U/_trace
在系统调用相关部分 user/user.h 、 user/usus.pl、 kernel/syscall.h 、 kernel/syscall.c 新增关于trace的声明
/* user/user.h */
int trace(int);
/* user/usys.pl */
entry("trace");
/* kernel/syscall.h */
#define SYS_trace 22
/* kernel/syscall.c */
extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
...
[SYS_trace] sys_trace,
};
/* kernel/sysproc.h */
uint64
sys_trace(void)
{
int n;
//获取追踪的mask
if(argint(0, &n) < 0)
return -1;
//将mask保存在本进程的proc中
myproc()->trace_mask = n;
return 0;
}
/* kernel/proc.h */
struct proc {
struct spinlock lock;
...
//因为系统调用只有二十几个所以用4字节的int即可全部覆盖
int trace_mask; // Mask for trace
};
/* kernel/proc.c */
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
//copy trace mask
np->trace_mask = p->trace_mask;
...
}
/* kernel/syscall.c */
//创建一个字符串数组来储存系统调用名
static char syscall_name[23][16] = {"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();
//系统调用号储存在a7
num = p->trapframe->a7;
//第一个参数储存在a0
int first_arg = p->trapframe->a0;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
//返回值储存在a0
p->trapframe->a0 = syscalls[num]();
//位操作判断mask是否覆盖了当前调用号
if(p->trace_mask > 0 && (p->trace_mask&(1<<num)))
{
printf("%d: sys_%s(%d) -> %d\n", p->pid, syscall_name[num-1], first_arg, p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
实现一个系统调用 sysinfo() 返回系统当前 可用内存、state为UNUSED的进程数、当前进程空闲的文件描述符数量
接下来根据hints一步一步实现
同上一个实验一样增加相应的函数声明
在 kernel/user.h 中提前声明 struct sysinfo
参考 kernel/sysfile.c/sys_fstat 和 kernel/file.c/filestat 使用 copyput() 将内核数据传输到用户态
/* kernel/sysproc.c */
uint64
sys_sysinfo(void)
{
uint64 addr;
if(argaddr(0, &addr) < 0)
return -1;
struct sysinfo info;
info.freemem = get_free_mem();
info.nproc = get_proc_num();
info.freefd = get_free_fd();
//copyout 参数:进程页表,用户态目标地址,数据源地址,数据大小
//返回值:数据大小
if(copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
/* kernel/kalloc.c */
/*
观察kalloc.c可以得知空闲内存由链表 kmem.list 给出,每一个节点代表一页内存
每页内存大小由 宏定义PGSIZE 给出
注意锁的获取和释放
*/
uint64
get_free_mem(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
int num = 0;
while(r)
{
++num;
r = r->next;
}
release(&kmem.lock);
return num * PGSIZE;
}
/* kernel/proc.c */
//sysinfo
int
get_proc_num(void){
struct proc *p;
int num = 0;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
++num;
}
release(&p->lock);
}
return num;
}
uint64
get_free_fd(void)
{
uint64 num = 0;
int fd;
struct proc *p = myproc();
for(fd = 0; fd < NOFILE; fd++){
if(p->ofile[fd] == 0){
++num;
}
}
return num;
}
/* kernel/defs.h */
/*
在defs.h中增加三个函数的声明以便sysinfo调用
*/
uint64 get_free_fd(void);
uint64 get_free_mem(void);
int get_proc_num(void);
注意到其中的脚本代码
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n"; //将系统调用号放入a7寄存器
print " ecall\n"; //调用ecall进入内核
print " ret\n";
}
它说明了我们实现的系统调用如何在汇编中调用
系统调用通过寄存器传递参数,储存在用户态的上下文 trapframe 中