MIT6.S081 lab2 system calls

lab2是实现几个系统调用,但此时阅读的资料其实有限,我觉得还是读到手册的第四章会比较好。

System call tracing

这个命令是追踪系统调用的过程。怎么追踪呢?其实这里就要求熟悉xv6发起一个系统调用的过程,仅仅阅读第二章还不能清楚地明白从用户态发起系统调用到内核态的整个过程,但就完成实验来说,靠一些猜测已经足够。

1
2
3
4
5
6
7
8
9
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";
}

通过这段代码我们能够知道是:系统调用的号码预先定义在 kernel/syscall.h中,a7寄存器存储了系统调用的号码,通过ecall进入到内核态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
...
}
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;
}
}

然后经过一些中断调用,我们来到了syscall.c,这里可以得知:通过这个号码查表得知是哪个系统调用,并将返回值存储在 p->trapframe->a0

System call implementations in the kernel need to find the arguments passed by user code. Because user code calls system call wrapper functions, the arguments are initially where the RISC-V C calling convention places them: in registers.

系统调用的参数存储在a0~a6寄存器上。

到这里,我们就能完成实验了。思路:从寄存器中取出用户态出入的掩码参数,然后保存位掩码到当前进程,以后每一个系统调用返回时,都要比较一次掩码来决定是否打印信息。

那为什么执行一个trae设置掩码后,再执行新的系统调用命令后就不会打印信息了呢,掩码又没被清零。是的,确实没有做掩码清零的操作。但shell执行进程的方式是fork+exec,这意味着每执行一个新的命令都会新开一个进程,命令结束后,这个进程也结束了。

Sysinfo

这个实验说实话是有点坑的,因为此时你还不熟悉虚拟内存、进程调度。最大的作用我想就是熟悉copyout函数,从内核拷贝内容到用户。

(1)在/kernel/proc.c文件中

一开始就定义了一个数组

1
struct proc proc[NPROC];

这个数组就保存着所有的进程,所以只要遍历这个数组判断状态就好了,

(2)在/kernel/kalloc.c文件中

定义了一个链表,每个链表都指向上一个可用空间,这个kmem就是一个保存最后链表的变量。

1
2
3
4
5
6
7
struct run {
struct run *next;
};
struct {
struct spinlock lock;
struct run *freelist;
} kmem;

遇到的错误

1
panic: acquire

原因:

1
printf("%s: syscall %s -> %d", p->pid, syscall_names[num], p->trapframe->a0);

在打印进程pid时,格式占位符写成了%s,使得输出以上错误。

详细原因,需要了解设备输出才能排查。

作者

Desirer

发布于

2024-01-05

更新于

2024-02-02

许可协议