前言
Before you start coding, read Chapter 2 of the xv6 book, and Sections 4.3 and 4.4 of Chapter 4, and related source files:
- The user-space code for systems calls is in
user/user.h
anduser/usys.pl
.- The kernel-space code is
kernel/syscall.h
,kernel/syscall.c
.- The process-related code is
kernel/proc.h
andkernel/proc.c
.
本次实验的主要内容是学习系统调用的工作流程,实现两个简单的系统调用。
实验原文:Lab: System calls (mit.edu)
系统调用基本流程
用户空间
根据实验说明的提示,用户进程执行系统调用时相关的代码在user/user.h
中声明,在 user/usys.pl
中定义。
首先可以看到user/user.h
中有很多预设的系统调用。在Clion中,部分系统调用的左边会显示转到定义的双向箭头,但实际上转向的内容是内核空间对应的代码而非用户空间对应的代码。
实际的定义写在 user/usys.pl
中,在make时perl脚本会被编译为汇编文件usys.S
。
打开编译后的usys.S
可以看到以下内容:
# generated by usys.pl - do not edit
#include "kernel/syscall.h"
.global fork
fork:
li a7, SYS_fork
ecall
ret
......
首先执行#include "kernel/syscall.h"
导入系统调用的编号,然后接下来是各个系统调用的定义,每一个系统调用只有三行指令。
以fork举例,.global fork
表示fork是一个全局函数,li a7, SYS_fork
表示将SYS_fork的值加载到trapframe的a7中(li = load immediate),trapframe是在进程陷入内核态时保存寄存器状态的结构。接下来执行ecall
陷入内核态,在内核完成系统调用后执行ret
结束系统调用。
内核空间
根据实验说明的提示,内核空间关于系统调用的代码写在kernel/syscall.h
和kernel/syscall.c
中。
在用户空间执行完ecall
后,CPU陷入内核态,本质上是将当前运行的进程从执行系统调用的用户进程切换到内核进程,这个转换过程将在实验四trap中详细学习。
切换完成后,执行syscall()
处理系统调用。
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;
}
}
从trapframe的a7中获取系统调用编号,然后执行相应的系统调用,并将返回值写入trapframe的a0中,至此完成系统调用,CPU从内核态转回用户态,转换回去的具体过程同样在实验四中学