通常,C库提供对系统调用的支持.用户应用程序可以从标准头部拉入功能原型,并与C库链接以使用系统调用(或库例程,反过来使用您的系统调用).但是,如果刚刚编写系统调用,glibc已经支持它了,这是值得怀疑的!
值得庆幸的是,Linux提供了一组用于封装系统调用访问的宏.它设置寄存器内容并发出陷阱指令.这些宏命名为_syscalln(),其中n在0到6之间.该数字对应于传入syscall的参数数量,因为该宏需要知道要预期的参数以及因此推入寄存器.例如,考虑系统调用open(),定义为
long open(const char *filename,int flags,int mode)
#define __NR_open 5 _syscall3(long,open,const char *,filename,int,flags,mode)
然后,应用程序可以简单地调用open().
对于每个宏,有2个2xn参数.第一个参数对应于系统调用的返回类型.第二个是系统调用的名称.接下来按照系统调用顺序按照每个参数的类型和名称. __NR_open定义在;它是系统调用号码. _syscall3宏扩展为具有内联汇编的C函数;组件执行上一节中讨论的步骤,将系统调用号码和参数推送到正确的寄存器中,并发出软件中断以陷入内核.将此宏放在应用程序中是使用open()系统调用所必需的.
我们来写宏来使用我们灿烂的新的foo()系统调用,然后编写一些测试代码来显示我们的努力.
#define __NR_foo 283 __syscall0(long,foo) int main () { long stack_size; stack_size = foo (); printf ("The kernel stack size is %ld\n",stack_size); return 0; }
应用程序可以简单地调用open()是什么意思?
解决方法
实际上,应用程序在内核提供的“虚拟机”上运行:它在user space中运行,并且只能在(最低机器级)执行user CPU mode中允许的机器指令集(由SYSENTER或INT 0x80 …)用于进行系统调用.因此,从用户级应用程序的角度来看,系统调用是原子伪机器指令.
Linux Assembly Howto解释了如何在程序集(即机器指令)级别完成系统调用.
GNU libc提供了与系统调用对应的C函数.所以例如open的功能是一个很小的胶水(即一个包装),在数字NR__open的系统调用之上(它正在使系统调用然后更新errno).应用程序通常在libc中调用这样的C函数,而不是执行系统调用.
你可以使用一些其他的libc.例如,MUSL libc是一个“简单的”,其代码可能更容易阅读.它还将原始系统调用包装到相应的C函数中.
如果你添加自己的系统调用,你最好还要实现一个类似的C函数(在你自己的库中).所以你也应该有一个头文件为您的库.
另见intro(2)和syscall(2)和syscalls(2)手册页,以及VDSO in syscalls的作用.
请注意,syscalls不是C函数.它们不使用调用堆栈(甚至可以在没有堆栈的情况下调用它们).系统调用基本上是一个数字,例如来自< asm / unistd.h>的NR__open,一个SYSENTER机器指令,具有关于在系统调用的参数之前保存哪些寄存器的SYSENTER机器指令,以及在系统调用的结果(包括失败的结果,在C库中设置errno包裹系统调用).系统调用的约定不是ABI规范中C函数的调用约定(例如x86-64 psABI).所以你需要一个C包装.