trace
在 usys.pl 中,有用户态到内核态的跳板函数:
| 12
 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
的用户态跳板函数:
| 12
 3
 4
 
 | trace:		# 定义用户态跳板函数li a7, SYS_trace	# 将系统调用 id 存入 a7 寄存器
 ecall				# ecall,调用 system call ,跳到内核态的统一系统调用处理函数 syscall()  (syscall.c)
 ret
 
 | 
项目代码中系统调用流程:
| 12
 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:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | voidsyscall(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).
| 12
 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()实现。如下所示
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | uint64sys_fstat(void)
 {
 struct file *f;
 uint64 st;
 
 if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
 return -1;
 return filestat(f, st);
 }
 
 | 
| 12
 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"
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | uint64sys_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的代码
空闲内存是使用一个链表来维护的:
| 12
 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字节:
| 12
 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;
 }
 
 | 
实现:
| 12
 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
参考这个函数的实现:
| 12
 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来判断进程是否空闲
程序实现:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | uint64getnproc(){
 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;
 }
 
 | 
