MIT 6.s081 实验解析——labs1
MIT 6.s081 实验解析——labs2
make qemu
./grade-lab-util <文件名>
//以sleep为例
./grade-lab-util sleep
trace 32 grep hello README
以上述指令来说,这个实验想要实现的效果是,跟踪grep hello README过程中所有的系统调用,其中32为掩码,是要跟踪的系统调用种类,将32变为2进制,根据下图,在要跟踪的系统调用位置置1。
所以先更改kernel/proc.h的进程结构体,新增掩码:
struct proc {
...
int mask;
};
为了实现进程间传递参数,需添加对mask的拷贝,在kernel/proc.c的fork定义中:
int
fork(void)
{
...
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
np->mask = p->mask;
...
}
接下来就是要去完成系统调用 trace 的函数定义,在该系统调用会接收用户态传递的参数,并将其赋值给mask,我们将定义写在kernel/sysproc.c中:
uint64
sys_trace(void){
int n;
argint(0,&n);//接收参数
myproc()->mask = n;//赋给mask
return 0;
}
为了在syscall中能调用sys_trace,我们需要将其函数入口地址存入数组static uint64 (*syscalls[])(void)中,在其末尾加入[SYS_trace] sys_trace,即可。
最后我们要在syscall调用完系统调用后,通过本进程的mask来确认这个系统调用是不是被追踪的,若是则输出相关信息,为了方便信息输出,我们为其定义一个字符串数组,修改的文件为kernel/syscall.c:
char *str[]={
[SYS_fork] "syscall fork",
[SYS_exit] "syscall exit",
[SYS_wait] "syscall wait",
[SYS_pipe] "syscall pipe",
[SYS_read] "syscall read",
[SYS_kill] "syscall kill",
[SYS_exec] "syscall exec",
[SYS_fstat] "syscall fstat",
[SYS_chdir] "syscall chdir",
[SYS_dup] "syscall dup",
[SYS_getpid] "syscall getpid",
[SYS_sbrk] "syscall sbrk",
[SYS_sleep] "syscall sleep",
[SYS_uptime] "syscall uptime",
[SYS_open] "syscall open",
[SYS_write] "syscall write",
[SYS_mknod] "syscall mknod",
[SYS_unlink] "syscall unlink",
[SYS_link] "syscall link",
[SYS_mkdir] "syscall mkdir",
[SYS_close] "syscall close",
[SYS_trace] "syscall trace",
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7; //系统调用号
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num](); //系统调用的返回值
if((p->mask >> num) & 1) //若该系统调用被跟踪
printf("%d: %s -> %d\n",p->pid,str[num],p->trapframe->a0);//输出信息
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
描述一下整个系统调用的流程:核心点就在于可以通过usys.pl文件里对系统调用的定义,使得可以在用户空间调用系统调用。
在用户空间的trace.c定义了trace调用,然后通过ECALL指令触发向内核态的切换,将对应的系统调用号和参数存入寄存器,切换至内核态之后由syscall函数对调用进行响应,然后调用对应的系统调用。处理完成之后将结果返还给用户空间,再切换回用户态,完成一次系统调用。
整体流程和trace差不多,获取非unused的进程数,核心就是遍历进程结构体数组proc,并判断其元素的state。
uint64 get_used_proc(){
struct proc *p;
uint64 n = 0;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state != UNUSED)
n++;
}
return n;
}
获取空闲内存,通过查看文件kernel/kalloc.c可知每个物理内存页的单位是PGSIZE=4096字节, 以一个单链表的形式管理空闲内存页,我们只需遍历该单链表获取空闲页数,每页计一个PGSIZE即可。
uint64 get_free_memory(){
uint64 n=0;
struct run* r = kmem.freelist;
while(r){
r=r->next;
n += PGSIZE;
}
return n;
}
将这些信息填入sysinfo结构体,然后返还给用户空间。
uint64 sys_sysinfo(){
uint64 st;
argaddr(0, &st);//获取从用户空间传入的指针。
struct sysinfo p;//将信息存在结构体中
p.nproc = get_used_proc();
p.freemem = get_free_memory();
if(copyout(myproc()->pagetable, st, (char *)&p, sizeof(p)) < 0)//拷贝回用户空间
return -1;
return 0;
}