从汇编调用交流function需要“asmlinkage”吗?

我正在编写一个将从汇编代码调用的C函数。

(具体来说,我想在linux内核的系统调用处理路径中做一些检查工作,所以我将在entry_32.S中调度系统调用之前调用c函数)

在定义我的c函数时,我对“asmlinkage”修饰符感到困惑。

我知道asmlinkage是告诉编译器参数将通过堆栈传递。

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

问题:

(1)在定义将从汇编代码调用的函数时是否需要asmlinkage?

(2)gcc中的默认调用约定是什么。 如果我在定义交流function时省略“asmlinkage”,它是否意味着_cdecl或fastcall?

(3)如果默认调用约定是cdecl,为什么需要asmlinkage,考虑到cdecl等于asmlinkage修饰符? (我在这里纠正吗?)

(4)为什么那些系统调用函数都用asmlinkage声明。 我们可以先将参数复制到寄存器,然后调用那些系统调用函数吗? 从我的角度来看,在x86中,当发出系统调用时,参数很容易保存在寄存器中; 那么为什么还要在堆栈中保存然后通过堆栈约定强制执行这样的传递参数呢?

最后,任何人都可以推荐一些我可以参考混合汇编/ c编程的资源/书籍吗?

经过几个小时的研究,我得到了以下经验点:

(1)在定义将从汇编代码调用的函数时是否需要asmlinkage?

实际上,经常使用fastcall。

例如,在entry_32.S中,如果搜索“call”,则可以获取从此程序集文件调用的所有c函数。 然后你可以看到,许多人使用fastcall而不是asmlinkage作为调用约定。 例如,

  /*in entry_32.S*/ movl PT_OLDESP(%esp), %eax movl %esp, %edx call patch_espfix_desc /*in traps_32.c*/ fastcall unsigned long patch_espfix_desc(unsigned long uesp, unsigned long kesp) 

(2)gcc中的默认调用约定是什么。 如果我在定义交流function时省略“asmlinkage”,它是否意味着_cdecl或fastcall?

(3)如果默认调用约定是cdecl,为什么需要asmlinkage,考虑到cdecl等于asmlinkage修饰符? (我在这里纠正吗?)

对于未从汇编代码调用的C函数,我们可以安全地假设默认调用约定是cdecl(或快速调用;它没关系,因为gcc将负责调用者和被调用者的参数传递。默认的调用约定可以是在编译时指定)。 但是,对于从汇编代码调用的C函数,我们应该显式声明函数的调用约定,因为已经修复了汇编端传递代码的参数。 例如,如果patch_espfix_desc被声明为asmlinkage,那么gcc将编译该函数以从堆栈中检索参数。 这与汇编方不一致,后者将参数放入寄存器中。

但我还不清楚何时使用asmlinkage以及何时使用fastcall。 我真的需要一些指导和资源来参考。

我想尝试自己回答问题(4):

为什么所有系统调用函数sys_ *,例如sys_gettimeofday,都使用stack来传递参数?

原因是无论如何内核需要在处理来自用户空间的系统调用请求时将所有寄存器保存到堆栈中(以便在返回用户空间之前恢复环境),因此参数在堆栈上可用。 即,它不需要额外的努力。

另一方面,如果要使用fastcall作为调用约定,则必须完成更多工作。 我们首先需要知道,当用户程序发出系统调用时,在x86-linux中,%eax用于系统调用号, %ebx,%ecx,%edx,%esi,%edi,%ebp用于传递6系统调用的参数(在“int 80h”或“sysenter”之前)。 但是,fastcall的调用约定是传递%eax中的第一个参数,%edx中的第二个参数,第三个ecx中的第二个参数,其他则从右到左传递到堆栈。 这样,要在内核中强制执行这样的fastcall约定,除了保存堆栈中的所有寄存器之外,还需要以某种方式安排这些约定。

我认为,这个想法是允许使用gcc选项编译内核,这些选项可以将默认调用约定更改为更高效的(即在寄存器中传递更多参数)。 但是,根据所使用的gcc选项,不能允许需要从asm调用的函数在调用约定中变化,或者对于每个受支持的gcc选项集必须有asm的单独版本。 因此,需要使用固定调用约定(恰好与默认值没有特殊gcc选项匹配)的函数使用特殊属性声明,以便它们的调用约定保持不变。