Lab2: system calls 引导
System call tracing(moderate)
在本作业中,您将添加一个系统调用跟踪功能,该功能可能会在以后调试实验时对您有所帮助。您将创建一个新的trace
系统调用来控制跟踪。它应该有一个参数,这个参数是一个整数“掩码”(mask),它的比特位指定要跟踪的系统调用。例如,要跟踪fork
系统调用,程序调用trace(1 << SYS_fork)
,其中SYS_fork
是kernel/syscall.h中的系统调用编号。如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时打印出一行。该行应该包含进程id、系统调用的名称和返回值;您不需要打印系统调用参数。trace
系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
int i;
char *nargv[MAXARG];
if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
fprintf(2, "Usage: %s mask command\n", argv[0]);
exit(1);
}
if (trace(atoi(argv[1])) < 0) {
fprintf(2, "%s: trace failed\n", argv[0]);
exit(1);
}
for(i = 2; i < argc && i < MAXARG; i++){
nargv[i-2] = argv[i];
}
exec(nargv[0], nargv);
exit(0);
}
仔细阅读trace.c可以发现,它需要的参数是一个int型,猜测是 系统调用号
Add $U/_trace
- 运行
make qemu
,您将看到编译器无法编译***user/trace.c***,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到***user/user.h***,存根添加到***user/usys.pl***,以及将系统调用编号添加到***kernel/syscall.h***,Makefile***调用perl脚本***user/usys.pl,它生成实际的系统调用存根*user/usys.S***,这个文件中的汇编代码使用RISC-V的ecall
指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先*make clean*
,再执行*make qemu*
),就运行trace 32 grep hello README
;但由于您还没有在内核中实现系统调用,执行将失败。 - 在***kernel/sysproc.c***中添加一个
sys_trace()
函数,它通过将参数保存到proc
结构体(请参见***kernel/proc.h***)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在***kernel/syscall.c***中,您可以在***kernel/sysproc.c***中看到它们的使用示例。 - 修改
fork()
(请参阅***kernel/proc.c***)将跟踪掩码从父进程复制到子进程。 - 修改***kernel/syscall.c***中的
syscall()
函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。
第一步
**user/usys.pl 文件中的代码 **
print "# generated by usys.pl - do not edit\n";
print "#include \"kernel/syscall.h\"\n";
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
下文是本人粗浅的理解
第8行是将 系统调用号(估计是头文件中kernel/syscall.h 中定义的 数字) 载入 a7 寄存器
第9行ecall
指令 进行 系统调用
所以第一步 按照要求完成即可
将系统调用的原型添加到***user/user.h***,存根添加到***user/usys.pl***,以及将系统调用编号添加到***kernel/syscall.h***
第二步
查看***kernel/sysproc.c***中的函数
uint64
sys_kill(void)
{
int pid;
if(argint(0, &pid) < 0)
return -1;
return kill(pid);
}
根据 kill 模仿 写出 trace ,用argint读取参数,并保存到proc
结构体
不过首先得添加一个新变量在proc中
第三步
因为打印的该行应该包含进程id、系统调用的名称和返回值
所以应该在fork中传递参数(copy the trace mask from the parent to the child process) 以便 通过掩码显示系统调用的名称
要传递的参数就是在proc添加的新变量
第四步 打印
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace, // trace
};
我们可以先来看一下这个东西,本质上是一个函数指针数组初始化,传入int,调用函数
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](); //系统调用命令 trace
/*
这里写代码
*/
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
所以在看这个函数,我们只需要在里面添加一个 函数名称数组,判断是否是trace调用并按照要求完成输出即可
到这里就完成了 lab2
总结一下
这个东西就是想让我们了解一个系统调用的全过程
画一张图(可能与实际略有出入,不太肯定),应该就是trace的全流程(细节不太肯定)
Sysinfo(moderate)
在这个作业中,您将添加一个系统调用sysinfo
,它收集有关正在运行的系统的信息。
系统调用采用一个参数:一个指向struct sysinfo
的指针(参见***kernel/sysinfo.h***)。
内核应该填写这个结构的字段:freemem
字段应该设置为空闲内存的字节数,nproc
字段应该设置为state
字段不为UNUSED
的进程数。
我们提供了一个测试程序sysinfotest
;如果输出“sysinfotest: OK”则通过。
添加$U/_sysinfotest
- 当运行
make qemu
时,***user/sysinfotest.c***将会编译失败,遵循和上一个作业一样的步骤添加sysinfo
系统调用。要在***user/user.h***中声明sysinfo()
的原型,需要预先声明struct sysinfo
的存在:
struct sysinfo;
int sysinfo(struct sysinfo *);
一旦修复了编译问题,就运行sysinfotest
;但由于您还没有在内核中实现系统调用,执行将失败。
-
sysinfo
需要将一个struct sysinfo
复制回用户空间;请参阅sys_fstat()
(kernel/sysfile.c)和filestat()
(kernel/file.c)以获取如何使用copyout()
执行此操作的示例。 - 要获取空闲内存量,请在 kernel/kalloc.c 中添加一个函数
- 要获取进程数,请在 kernel/proc.c 中添加一个函数
第一步
遵循和上一个作业一样的步骤添加sysinfo
系统调用
1、将系统调用的原型添加到***user/user.h***同时声明 struct sysinfo,
2、存根添加到***user/usys.pl***,
3、以及将系统调用编号添加到***kernel/syscall.h***
4、调整***syscall.c*** 在***kernel/sysproc.c***中添加一个sys_sysinfo()
函数
第二步
sysinfo
需要将一个struct sysinfo
复制回用户空间;参阅sys_fstat()
(kernel/sysfile.c)和filestat()
(kernel/file.c)以获取如何使用copyout()
执行此操作的示例。
看sys_fstat()
和filestat()
的copyout(),可以发现这sys_fstat 系统调用的功能将一个 将一个struct file复制回用户空间,所以理论上sysinfo模仿这个调用写即可,
int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len);
uint64
sys_fstat(void)
{
struct file *f;
uint64 st; // user pointer to struct stat
if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
return -1;
return filestat(f, st);
}
// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
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;
}
第三步
在kernel/kalloc.c中添加一个函数
分配器从哪里获得内存来填充该数据结构呢?它将每个空闲页的
run
结构存储在空闲页本身,因为在那里没有存储其他东西。空闲列表受到自旋锁(spin lock)的保护(kernel/kalloc.c:21-24)。列表和锁被封装在一个结构体中,以明确锁在结构体中保护的字段。 参考书第三章内容函数
kfree
(kernel/kalloc.c:47)首先将内存中的每一个字节设置为1。这将导致使用释放后的内存的代码(使用“悬空引用”)读取到垃圾信息而不是旧的有效内容,从而希望这样的代码更快崩溃。然后kfree
将页面前置(头插法)到空闲列表中:它将pa
转换为一个指向struct run
的指针r
,在r->next
中记录空闲列表的旧开始,并将空闲列表设置为等于r
。
获取空闲内存量,要先读一读kallo.c的变量信息,根据参考书目可以写出函数返回空闲内存量
struct run { //空闲页
struct run *next;
};
struct { //空闲链表
struct spinlock lock;
struct run *freelist;
} kmem;
uint64 freeMem(void) {
struct run *pg = kmem.freelist;
uint64 n = 0;
while(pg) { //计算页的数量
n++;
pg = pg->next;
}
return n * PGSIZE;
}
第四步
在kernel/proc.c中添加一个函数,获取进程数
struct proc proc[NPROC]; //最大进程数为64
这个数组就保存着所有的进程,其中没用的是UNUSED,遍历即可得到进程数
模仿allocproc中寻找UNUSED的代码即可
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
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;
故所写代码如下
uint64 procNum(void) {
uint64 n = 0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state != UNUSED) n++;
release(&p->lock);
}
return n;
}
第五步
再kernel/sysproc.c 按要求实现 函数 sys_sysinfo()
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};
uint64
sys_sysinfo(void) {
uint64 addr;
struct sysinfo info;
struct proc *p = myproc();
if(argaddr(0, &addr) < 0)
return -1;
info.freemem = freeMem();
info.nproc = procNum();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}