簡體   English   中英

函數參數傳入Linux內核中斷處理程序(從asm到C)

[英]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個參數( EAXECXEDX )作為優化傳遞。 根據內核,此約定在函數上使用__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復制到EAXEAX成為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM