MIT 6.S081 Fall 2020 Lab 2
System calls
学!
System call tracing
自己写一个系统调用, 能够追踪打印系统调用. 看到的时候我是蒙的, 这咋追踪. 后来看提示, 原来是修改 syscall()
函数. kernel/syscall.c
中的 syscall 函数长这样:
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]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
num 赋值为寄存器 a7 的值, 也就是系统调用号. 然后判断是否合法, 最后调用 syscallsnum. syscalls 是函数指针数组.
在这句话结束后, 打印信息就可以了.
首先加入一个系统调用:
- 在
kernel/syscall.h
中加入#define SYS_trace 22
- 在
kernel/syscall.c
中加入系统调用 sys_trace 的声明extern uint64 sys_trace(void);
; 在 syscalls 数组中加入[SYS_trace] sys_trace
的声明 - 在
user/user.h
中加入函数声明int trace(int);
- 在
user/usys.pl
中加入入口声明entry("trace");
一句话, 就是出现一堆系统调用的地方都对着抄加一个 trace 即可.
根据提示, 我们要把 mask 写到 proc 结构体中, 这样 fork 的时候就可以进行传递了. 于是在 kernel/proc.h
的 struct proc
最后加一个变量 int sys_trace_mask;
找到 kernel/proc.c
中的 fork 函数, 将子进程的 proc 结构体中的 sys_trace_mask
赋值为父进程的.
由于需要打印系统调用的名称, 所以我们还要创建一个字符串数组. 在 kernel/syscall.c
中写入如下内容:
const char *names[] = { "",
"fork", "exit", "wait", "pipe",
"read", "kill", "exec", "fstat",
"chdir", "dup", "getpid", "sbrk",
"sleep", "uptime", "open", "write",
"mknod", "unlink", "link", "mkdir",
"close", "trace"
};
这里用的就是系统调用号作下标.
把 syscall 函数改成这样:
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]();
if (p->sys_trace_mask & (1 << num))
printf("%d: syscall %s -> %d\n",
p->pid, names[num], p->trapframe->a0);
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
就可以实现打印 mask 所指示的系统调用过程了. 由于 proc 是全局变量, 所以初始的 mask 是 0. 这个打印就不会执行.
最后差一个修改 mask, 也就是 sys_trace 的功能. 在 kernel/sysproc.c
中写入如下函数即可:
uint64
sys_trace(int)
{
int mask;
if(argint(0, &mask) < 0)
return -1;
myproc()->sys_trace_mask = mask;
return 0;
}
Sysinfo
写一个系统调用, 返回系统信息. kernel/sysinfo.h
如下:
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};
先按照上一个任务的方法加入一个 sysinfo 系统调用. 这里在 kernel 文件夹下再写一个 sysinfo.c
来实现这个系统调用. 在 Makefile 的 OBJS
加入 $K/sysinfo.o \
.
sysinfo.c
如下:
#include "types.h"
#include "param.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "defs.h"
#include "sysinfo.h"
extern int freemem(void);
extern int proc_number(void);
uint64
sys_sysinfo(void)
{
struct proc *p = myproc();
struct sysinfo info;
uint64 addr; // user pointer to struct sysinfo
if (argaddr(0, &addr) < 0)
return -1;
info.freemem = freemem();
info.nproc = proc_number();
if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
需要注意头文件的顺序… 这还是第一次了解这玩意. 展开宏的时候是一个一个展开的, 所以如果有一个头文件中的 type 没有在之前的头文件中定义, 那么就会报错.
然后说一下 copyout. 由于系统调用已经陷入了内核态, 而传入的地址是某个程序的虚拟地址, 所以需要程序的页表来计算得到物理地址, 再进行写入 (这里是 copy 的方式). 千万不能认为传入的地址就是用户态的某一个变量的地址, 然后直接通过指针操作改内容.
你问我怎么知道要这样写? 答案是看 sys_fstat()
.
系统调用的问题解决了, 可以先随便赋值, 测试一下, 没问题再继续.
然后是写 freemem 和 proc_number.
在 kernel/kalloc.c
中加入如下代码:
int
freemem(void)
{
struct run *r;
int mem = 0;
for (r = kmem.freelist; r; r = r->next)
mem += PGSIZE;
return mem;
}
通过翻阅 kalloc.c
之前的代码可以知道, kmem 这个结构里维护了一个空闲内存链表, 单位是页大小. 所以遍历累加即可.
在 kernel/proc.c
中加入如下代码:
int
proc_number(void)
{
struct proc *p;
int n = 0;
for (p = proc; p < &proc[NPROC]; p++)
n += p->state != UNUSED;
return n;
}
通过翻阅之前的代码可以知道, proc 数组维护了所有的进程结构, 那么只需要遍历, 统计 state 不为 UNUSED 的数量就行. 当然更优秀的做法是直接用一个变量维护进程数.
Optional challenge exercises
trace syscall arguments
额外记录 syscall 的参数个数, 然后去翻寄存器就行. 不写了.
sysinfo load average
TODO. 连负载是啥我都不知道… 再说, 先学之后的, 以后回来补.