linux asmlinkage引出的问题
在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢?对x86比较了解的话,应该知道早期这些函数参数和函数内部局部变量一起被分配到了函数的局部堆栈中,这是由于早期寄存器比较少才采用这种挫逼的方式。linux操作系统支持多种CPU架构,比如x86、ppc和arm等,在不同的处理器结构上不能保证都是通过寄存器传递参数的,所以这种方式适用于早期的cpu。假如你有8个寄存器,要传4个参数,当然也可以把参数放到前4个寄存器中传递,但是函数内也是要使用寄存器来运算的,这时候4个不够用,这时候再把前4个寄存器中的参数入栈就有点蠢了。
现在cpu中都有多个寄存器,很多都有32个寄存器,很少有人会写传10个参数的函数,就算有这种形式的函数,使用它接口的人都要疯掉了。ARM对函数调用过程中的传参定义了一套规则,即 ATPCS,规则中明确指出ARM中R0-R4都是作为通用寄存器使用,在函数调用时处理器从R0-R4中获取参数,在函数返回时再 将需要返回的参数一次存到R0-R4中,也就是说可以将函数参数直接存放在寄存器中,所以为了严格区别函数参数传递方式,引入了两个标记,即 asmlinkage和FASTCALL,前者表示将函数参数存放在局部栈中,后者则是通知编译器将函数参数用寄存器保存起来。
- 在x86中asmlinkage才会通过栈传参,在x86_64中传参只是通过寄存器,asmlinkage在x86_64下是空的,什么也不做,也就是带了这个修饰标记也是通过寄存器传参,这个很坑的,网上一大堆资料都没说这点,可以通过编译后反汇编看一下究竟是怎么传参的。
通过堆栈传参:试用前将参数入栈,使用时将参数加载到寄存器。通过寄存器传参:使用前将参数直接放到寄存器中使用,免去了两次mem<->reg
的拷贝 - 应用程序到内核使用系统调用时,应用程序并不是自己直接使用系统调用接口,而是通过特定arch下封装之后的接口。具体的系统调用传参各自的arch中有自己的约定,各不相同,可以通过
man syscall
来查看约定,简称ABI(arch binary interface)
arch/ABI instruction syscall # retval Notes
───────────────────────────────────────────────────────────────────
x86_64 syscall rax rax See below
x32 syscall rax rax See below
arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes
──────────────────────────────────────────────────────────────────
x86_64 rdi rsi rdx r10 r8 r9 -
x32 rdi rsi rdx r10 r8 r9 -
- 当使用寄存器传递参数时,abi中寄存器使用约定并不是通常的函数传参,x86可以参考:
arch/x86/include/asm/calling.h
文件中注释说明了寄存器使用规则:
x86 function call convention, 64-bit:
arguments [caller-clobbered] | callee-saved [callee-clobbered] | extra caller-saved | return |
---|---|---|---|
rdi rsi rdx rcx r8-9 | rbx rbp [*] r12-15 | r10-11 | rax, rdx [**] |
x86 function calling convention, 32-bit:
arguments [caller-clobbered] | callee-saved [callee-clobbered] | extra caller-saved | return |
---|---|---|---|
eax edx ecx | ebx edi esi ebp [*] | eax, edx [**] |
关于caller-clobbered和callee-clobbered参考另外一篇说明:
https://blog.csdn.net/faxiang1230/article/details/104046520
为什么使用asmlinkage:
asmlinkage是gcc中的一种特性,告诉编译器,函数期望从这个cpu的栈上找到它的参数而不是从寄存器(虽然寄存器上更快)上。系统调用会将它的第一个参数当做系统调用号,支持四个或更多参数到内核中。系统调用实现了这个需求:前面的参数放在寄存器中,而其他的参数放到栈上。所有系统调用都被打上了asmlinkage
tag,结果就是他们都会到栈上寻找参数.当然,sys_ni_syscall
没有什么不同,只是并不传任何参数。
现在系统调用基本上不从栈上取参数了,寄存器这么多,用户空间发起系统调用之后陷入到内核中,内核只是处理ss, cs, rip
等寄存器,传参的寄存器和保存系统调用号的寄存器在这个过程中时没有人修改的,所以别再傻傻的在系统调用函数中找怎么取参的了。