trace
在 usys.pl 中,有用户态到内核态的跳板函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
entry("fork"); entry("exit"); entry("wait"); entry("pipe"); entry("read"); entry("write"); entry("close"); entry("kill"); entry("exec"); entry("open"); entry("mknod"); entry("unlink"); entry("fstat"); entry("link"); entry("mkdir"); entry("chdir"); entry("dup"); entry("getpid"); entry("sbrk"); entry("sleep"); entry("uptime"); entry("trace");
|
这个脚本在运行后会生成 usys.S 汇编文件,里面定义了每个 system call
的用户态跳板函数:
1 2 3 4
| trace: # 定义用户态跳板函数 li a7, SYS_trace # 将系统调用 id 存入 a7 寄存器 ecall # ecall,调用 system call ,跳到内核态的统一系统调用处理函数 syscall() (syscall.c) ret
|
项目代码中系统调用流程:
1 2 3 4 5
| 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
文件中的argint
,argaddr
,argstr
等函数能够获取到
bug2:
测试trace 2147483647 grep hello README
的时候不显示trace的系统调用
主要的问题出在,调用trace的时候执行到打印的时候可以看出mask没有传进来比较滞后还是0。
继续dbug发现其实trace是执行了的也就是说应该是改了mask以后才执行到系统调用这段来:

然后发现:
p->trapframe->a0 = syscalls[num]();
这句话一定要放在uint64 trace_mask= p->trace_mask;
的前面就可以修复
主要原因:每一个系统调用有一个返回值,这个返回值保存在trapframe->a0
中,如果系统调用号未知,就保存-1
最后systemcall:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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](); uint64 trace_mask= p->trace_mask; if (1 & (trace_mask>>num)) { 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; } }
|
Sysinfo
实现功能:添加 sysinfo
系统调用,
用于收集系统运行时候的信息.系统调用的入参是一个结构体指针:
struct sysinfo
(see kernel/sysinfo.h
).
1 2 3 4 5
| struct sysinfo { uint64 freemem; uint64 nproc; };
|
内核程序负责填写这个结构体: freemem
字段被设置成内存空闲的比特数, nproc
字段被设置成 the number
of processes whose state
is not UNUSED
首先先在内核实现这个系统中断(流程和上一个trace实现过程一致)
sysinfo需要负责将struct sysinfo结构体复制回用户空间;
这一过程的实现可以参考sys_fstat()
(kernel/sysfile.c
) 和filestat()
(kernel/file.c
) 中的实现过程,主要通过调用
copyout()
实现。如下所示
1 2 3 4 5 6 7 8 9 10
| uint64 sys_fstat(void) { struct file *f; uint64 st;
if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0) return -1; return filestat(f, st); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
int filestat(struct file *f, uint64 addr) { struct proc *p = myproc(); struct stat st; if(f->type == FD_INODE || f->type == FD_DEVICE){ ilock(f->ip); stati(f->ip, &st); iunlock(f->ip); if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) return -1; return 0; } return -1; }
|
首先模仿上面接收传入的结构体指针argaddr(1, &st)
.
首先获取用户传递给系统调用的参数放入addr
中(用户地址空间地址),然后在内核空间中申请一个struct sysinfo
,分别调用getfreemem()
和getnproc()
函数填充该结构体相应字段之后,调用copyout()
将内核空间的内存复制到用户空间的地址中
注意包含头文件:#include "sysinfo.h"
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| uint64 sys_sysinfo(void) { uint64 addr; if (argaddr(0, &addr) < 0) return -1; struct sysinfo info; info.freemem = getfreemem(); info.nproc = getnproc(); struct proc *p = myproc(); if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) return -1; return 0; }
|
- To collect the amount of free memory, add a function to
kernel/kalloc.c
我们看一下kalloc.c的代码
空闲内存是使用一个链表来维护的:
1 2 3 4 5 6 7 8
| struct run { struct run *next; };
struct { struct spinlock lock; struct run *freelist; } kmem;
|
为了获取系统空闲内存字节数,需要遍历空闲内存链表,参考kalloc函数:我们可以看出memset((char*)r, 5, PGSIZE);
中,一个链表指针指向的空闲内存的单位是PGSIZE:

所以我们想得到空闲内存的比特数要乘4096。每遍历一个节点就增加一个PGSIZE
字节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void * kalloc(void) { struct run *r;
acquire(&kmem.lock); r = kmem.freelist; if(r) kmem.freelist = r->next; release(&kmem.lock);
if(r) memset((char*)r, 5, PGSIZE); return (void*)r; }
|
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| uint64 getfreemem(){ struct run *r; uint64 freeret=0; acquire(&kmem.lock); r = kmem.freelist; while(r){ freeret++; r=r->next; } release(&kmem.lock); freeret*=PGSIZE; return freeret; }
|
- To collect the number of processes, add a function to
kernel/proc.c
实现getnproc()可以参考 kernel/proc.c
参考这个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| allocproc(void) { struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == UNUSED) { goto found; } else { release(&p->lock); } } return 0; ... }
|
可以看出,遍历进程池,通过p->state == UNUSED
来判断进程是否空闲
程序实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| uint64 getnproc(){ struct proc *p; int cnt=0; for(p=proc;p<&proc[NPROC];p++){ acquire(&p->lock); if(p->state!=UNUSED){ cnt++; } release(&p->lock); } return cnt; }
|
