实验二主要涉及对系统函数调用过程的理解以及尝试自己手动添加系统调用。首先需要大致了解一下xv6系统调用的过程,这里以fork为例:
根据这个过程,就很容易完成本次实验了。
该实验需要打印其他系统调用的信息。根据上面的分析和文档说明,首先需要给user.h、usys.pl(用来生成usys.S的辅助脚本)和syscall.h添加对应的函数的系统调用号,然后给syscall.c的系统调用数组添加对应的函数指针和函数头,在sysproc.c添加对应的函数实现,sysproc.c里主要是接收参数并给proc结构体复制,具体代码如下:
uint64 sys_trace(void) {
int mask;
if (argint(0, &mask) < 0) return -1;
myproc()->mask = mask; return 0;
}
这样trace函数调用就完成了,但实际功能并没有实现,真正打印其他系统调用信息的操作应该在syscall.c中进行,在syscall函数的末尾(其他系统调用结束后)输出信息:
if (p->mask & (1 << num))
printf("%d: syscall %s -> %d\n", p->pid, callnames[num - 1], p->trapframe->a0);
添加系统调用的过程和上一个任务类似,这里就不提了。具体sys_sysinfo函数的实现需要首先获得所需要的信息,然后获得传进系统调用函数的地址参数,将获得的信息复制到这个地址,由于是涉及内核态和用户态的地址转换,所以需要使用copyout函数:
uint64 sys_sysinfo(void) {
uint64 ip; struct sysinfo si;
si.freemem = freemem();
si.nproc = nproc();
if (argaddr(0, &ip) < 0) return -1;
if(copyout(myproc()->pagetable, ip, (char *)&si, sizeof(si)) < 0)
return -1;
return 0;
}
关于freemem函数,这里就需要理解kalloc.c的内容了,这个文件主要进行物理内存的管理,使用一个链表来管理空闲空间,而且一个链表节点就代表一页,所以遍历整个链表,节点数乘上内存页的大小就是空闲空间:
uint64 freemem(void) {
struct run *r = kmem.freelist; uint64 n = 0;
for (; r != 0; r = r->next) n += 4096;
return n;
}
关于nproc函数,需要理解proc.c的内容了,这个文件主要进行进程的管理,xv6用一个数组来维护所有的进程,不管是在运行的、在等待的还是没被分配的。所以nproc函数只需要遍历这个数组数清有多少没被分配的进程就行了:
uint64 nproc(void) {
struct proc* p; uint64 n = 0;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state != UNUSED) n++;
release(&p->lock);
}
return n;
}
总结一下,这次实验我觉得设计得十分优秀,直击系统调用的要点,而且也稍微涉及了物理内存管理和进程管理的内容,为后面的实验打下基础。实际上这次实验是2020年版本的6.S081实验才有的,以前的第二次实验都是写shell,私以为这个系统调用的实验远强于写shell的实验,shell实验的主要技术点和实验一基本上是重复的,而主要工作量则集中在字符串操作等繁琐的地方,感觉这样就舍本逐末了,我理想中优秀的实验就应该是事半功倍,用最小的工作量,最清晰的操作指南帮助学生掌握最多的知识。