左岸的一座白色环形阶梯
浪人正在用和弦练习忧郁
晨曦下的少女听着吉他旋律
在许愿池边巴洛克式的叹息
——许愿池的希腊少女
完整代码见:SnowLegend-star/6.s081 at syscall (github.com)
System call tracing (moderate)
这个实验要求我们跟踪系统调用。
感觉实验说明对mask的解释有点语焉不详,研究了好一番才明白mask的作用:把mask转换为32-bit的二进制数n,第ni 位为1就跟踪编号为i的syscall。而且说明里面应该还有个错误,int类型的最大正数应为“2147483647”,而实验说明里搞了个这数,我也是看半天没反应过来,可恶。
读懂了实验要求,就可以着手实验部分了。根据hints,我们可以大致设计如下实现流程:
①确定syscall函数名sys_trace
②在syscall.h中给sys_trace设定一个系统调用编号
③在syscall.c中注册sys_trace,同时建立一个指针数组*syscallsName[]来记录系统调用的函数名称。
④在user.h中定义函数trace(int),在usys.pl中设置sys_trace的调用入口。
⑤完成sys_trace在各个头文件的声明之后,在struct proc中添加一个成员mask,从而能够使父进程将mask传递给子进程。
确定好大致流程后,进行sys_trace()的实现工作。这里说一点,貌似系统调用函数都是不用传参的,大概是为了保证操作系统的isolation。用户空间与内核空间的传参会有专门的实验函数维护。那么问题来了,我们得想办法从用户空间吧mask拿过来。我们注意到hints里有这句话
The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c
结合xv6文档的4.4节
The functions argint, argaddr, and argfd retrieve the n ’th system call argument from the trap frame as an integer, pointer, or a file descriptor.
看完上面两点之后就大致有点头绪了,然后我们在sysproc.c文件里面浏览一番,看看有没有哪些函数调用了argint()这种函数。可以发现调用形式都是argint(0,&argument)。这就好办了,我们来个argint(0,&mask)应该就可以从用户空间里获得mask。Ok,现在有了mask这个参数,剩下的就是把它传递给当前进程了。
还是观察sysproc.c内部的那些函数,用相对敏锐的注意力察觉到myproc()可能有点作用。浏览一番定义果真是获取当前进程的函数。那么sys_trace到这里也就结束了。最后在syscall.c函数里面修改打印内容即可。当然,这里得用上a0和a7这两个寄存器,xv6文档内部也有提到,就略过不表了。
此时再顺便把进程相关的结构体浏览一番(proc.h),对进程的处理有个大致的了解后为第二个实验打下基础。
sys_trace()如下
//跟踪系统调用情况
uint64
sys_trace(void){int mask;if(argint(0,&mask)<0)return -1;myproc()->mask=mask;return 0;
}
Sysinfo (moderate)
完成这个实验之前也可以自己设计个大致流程,和sys_trace比较相似,就不再赘述了。我们观察struct sysinfo这个结构体,可以确定主要任务就是获取它其中的两个成员: freemen和nproc。这里定义两个函数kfmstat()和sum_USED来分别统计系统剩余内存和process->state不为UNUSED。
kfmstat()根据hints可以在kalloc.c中实现。在完成它之前,我们还是老样子观察下kalloc.c里的其他函数是怎么实现的。可以发现系统用freelist来维护内存分配。那kfmstat()的实现核心也就呼之欲出了——遍历一遍freelist。
Tips: freelist中,每个内存节点的大小都是4096B
收集进程数则是在proc.c中实现,这里还有个现成的函数procdump()统计系统状态。其实偷懒点直接改造procdump()都行。由于太过简单就略过不表。
这个实验最难的地方是在sys_sysinfo自己的函数体。在sys_sysinfo()内部调用完上述两个函数初始化sysINFO后,我们要怎么把这个结构体给传递到用户空间呢?
我们按照hints查看sys_fstat() (kernel/sysfile.c)和filestat() ( kernel/file.c ),可以发现一条及其重要的注释
先通过argaddr()函数得到用户空间
sysinfo(info)
传过来的这个地址,存放在uint64类型的变量中。(注意,用户空间传过来的地址一定要放在uint64类型的变量中,不可以放在struct sysinfo类型中)然后再使用copyout函数把内容从内核空间拷贝进用户空间中。
我们进入vm.c中查看下copyout()的实现方法
// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
接下来就可以顺便将内核空间中的sysINFO传递出去了。
在完成这个lab的时候,我突然想到一个问题,用户空间中使用的函数是trace(int n),但是内核空间使用的却是sys_trace(void),那用户空间传递的这个n到底是怎么被内核接收到的呢?
其实这个问题的答案在上面已经提到过了,sys_trace(void)不能直接通过传参的方式接收来自用户空间的参数,是通过argint()等函数来接收来自用户空间的参数。
最后,我有一个问题始终没有得到解决。当我使用argaddr来接收参数时
argaddr(1,&sysINFO_user)
如果把第一个参数设置为1则会报错
我的猜测由于argaddr第一个参数其实是和寄存器相关的,传参时系统会默认按序使用寄存器。那么来自用户空间的sysinfo地址就被存在第一个寄存器a0里面了,而不是存在第二个寄存器a1内部。
见Xv6-2020文档的4.4节
The functions argint, argaddr, and argfd retrieve the n ’th system call argument from the trap frame as an integer, pointer, or a file descriptor
sys_info()如下
uint64
sys_sysinfo(void){struct sysinfo sysINFO;struct proc *p=myproc();uint64 sysINFO_user; //user pointer to struct sysinfo sysINFO.freemem=kfmstat(); //刚才用sys_checkmem是有问题的sysINFO.nproc=sum_UNUSED();// printf("In kernel, available memory: %dB\n", sysINFO.freemem);// printf("In kernel, UNUSED process: %d\n", sysINFO.nproc);if(argaddr(0,&sysINFO_user)<0)return -1;// sysINFO_user=(struct sysinfo)sysINFO_user;// printf("The sysinfo from user space: \n")// printf("available memory: %d",sysINFO_user);if(copyout(p->pagetable, sysINFO_user, (char *)&sysINFO, sizeof(sysINFO)) < 0)return -1;return 0;
}