函数参数传入Linux内核中断处理程序(从asm到C)

B.S*_*Gao 7 x86 assembly calling-convention linux-kernel interrupt-handling

当我阅读Linux内核源码时,我遇到了这段代码:

__visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)   
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    entering_ack_irq();
    local_apic_timer_interrupt();
    exiting_irq();

    set_irq_regs(old_regs);
}
Run Code Online (Sandbox Code Playgroud)

该函数smp_apic_timer_interrupt()有一个参数.调用此函数是通过一段汇编语言代码:

ENTRY(apic_timer_interrupt)
     RING0_INT_FRAME;     
     ASM_CLAC;           
     pushl_cfi $~(0xef);
     SAVE_ALL;         
     TRACE_IRQS_OFF
     movl %esp,%eax;
     call smp_apic_timer_interrupt; // <------call high level C function       
     jmp ret_from_intr;      
     CFI_ENDPROC;           
ENDPROC(apic_timer_interrupt)
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚高级C函数如何smp_apic_timer_interrupt()获取其参数(通过哪个寄存器)?

Mic*_*tch 5

您可能正在考虑正常的调用约定(堆栈上的参数)。现代Linux内核(32位变体)将寄存器中的前3个参数(EAXECXEDX)作为优化传递。根据内核的不同,在使用__attribute__(regparm(3))或在命令行中将GCC的现代版本的内核传递-mregparm=3选项指定为函数时,该约定被指定为函数的属性修饰符。在海湾合作委员会的文件说,这大约是选项/属性:

regparm (number)

On the Intel 386, the regparm attribute causes the compiler to pass up to
number integer arguments in registers EAX, EDX, and ECX instead of on the
stack. Functions that take a variable number of arguments will continue to
be passed all of their arguments on the stack. 
Run Code Online (Sandbox Code Playgroud)

在古代内核中,通常使用32位ABI(以及堆栈中的参数约定)。最终,内核配置通过内核构建配置中的CONFIG_REGPARM设置支持寄存器中的参数常规堆栈约定:

config REGPARM
    bool "Use register arguments"
    default y
    help
    Compile the kernel with -mregparm=3. This instructs gcc to use
    a more efficient function call ABI which passes the first three
    arguments of a function call via registers, which results in denser
    and faster code.

    If this option is disabled, then the default ABI of passing
    arguments via the stack is used.

    If unsure, say Y.
Run Code Online (Sandbox Code Playgroud)

Linux内核维护者在2006年通过以下内核commit放弃了该选项:

-mregparm=3 has been enabled by default for some time on i386, and AFAIK
there aren't any problems with it left.

This patch removes the REGPARM config option and sets -mregparm=3
unconditionally.
Run Code Online (Sandbox Code Playgroud)

基于这些知识,您可以查看您提供的代码,并假设我们位于内核中,默认情况下,它会将前三个参数传递到寄存器中。在您的情况下:

 __visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)
Run Code Online (Sandbox Code Playgroud)

一个参数,因此它在EAX中传递。调用smp_apic_timer_interrupt的代码如下所示:

ENTRY(apic_timer_interrupt)
     RING0_INT_FRAME;     
     ASM_CLAC;           
     pushl_cfi $~(0xef);
     SAVE_ALL;         
     TRACE_IRQS_OFF
     movl %esp,%eax;
     call smp_apic_timer_interrupt; // <------call high level C function       
     jmp ret_from_intr;      
     CFI_ENDPROC;           
ENDPROC(apic_timer_interrupt)
Run Code Online (Sandbox Code Playgroud)

重要的是,SAVE_ALL宏调用将所有必需的寄存器压入堆栈。内核的版本会有所不同,但是将寄存器推入堆栈的主要作用是相似的(为简便起见,我删除了DWARF条目):

.macro SAVE_ALL
         cld
         PUSH_GS
         pushl_cfi %fs
         pushl_cfi %es
         pushl_cfi %ds
         pushl_cfi %eax
         pushl_cfi %ebp
         pushl_cfi %edi
         pushl_cfi %esi
         pushl_cfi %edx
         pushl_cfi %ecx
         pushl_cfi %ebx
         movl $(__USER_DS), %edx
         movl %edx, %ds
         movl %edx, %es
         movl $(__KERNEL_PERCPU), %edx
         movl %edx, %fs
         SET_KERNEL_GS %edx
.endm
Run Code Online (Sandbox Code Playgroud)

完成后,ESP将指向最后一个寄存器被压入的位置。这个地址复制到EAXmovl %esp,%eaxEAX成为指针struct pt_regs *regs。堆栈上所有推入的寄存器都变为实际的pt_regs数据结构,EAX现在指向它。

asmlinkage宏将在内核中发现,对于那些需要参数堆栈中的传统方式上传递的功能。它的定义如下:

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
Run Code Online (Sandbox Code Playgroud)

其中regparm(0)表示没有参数将通过寄存器传递。

确实必须知道什么是构建选项,以及要用来对所使用的约定进行准确评估的内核版本。


Mic*_*ael 3

引用自https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s06.html

SAVE_ALL宏扩展为以下片段:

cld
push %es
push %ds
pushl %eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
pushl %ecx
pushl %ebx
movl $ _ _USER_DS,%edx
movl %edx,%ds
movl %edx,%es
Run Code Online (Sandbox Code Playgroud)

保存寄存器后,当前堆栈顶部位置的地址被保存在寄存器中eax[与movl %esp,%eax,因此 ]eax指向包含由 压入的最后一个寄存器值的堆栈位置SAVE_ALL

所以eax寄存器就是通过它smp_apic_timer_interrupt接收pt_regs指针的寄存器。