当我阅读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()
是如何获取其参数的(通过哪个寄存器)?
2条答案
按热度按时间azpvetkf1#
你可能在考虑正常的调用约定(堆栈上的参数)。现代Linux内核(32位变体)将寄存器中的前3个参数(EAX、EDX、ECX,按此顺序)作为优化。根据内核的不同,这个约定被指定为使用
__attribute__(regparm(3))
的函数的属性修饰符,或者内核的现代版本在命令行上将-mregparm=3
选项传递给 GCC。GCCdocumentation是这样描述这个选项/属性的:在古老的内核中,正常的32位ABI(以及堆栈上参数的约定)是标准。最终,内核配置通过内核构建配置中的 CONFIG_REGPARM 设置支持寄存器中的参数 * 或 * 正常堆栈约定:
Linux内核维护者在2006年用这个kernel commit去掉了这个选项:
基于这些知识,我们可以查看您提供的代码,并假设我们在内核上,默认前3个参数传入寄存器。在您的案例中:
有 * 一个 * 参数,所以它在 EAX 中传递。调用 smp_apic_timer_interrupt 的代码如下所示:
重要的部分是保存_ALL宏调用将所有必需的寄存器压入堆栈。它会因内核的版本而异,但将寄存器推入堆栈的主要效果是相似的(为了简洁起见,我删除了 DWARF 条目):
完成后,ESP 将指向最后一个寄存器被推入的位置。该地址通过
movl %esp,%eax
复制到 EAX,EAX 成为struct pt_regs *regs
的指针。堆栈上所有被压入的寄存器都变成了实际的 *pt_blog * 数据结构,EAX 现在指向它。asmlinkage
宏可以在内核中找到,用于那些需要以传统方式在堆栈上传递参数的函数。它的定义如下:其中
regparm(0)
表示不会通过寄存器传递任何参数。人们真的必须知道构建选项是什么,以及使用的内核版本,以便对使用的约定进行准确的评估。
bwntbbo32#
引用自https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s06.html
SAVE_ALL
宏扩展为以下片段:保存寄存器后,当前栈顶位置的地址保存在
eax
寄存器中[与movl %esp,%eax
一起,以便]eax
指向包含SAVE_ALL
推入的最后一个寄存器值的栈位置因此,
eax
寄存器是smp_apic_timer_interrupt
接收pt_regs
指针的寄存器。