在Linux内核中断处理程序(从asm到C)中传递的函数参数 [英] Function parameter passing in a Linux kernel interrupt handler (from asm to C)

查看:422
本文介绍了在Linux内核中断处理程序(从asm到C)中传递的函数参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When I read the Linux kernel source, I came across this piece of code:

__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);
}

函数smp_apic_timer_interrupt()具有一个参数.该功能的调用是通过一段汇编语言代码进行的:

The function smp_apic_timer_interrupt() takes one parameter. The calling of this function is by a piece of assembly language code:

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)

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

I cannot figure out how the high level C function smp_apic_timer_interrupt() get its parameter (by which register)?

推荐答案

您可能正在考虑正常的调用约定(堆栈中的参数).现代Linux内核(32位变体)通过寄存器中的前三个参数( EAX ECX EDX )进行了优化.根据内核的不同,此约定在使用__attribute__(regparm(3))的函数中指定为属性修饰符,或者在命令行上将现代版本的内核将-mregparm=3选项传递给 GCC . GCC 文档说关于该选项/属性的信息:

Your are probably thinking normal calling convention (arguments on the stack). Modern Linux kernels (32-bit variants) pass the first 3 parameters in registers (EAX, ECX, EDX) as an optimization. Depending on the kernel this convention is specified as an attribute modifier on the functions using __attribute__(regparm(3)), or modern versions of the kernel pass -mregparm=3 option to GCC on the command line. The GCC documentation says this about that option/attribute:

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. 

在古代内核中,正常的32位ABI(以及堆栈上的参数约定)是标准.最终,内核配置通过内核构建配置中的 CONFIG_REGPARM 设置支持寄存器 OR 中的参数,这是常规堆栈约定:

In ancient kernels the normal 32-bit ABI (and convention of arguments on the stack) was the norm. Eventually the kernel configuration supported arguments in registers OR the normal stack convention via the CONFIG_REGPARM setting in the kernel build configuration:

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.

Linux内核维护者在2006年使用

The Linux kernel maintainers got rid of this option in 2006 with this kernel 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.

基于此知识,您可以看一下您提供的代码,并假设我们在内核上,默认情况下,它会将前三个参数传递到寄存器中.就您而言:

Based on this knowledge one can look at the code you have presented and assume we are on a kernel where it has defaulted to the first 3 parameters being passed in registers. In your case:

 __visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)

具有一个参数,因此它在 EAX 中传递.调用 smp_apic_timer_interrupt 的代码如下:

has one parameter so it is passed in EAX . The code that called smp_apic_timer_interrupt looked like this:

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)

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

The important part is that the SAVE_ALL macro call pushes all the required registers on the stack. It will vary from version to version of the kernel, but the main effect of pushing registers on the stack is similar (I've removed the DWARF entries for brevity):

.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

完成后, ESP 将指向最后一个寄存器被压入的位置.该地址将与movl %esp,%eax复制到 EAX ,并且 EAX 成为struct pt_regs *regs的指针.堆栈上所有推入的寄存器都变为实际的 pt_regs 数据结构,而 EAX 现在指向该结构.

When completed ESP will point to the location where the last register was pushed. That address is copied to EAX with movl %esp,%eax, and EAX becomes the pointer for struct pt_regs *regs . All the pushed registers on the stack become the actual pt_regs data structure, and EAX now points to it.

asmlinkage宏将在内核中找到,这些函数要求将参数以常规方式传递到堆栈上.它的定义如下:

The asmlinkage macro will be found in the kernel for those functions that require arguments to be passed on the stack the conventional way. It is defined as something like:

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

regparm(0)表示不会通过寄存器传递任何参数.

Where regparm(0) says that no parameters will be passed via registers.

人们真的必须知道构建选项是什么,以及用来准确评估所使用约定的内核版本.

One really has to know what the build options are, and the version of the kernel being used to make an accurate assessment of the convention being used.

这篇关于在Linux内核中断处理程序(从asm到C)中传递的函数参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆