[英]Function parameter passing in a Linux kernel interrupt handler (from asm to C)
當我閱讀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位變體)將寄存器中的前3個參數( EAX , ECX , EDX )作為優化傳遞。 根據內核,此約定在函數上使用__attribute__(regparm(3))
指定為屬性修飾符,或者在命令行-mregparm=3
內核的現代版本傳遞給GCC -mregparm=3
選項。 GCC 文檔說明了這個選項/屬性:
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年通過此內核提交擺脫了這個選項:
-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)
表示沒有參數通過寄存器傳遞。
我們必須知道構建選項是什么,以及用於對所使用的約定進行准確評估的內核版本。
引自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
保存寄存器后,當前頂部堆棧位置的地址保存在
eax
寄存器中[使用movl %esp,%eax
,這樣]eax
指向包含SAVE_ALL
推送的最后一個寄存器值的堆棧位置
因此, eax
寄存器是smp_apic_timer_interrupt
通過其接收pt_regs
指針的寄存器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.