源码阅读
在开始coding前,应该先阅读以下源码:
1. user/user.h and user/usys.pl
这是用户空间的代码,与系统调用相关。
user.h内声明了一些系统调用的接口。
usys.pl文件是perl脚本语言文件,用作生成usys.s汇编文件
print "# generated by usys.pl - do not edit\n";
# 包含文件kernel/syscall.h 定义了各个系统调用的编号
print "#include \"kernel/syscall.h\"\n";
# Generate usys.S, the stubs for syscalls
sub entry {
# 接受一个参数${name}, shift表示从参数列表的开头取出并返回第一个元素,并将该元素从原数组或参数列表中删除
my $name = shift;
# 定义一个全局符号,添加到全局符号表中,以便在其它文件中能够调用
print ".global $name\n";
# 表示一个代码段的开始,名字为${name}
print "${name}:\n";
# 将SYS_${name}对应的值存在a7寄存器中
print " li a7, SYS_${name}\n"; //
# 调用ecall,执行a7寄存器保存编号对应的系统调用
print " ecall\n";
# 返回到调用者
print " ret\n";
}
entry("fork");
entry("exit");
entry("wait");
...
注意到一行注释
# Generate usys.S, the stubs for syscalls
注: stub意为“替代物”,是软件开发中的常用词语,在不同的语境中有不同的意义:
- 这里指的是系统调用的替代物(system call stubs),我们在用户空间中调用的“系统调用”并非真实的系统调用,真实的系统调用需要通过ecall来调用。
- 在软件测试中,某系统存在外部依赖,而这外部依赖不容易获取或获取成本高时(例如,需要收费的API),则在软件进行测试时,先使用本地的模块进行替代,这个模块就叫做stub。
2.kernel/syscall.h and kernel/syscall.c
这是内核空间的代码,与系统调用相关。
syscall.h 定义了一些系统调用的编号,例如SYS_fork是1
syscall.c 定义了一些从用户空间取出系统调用参数的函数 ,主要在kernel/proc.c中使用
// 提取地址
int fetchaddr(uint64 addr, uint64 *ip)
// 提取字符串
int fetchstr(uint64 addr, char *buf, int max)
// 获取第n个寄存器中的值
static uint64 argraw(int n)
// 获取第n个寄存器中的值(根据值的数据类型不同,选择下面三者其一),保存在ip对应的地址块中
int argint(int n, int *ip)
int argaddr(int n, uint64 *ip)
int argstr(int n, char *buf, int max)
// 函数指针数组,使用初始化特定元素的数组初始化方式。注意使用了static关键字,本数组只能在本文件中使用
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, };
void syscall(void) {
int num;
// myproc()返回当前进程结构体的指针
struct proc *p = myproc();
// 获取保存在trapframe中的寄存器a7的内容
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// 调用系统调用 并 将系统调用的返回值保存在a0寄存器中
p->trapframe->a0 = syscalls[num]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
3.kernel/proc.h and kernel/proc.c
这是内核空间的代码,与进程相关。
proc.h的主要内容如下:
// 用作在内核上下文切换时保存寄存器的内容
struct context
// 是每个CPU的状态
struct cpu
// 外部声明struct cpu
extern struct cpu cpus[NCPU]
// 陷入帧,每个进程都会保存一份
struct trapframe
// 进程,其中部分变量在使用时需要持有锁,而另外一部分不需要
struct proc
// 表示进程状态。
enum procstate
proc.c主要包含一些与进程相关的函数:
// 进程初始化
void procinit(void)
// 返回正在本cpu上运行的进程的结构题指针
struct proc* myproc(void) {
push_off();
// 获得当前cpu的
struct cpu *c = mycpu();
struct proc *p = c->proc;
pop_off();
return p;
}
// pid分配
int allocpid()
// 释放进程
static void freeproc(struct proc *p)
// 创建用户页表,
pagetable_t proc_pagetable(struct proc *p)
注:"trampoline"意为跳板,在Linux内核中,trampoline通常指的是一个小段代码,作为一个跳板来执行其他函数或指令。在Lecture6中有所解释。
System call tracing (moderate)
在本文第一部分源码阅读中,我们静态地理解系统调用的原理,而在Lab 2的两个assignment中,我们将尝试以动态的视角去理解系统调用的过程,这可能可以很好地帮助我们理解用户程序是如何调用系统调用的。
trace系统调用的作用是:在调用其它系统调用之前,在本进程的结构体struct proc中记录mask的值,并通过fork()传递给所有子进程。后续执行其它系统调用时,syscall()中根据mask的值,输出我们期望观测的系统调用的信息。而不调用trace时,struct proc中mask值默认为0,即所有系统调用的信息都不输出。
在user/trace.c中,使用在user/user.h中声明的system call stub接口(关于stub的解释见本文第一部分源码阅读的第二点的最后):
trace(atoi(argv[1]))
在user/usys.S中我们可以看到这个stub的具体实现:
.global trace
trace:
li a7, SYS_trace
ecall
ret
执行ecall后,会跳转到内核空间的syscall()函数(kernel/syscall.c),且本cpu的a7寄存器的值存在本进程结构体的trapframe中,以方便内核空间使用。
Q:为什么不在内核空间中直接读取a7寄存器的值?
A:是因为有可能在调用系统调用的过程中会切换到其它进程,这样的话a7寄存器的内容可能会被其它进程改变?
注:在执行ecall到跳转到syscall()函数之间,应该还有一些细节,与前文提到的trampoline相关,但还有待探究。
执行kernel/syscall.c的syscall()函数并取出当前进程的trapframe中保存的a7寄存器内容,即系统调用的编号,然后执行对应的系统调用。
Sysinfo (moderate)
与assignment1相反,在本assignment中,我们需要将数据从内核空间传回用户空间。 具体原理有待更新。
在本Lab中学习到的一些命令
gdb
(gdb) layout split
(gdb) stepi # 机器指令层面的单步步入
Linux
使用grep的-n参数,从标准输出中搜素关键字
$ cat gdb.sh | grep -n f
1:file kernel/kernel
qemu
使用一个核运行qemu
make CPUS=1 qemu-gdb
vim
在vim中搜索关键字后,关键字会一直高亮,如何取消高亮?使用vim命令:
# 取消高亮
:nohlsearch # no high light search
:noh
# 恢复高亮
:hlsearch
:hl