xv6源码分析 001
我们先看看xv6这个项目的基本结构(只看代码部分)
主要就是两个目录kernel
和 user
。
-
user
是一些用户程序,也就是我们平时在shell上面执行的命令,每执行一个命令就会创建一个新的用户进程来执行这个命令在
user
目录下有两个文件需要我们注意一下:usys.pl
和initcode.S
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"; }
很明显,这是系统调用的trap过程,但是这个是什么语言我还不清楚,
.global $name
:类似C语言中的声明,global声明这个过程在这个目录内可见${name}:
:过程的名称,在c代码中通过一个向量extren char name[];
可以引入这个汇编过程li a7, SYS_${name}
:li
指令的作用将任意的立即数加载到指定的寄存器中,这里是将SYS_{name}
这个宏常量加载到寄存器a7
中,为什么这么做呢?明天我们将会看到,在进行系统调用的时候,发起系统调用的进程将会将CPU的执行权交给内核线程,内核线程如何知道进程调用的是那个系统调用呢?这是就可以从寄存器a7
中取出先前存放进去的SYS_{name}
系统调用号,来执行相应的系统调用。注意:这是涉及的是线程切换:由用户态线程切换到了内核线程,因为每个进程都都一个内核线程,这样能够大大减少系统调用的开销。ecall
:陷入指令(trap),使进程从用户态切换到内核态,即用户线程切换到内核线程ret
:调用返回,返回的内核线程的上下文
我们再来看看
initcode.S
主要作用是系统调用
exec
和start
的过程,我就直接在源码上注释了# Initial process that execs /init. # This code runs in user space.#include "syscall.h"# exec(init, argv) .globl start # 声明start过程为整个目录可见 start: # 过程的开始,标签la a0, init # 将init这个字符串的地址加载到寄存器a0la a1, argv # 将argc字符串的地址加载到寄存器a1li a7, SYS_exec # 将exec的系统调用号加载到寄存器a7ecall # 陷入内核执行系统调用# for(;;) exit(); exit: li a7, SYS_exitecalljal exit # jal指令是跳转到对应的标签,相当于c语言的goto# char init[] = "/init\0"; init: # 定义一个字符串,表示加载的可执行程序映像的名称.string "/init\0"# char *argv[] = { init, 0 }; # 定义了一个argv 数组 .p2align 2 argv:.long init # init字符串的地址.long 0 # 一个空指针
ok,今晚就先到这里了,我们明晚再继续吧。
字符串的地址
.long 0 # 一个空指针
ok,今晚就先到这里了,我们明晚再继续吧。