Unikraft函数调用
1、论文相关
- 每个实现系统调用处理程序的库,都通过宏注册到syscall shim这个库中,然后shim层生成lib级别的系统调用接口,当使用unikraft本地编译应用程序源文件时,直接链接到系统调用,从而将系统调用转化为廉价的函数调用
2、源码相关
1、lib/syscall_shim
1、syscall_shim/syscall.h.in
- 与Linux相似,unikraft中也有系统调用号,以NR_开头
1 | /* SPDX-License-Identifier: BSD-2-Clause */ |
2、syscall_shim下的Config.uk:
1 | if LIBSYSCALL_SHIM |
最后一段提到这里会为不可用的系统调用自动生成 libc -style的存根stubs, 但是由于符号的二重定义,此功能有时可能会导致链接失败。 当这种情况时,另一个库会提供一些 libc -style的系统调用,并且无需将它们注册到 syscall_shim库。也就是会有部分库中涉及到的系统调用会在syscall_shim这里通过宏注册,但是也会有部分系统调用会直接在一些库中实现,并没有通过宏定义。
接着:
1 | config LIBSYSCALL_SHIM_HANDLER |
- 为二进制系统调用启用系统调用处理程序请求(例如,sysenter/sysexit)。 处理程序映射根据 Linux ABI 标准注册值
3、syscall_shim中的awk文件
在syscall_shim下有很多awk脚本,会生成一些关于系统调用名称、调用函数的头文件和程序(后面有图)
如在gen_syscall_nrs.awk中生成定义系统调用名称的程序
1 | BEGIN { |
- 在gen_provided.awk中生成已经提供的系统调用的头文件
1 | BEGIN { |
- 在stubs、map、syscall_r、syscall_r_fn等awk中根据提供的系统调用名,定义(或者说注册)uk_syscall_e_*以及uk_syscall_r_*(*表示系统调用的名称)一系列宏定义函数,在其它库的exportsysm.uk文件中能找到
1 | gen_syscall_map.awk: |
在syscall_shim下的Makefile.uk文件中出现相关头文件和程序:
4、legacy_syscall.h
- 这里定义了一些被新的系统调用实现的一些遗失调用(在前面的一些实现中会提及查阅legacy头文件部分并跳过)
1 | /** |
5、syscall.h
- 在这个头文件中是大量的宏定义,包括了原始的系统调用样式(直接将函数调用映射到目标程序)、包括/不包括lib风格封装的两种宏定义、以及一些错误的汇总
1 | /* System call, returns -1 and sets errno on errors */ |
6、uk_syscall_e_*和uk_syscall_r_*
uk_syscall_e_*是通过宏UK_LLSYSCALL_R_DEFINE展开的,uk_syscall_r_*是通过宏UK_SYSCALL_R_DEFINE展开的
在syscall.h中对这两种宏的定义:
UK_LLSYSCALL_R_DEFINE()不提供libc风格的封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/*
* UK_LLSYSCALL_R_DEFINE()
* Low-level variant, does not provide a libc-style wrapper
*/
...而UK_SYSCALL_R_DEFINE提供:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*
* UK_SYSCALL_R_DEFINE()
* Based on UK_LLSYSCALL_R_DEFINE and provides a libc-style wrapper
* in case UK_LIBC_SYSCALLS is enabled
*/
...如在vfscore\main.c里面可以看到,像uk_syscall_e_*都是在它定义的open()、ioctl()这样的接口里面调用的,相当于是需要在uk_syscall_e_*这个外面再封装一层才能给应用程序调用,而uk_syscall_r_*则是直接可以被调用的
2、lib层的系统调用实现
- 在syscall_shim层注册了一些提供的系统调用名称以及相关宏定义之后,在其他具体涉及到相关系统调用的lib中进行实现,这里以vfscore举例说明
1、vfscore/vfs.h
- 在vfscore/vfs.h中定义了系统调用函数原型:
1 | /* * per task data |
2、vfscore/syscall.c
- 在vfscore/syscall.c中具体实现,如sys_open():
1 | /* |
3、vfscore/main.c
在vfscore/main.c中定义了一些libc样式的,像c标准库函数那样的调用接口:
比如提供了调用接口open():
1 |
|
- 最后一行uk_syscall_e_open()来自宏的展开,像open系统调用是在syscall_shim层注册过的。在这个展开的函数中调用了在syscall.c中实现的真正的系统调用函数sys_open()。(猜测像这里出现的宏的展开是对系统调用的参数的验证)
1 | UK_LLSYSCALL_R_DEFINE(int, open, const char*, pathname, int, flags, |
- 总结open()的调用过程:应用程序接口open() —-> 宏定义展开系统调用函数uk_syscall_e_open() —->syscall.c中实现的最底层的系统调用sys_open()
4、support/scripts的解释
- 这里提到一个声明:
1 | check for UK_(LL)SYSCALL_DEFINE(), raw implementation should be preferred |
这里提到首选使用原始实现UK_SYSCALL_R_DEFINE
在vfscore中可以看到一些只有UK_SYSCALL_R_DEFINE定义,而没有以sys_开头的系统调用函数,如pipe2,位于lib/vfscore/pipe.c文件中,但没有找到sys_pipe这种东西
1 | UK_SYSCALL_R_DEFINE(int, pipe, int*, pipefd) |
3、总结
在syscall_shim层定义了一些系统调用编号、名称,通过那些.awk脚本、sycall.h去注册生成对应的系统调用名称和定义几类宏,接着在lib层,如vfscore中去实现相关的系统调用的宏的展开以及具体的系统调用(某些可能是不需要注册而直接定义的系统调用),并在main.c中会封装一些接口给应用程序直接调用
根据上面提到的support/scripts的声明解释,所以当有注册过的宏定义时,应优先调用的是以UK_SYSCALL_R_DEFINE、UK_LLSYSCALL_R_DEFINE展开的函数
注意到前面提到过的,部分系统调用是不需要注册而直接在lib层定义实现的,所以可以看到存在一些直接调用sys_xxx()而没有调用uk_syscall_e_xxx等
4、与Linux系统调用比较
Linux:
- 调用syscall(),然后通过系统调用号在系统调用表里面找到对应系统调用函数的入口地址,然后int 0x80中断,进入内核态去做系统调用
Unikraft:
- 直接在syscall_shim去注册涉及到的系统调用,一大堆宏定义展开后是每个具体的系统调用,没有系统调用表
- 在其他涉及到的库中,直接实现了对应的系统调用函数,在程序中直接调用已经实现的系统调用函数,不需要通过系统调用表和中断陷入内核的方式