gpt4 book ai didi

assembly - 在 Linux 内核中断处理程序中传递函数参数(从 asm 到 C)

转载 作者:行者123 更新时间:2023-12-04 11:35:53 24 4
gpt4 key购买 nike

当我阅读 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);
}

函数 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)

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

最佳答案

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

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 设置支持寄存器中的参数或正常堆栈约定:

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 年通过这个 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.


基于这一知识,您可以查看您提供的代码,并假设我们在内核中,它默认为在寄存器中传递的前 3 个参数。在你的情况下:
 __visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)

有一个参数,所以它在 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)

重要的部分是 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

完成后,ESP 将指向最后一个寄存器被推送的位置。该地址通过 movl %esp,%eax 复制到 EAX , EAX 成为 struct pt_regs *regs 的指针.堆栈上所有压入的寄存器成为实际的 pt_regs 数据结构,EAX 现在指向它。
asmlinkage对于那些需要以传统方式在堆栈上传递参数的函数,将在内核中找到宏。它被定义为:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

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

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

关于assembly - 在 Linux 内核中断处理程序中传递函数参数(从 asm 到 C),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33932394/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com