xv6系统启动——bootloader
我们以在xv6中添加一个date系统调用为例,介绍在xv6中添加系统调用的方法和原理。
date syscall介绍
date系统调用可以获取系统当前的UTC时间。
在我们完成date系统调用后,我们利用这个系统调用做一个小小的工具,可以打印当前的系统时间,工具代码如下:
date.c
1 |
|
变量r
是我们获取到的时间,我们可以通过r打印我们想要的时间格式,结构体r的内容如下:
1 | struct rtcdate { |
添加系统调用
首先要注意的是,我们要添加一个系统调用,我们需要在好多地方添加对应的代码,我们很容易遗忘。因此,我们可以借鉴xv6中已经写好的系统调用,uptime
系统调用是一个很好的例子。
1 | grep -n uptime *.[hcS] |
我们不难发现我们要在六个地方添加对应的代码,那么加下来的事情就是照葫芦画瓢即可。
-
extern int sys_uptime(void); extern int sys_date(void); <!--code3-->
-
4-->
<!--code -
SYSCALL(uptime) SYSCALL(date)
1
2
3
4
5
6
7
8
9
10
11
12
13
第四部分的内容是系统调用的具体实现,我们在这里需要借助于系统`lapic.c`中给我们提供的`cmostime`函数,这个函数接受一个 `struct rtcdate`指针,将时间赋值到其中,此外,参数我们也需要借助于xv6提供的argptr来获取。
```c
int
date(void)
{
struct rtcdate *r;
if(argptr(0, (void*)&r, sizeof(struct rtcdate)) < 0)
return -1;
cmostime(r);
return 0;
}
执行过程分析
-
我们在使用这个系统调用在用户态的形式,这个函数被定义在
user.h
中。只需要引入user.h
我们就可以使用系统调用。 -
这个系统调用使用汇编实现的,当然,这个实现比较简单,我们在传入参数之后,我们在
%eax
寄存器中传入我们需要使用的系统调用号,然后使用int
指令即可。这个实现在usys.S
中。
1 |
|
- 关于
movl $SYS_ ## name, %eax;
在C语言中,两个#
表示字符串拼接,以fork为例,这里的指令实际上是movl $SYS_ fork, %eax;
,还记得我说%eax
要传入系统调用号吗,那么SYS_fork
是怎么转变成正确的系统调用号的呢,在syscall.h
中:
1 |
这样,在预编译后,指令就是(fork为例),movl 1,%eax
。
- 然后,
int
指令会陷入内核,具体的细节我们暂不关系,接下来,我们会执行syscall.c
中的syscall
函数
1 | void |
可以看到核心函数:
1 | curproc->tf->eax = syscalls[num](); |
其中num是系统调用号,那么syscalls是什么呢,也是在这个文件中,内容如下:
1 | static int (*syscalls[])(void) = { |
是不是巧妙地利用了C语言的宏。
- 最后,(还是以fork为例),我们会发现系统会执行
sys_fork
函数,这个函数被定义在sysproc.c
中(事实上可以被定义在任何位置)
1 | int |
可以发现有意思的一点,系统调用调用到最后竟然还是调用fork
?!这个fork已经和前面的fork完全不同了,现在的fork是一个内核中的函数,被定义在proc.c
中,前面的过程事实上都可以理解为对内核函数的包装,通过上面的一系列过程,我们最终实现了在用户态"调用"
内核中函数。
可以发现date也是同理,date系统调用最终是调用了
cmostime
函数。不过看起来fork系统调用由于用户态和内核态有着相同的名字看起来更加有趣。
将date工具加入到xv6中
我们现在已经完成了我们的系统调用,最后我们只需要make
编译即可,make
会帮助我们把date.c
编译成_date
二进制文件。最后,在编译之前,我们需要把_date
加入到Makefile
的UPROGS
中,这样命令才能生效。
到这里我们就成功为xv6添加了一个系统调用,用利用这个系统调用制作了一个打印的时间的工具。
tips:分清楚系统调用和shell 命令
date.c和_date
是我们利用date系统调用制作的,我们要注意我们在xv6 shell中使用的date命令是_date
二进制文件调用的结果,而不是直接使用系统调用
1 | UPROGS=\ |
1 | make |