我正在查看asmlinkage标签。
来自https://kernelnewbies.org/FAQ/asmlinkage
这是某些gcc魔术的#define,它告诉编译器函数不应期望在寄存器中找到其任何参数(常见的优化),但仅在CPU的堆栈上。
根据定义,对于x86_64,它定义为空
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
#ifndef asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif
而且,我读到ABI规范说我们应该将系统调用号放在一个寄存器中,并将参数放在其他一些寄存器中。
然后我们为什么要在堆栈中查找函数参数。系统电话号码也放在堆栈中吗?
asmlinkage告诉编译器在CPU堆栈上查找功能参数,而不是寄存器。
实际上,它使用GCC的regparam属性(Function-Attributes)或IA64的syscall_linkage
:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
但是您的问题是为什么这是必要的。系统调用是用户空间可以调用以请求内核为其执行某些操作(因此在内核空间中执行)的服务。这些函数在您不能期望它们像普通函数那样运行的意义上是非常不合常规的,在这些函数中,参数通常是通过写入程序堆栈来传递的,而实际上是通过写入寄存器来传递的。在用户空间中时,调用syscall要求将某些值写入某些寄存器中。系统呼叫号码将始终以eax写入,而其余参数将输入ebx,ecx等。现在,当发生软件中断时,CPU切换到内核模式,然后执行system_call()。当CPU切换到内核模式时,首先将所有寄存器保存在CPU堆栈中(eax,ebx,ecx等)。在检查了其他事情(例如验证参数)之后,如果一切正常,它将调用相应的系统调用。因此,因为从用户空间一直传递到此点的所有参数信息都很好地存储在堆栈中,必须对此指示编译器,因此请使用asmlinkage。
另一个原因是,在处理来自用户空间的系统调用请求时,内核无论如何都需要将所有寄存器保存到堆栈中(以便在返回到用户空间之前恢复环境),因此在堆栈上可用参数之后,它不需要额外的努力。
系统调用既不是用户级程序也不是共享库的一部分,它们是内核提供的系统服务,并且在内核空间中执行..为了从用户空间移至内核空间,需要执行一些特殊的汇编指令告诉处理器跳到超级用户模式,在此之前,它希望将一些信息推送到寄存器中,以便内核侧知道要执行的系统调用-它具有一个函数指针表,该表指向各个函数“服务”每个系统调用)-
#include "SYS.h"
ENTRY(syscall)
pop %ecx /* rta */
pop %eax /* syscall number */
push %ecx
KERNCALL
push %ecx /* need to push a word to keep stack frame intact
upon return; the word must be the return address. */
jb 1f
ret
其中KERNCALL依赖于体系结构,并且是一些汇编语言指令,这些指令告诉CPU在内核空间中跳入超级用户模式-
./lib/libc/amd64/SYS.h:#define KERNCALL movq %rcx, %r10; syscall
./lib/libc/i386/SYS.h:#define KERNCALL int $0x80
所以,在编译程序时,优化器偶尔会在寄存器中抛出函数的参数,而不是将其放在程序堆栈中..之所以如此优化是因为编译器会同时为调用方和被调用方发出代码,并且因此双方都意识到了这一点小技巧。但是,对于内核来说不是这样。它不知道在哪个寄存器中寻找什么,因此,用于系统调用的所有参数都必须位于userland程序的堆栈中。