2012-04-08 85 views
15

我正在写一个将从汇编代码中调用的C函数。从程序集中调用c函数需要“asmlinkage”吗?

(具体来说,我想要做的系统调用Linux内核处理的路径一些检查工作,所以我会调用C功能的系统调用在entry_32.S派遣之前)

我很困惑在定义我的c函数时使用“asmlinkage”修饰符。

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

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

问题:

(1)定义将在汇编代码来调用这样的功能时,需要asmlinkage?

(2)gcc中的默认调用约定是什么?如果我在定义c函数时省略“asmlinkage”,它是否暗示_cdecl或fastcall? (3)如果默认的调用约定是cdecl,为什么需要asmlinkage,考虑到cdecl等于asmlinkage修饰符? (我在这里纠正?)

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

最后,有人可以推荐一些我可以参考的混合装配/ c编程资源/书籍吗?

回答

15

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

(1)defning将从汇编代码中调用这样的功能时,需要asmlinkage?

不,实际上快速调用是经常使用的。

例如,在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中的默认调用约定是什么?如果我在定义c函数时省略“asmlinkage”,它是否暗示_cdecl或fastcall? (3)如果默认调用约定是cdecl,为什么需要asmlinkage,考虑到cdecl等于asmlinkage修饰符? (我是正确的吗?)

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

但是我仍然不清楚何时使用asmlinkage和何时使用fastcall。我真的需要一些指导和资源来提及。

+0

在32位x86上,内核中使用的调用约定似乎不完全是GNU fastcall(即前'2个参数 - '%ecx'和'%edx')。它受'-mregparm = 3' [GCC选项](http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html)的控制,它指示编译器使用'%eax', '%edx','%ecx'为前3个参数,依次。这与Borland fastcall约定相似,但仍然如此。 – Eugene 2012-04-09 07:27:32

3

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

11

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

为什么所有的系统调用函数SYS_ *,例如sys_gettimeofday,使用堆栈来传递参数?

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

另一方面,如果您想为调用约定使用fastcall,则需要完成更多工作。我们首先需要知道的是,当用户程序发出系统调用时,在x86-linux中,%eax是系统调用号码,而%将6个参数传递给系统调用(在“int 80h”或“sysenter”之前)。但是,fastcall的调用约定是传递%eax中的第一个参数,%edx中的第二个参数,第三个%ecx,其他人从右向左推入堆栈。通过这种方式,为了在内核中实施这种快速调用约定,除了将所有寄存器保存在堆栈上之外,还需要以某种方式安排这些约定。