6

Когда я прочитал исходный код ядра Linux, я наткнулся на этот кусок кода:параметр функции, переходящий в обработчик прерываний ядра Linux (от ASM до C)

__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() получить свой параметр (на каком регистре)?

ответ

3

Цитируя https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s06.html

The 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 зарегистрирован в этом регистре: получает указатель pt_regs.

+1

Источник выглядит так, будто ему нужен указатель на то, где он хранится в стеке, поэтому 'eax' имеет смысл, но ни вопрос, ни ответ ничего не говорили о Linux (ядре) внутренне с использованием функции register-call ABI даже для i386 (не amd64). Предполагая, что вы правильно знаете, почему существует 'mov% esp,% eax', это был бы еще лучший ответ, если бы вы могли ссылаться на документы ядра, которые это объясняют. В противном случае я бы предположил, что «smp_apic_timer_interrupt» займет последнее место в стеке ('% ebx') в качестве первого аргумента, что не имеет смысла. –

+0

Ядро Linux построено fastcall (eax, ecx, edx). –

4

Возможно, вы, вероятно, считаете нормальное соглашение о вызове (аргументы в стеке). Современные ядра Linux (32-битные варианты) передают первые 3 параметра в регистры (EAX, ECX, EDX) в качестве оптимизации. В зависимости от ядра это соглашение указывается в качестве модификатора атрибута для функций с использованием __attribute__(regparm(3)) или современных версий кода ядра -mregparm=3 для GCC в командной строке. GCCdocumentation говорит это о том, что параметр/атрибут:

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. 

Основываясь на этих знаниях, вы можете посмотреть код, который вы представили, и предположите, что мы находимся на ядре, где оно по умолчанию было выполнено с первыми тремя параметрами, передаваемыми в регистры.В вашем случае:

__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 Макровызов толкает все необходимые регистры в стеке. Она будет меняться от версии к версии ядра, но основной эффект выталкивания регистров в стек похоже (я удалил карликовых записи для краткости):

.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 будет укажите место, где был отодвинут последний регистр. Этот адрес копируется в EAX с movl %esp,%eax и EAX становится указателем на struct pt_regs *regs. Все толкаемые регистры в стеке становятся фактической структурой данных pt_regs, и теперь EAX указывает на это.

Макрос asmlinkage будет найден в ядре для тех функций, для которых аргументы должны передаваться в стеке обычным способом. Она определяется как что-то вроде:

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

Где regparm(0) говорит, что никакие параметры не будут передаваться через регистры.

Необходимо действительно знать, что такое варианты сборки, и версию ядра, используемую для точной оценки используемого соглашения.

Смежные вопросы