1. 程式人生 > >Linux x86_64內核中斷初始化

Linux x86_64內核中斷初始化

linkage struct 內核調試 tasklet 多處理器 錯誤 star should action

中斷分類

Linux系統中,中斷分為:

  • 硬中斷:由外部設備或者執行異常產生的需要快速處理的中斷。如缺頁中斷、定時器硬件中斷。

    根據內部產生還是外部產生,分為:
    1. 異常:異常是內部產生的中斷,不可屏蔽。
    2. 外部中斷:外部中斷是由外部設備產生的,可以屏蔽。
  • 軟中斷:

? 軟中斷是Linux系統中斷處理的底半處理部分,是Linux模擬的中斷。為了加快硬件中斷的處理,防止數據的丟失,Linux對中斷處理分為頂半處理和底半處理兩部分,頂半處理程序快速處理硬件事件,把不是那麽緊急的邏輯放到底半處理程序中,可以簡單的認為硬終端處理程序為頂半處理程序,軟中斷處理程序為底半處理程序。軟中斷一般在硬中斷處理程序執行後才會執行。但是當硬中斷嵌套的時候,軟中斷會在所有的硬中斷處理完畢後才會處理,當軟中斷太多,會放到ksoftirqd線程中處理。

內核初始化-中斷

intel處理器有256個硬中斷號。其中前32個中斷號為異常使用,在內核初始化的時候進行初始化。內核初始化的代碼流程如下:
可以看到首先初始化異常處理,再初始化部分外部中斷,再初始化一部分軟中斷處理。


asmlinkage void __init start_kernel(void)
{
     
    lock_kernel();
    ...
     //初始化調度模塊
    sched_init();

    ...
    sort_main_extable();
    // 初始化異常處理。
    trap_init();
    ...
    // 初始化外部中斷
init_IRQ(); ... // 初始化定時器模塊,同時,會註冊定時器的軟中斷處理函數。 init_timers(); // 初始化軟中斷) softirq_init(); time_init(); ... // 初始化 acpi_early_init(); }

異常中斷初始化

異常中斷在內核中稱為trap,異常中斷初始化代碼為

//門初始化。初始化中斷向量表。系統有固定的256個硬件中斷向量。
void __init trap_init(void)
{
    
    set_intr_gate(0,&divide_error);
    set_intr_gate_ist(1
,&debug,DEBUG_STACK); set_intr_gate_ist(2,&nmi,NMI_STACK); set_intr_gate(3,&int3); set_system_gate(4,&overflow); /* int4-5 can be called from all */ set_system_gate(5,&bounds); set_intr_gate(6,&invalid_op); set_intr_gate(7,&device_not_available); set_intr_gate_ist(8,&double_fault, DOUBLEFAULT_STACK); set_intr_gate(9,&coprocessor_segment_overrun); set_intr_gate(10,&invalid_TSS); set_intr_gate(11,&segment_not_present); set_intr_gate_ist(12,&stack_segment,STACKFAULT_STACK); set_intr_gate(13,&general_protection); set_intr_gate(14,&page_fault); set_intr_gate(15,&spurious_interrupt_bug); set_intr_gate(16,&coprocessor_error); set_intr_gate(17,&alignment_check); #ifdef CONFIG_X86_MCE set_intr_gate_ist(18,&machine_check, MCE_STACK); #endif set_intr_gate(19,&simd_coprocessor_error); #ifdef CONFIG_IA32_EMULATION set_system_gate(IA32_SYSCALL_VECTOR, ia32_syscall); #endif set_intr_gate(KDB_VECTOR, call_debug); /* * Should be a barrier for any external CPU state. */ cpu_init(); }

總結如下:

中斷向量號 異常事件 Linux的處理程序
0 除法錯誤 Divide_error
1 調試異常 Debug
2 NMI中斷 Nmi
3 單字節,int 3 Int3
4 溢出 Overflow
5 邊界監測中斷 Bounds
6 無效操作碼 Invalid_op
7 設備不可用 Device_not_available
8 雙重故障 Double_fault
9 協處理器段溢出 Coprocessor_segment_overrun
10 無效TSS Incalid_tss
11 缺段中斷 Segment_not_present
12 堆棧異常 Stack_segment
13 一般保護異常 General_protection
14 頁異常 Page_fault
15 Spurious_interrupt_bug
16 協處理器出錯 Coprocessor_error
17 對齊檢查中斷 Alignment_check
0x80 系統調用 ia32_syscall
0xf9 內核調試 call_debug

上述中斷處理函數都是匯編語言編寫。一部分匯編直接處理完畢,一部分通過調用C函數幫助處理。
匯編代碼在linux/arch/x86_64/entry.S中,大部分都是調用C函數do_中斷處理函數名處理。
整理如下:

中斷向量號 異常事件 Linux匯編 調用c函數 處理結果
0 除法錯誤 Divide_error do_divide_error 發送SIGFPE信號
1 調試異常 Debug do_debug 發送SIGTRAP信號
2 NMI中斷 Nmi do_nmi
3 單字節,int 3 Int3 do_int3 發送SIGTRAP信號
4 溢出 Overflow do_overflow 發送SIGSEGV信號
5 邊界監測中斷 Bounds do_bounds 發送SIGSEGV信號
6 無效操作碼 Invalid_op do_invalid_op 發送SIGILL信號
7 設備不可用 Device_not_available math_state_restore 發送SIGSEGV信號
8 雙重故障 Double_fault do_double_fault
9 協處理器段溢出 Coprocessor_segment_overrun do_coprocessor_segment_overrun 發送SIGFPE信號
10 無效TSS Invalid_tss do_invalid_TSS 發送SIGSEGV信號
11 缺段中斷 Segment_not_present do_segment_not_present 發送SIGBUS信號
12 堆棧異常 Stack_segment do_stack_segment
13 一般保護異常 General_protection do_general_protection
14 頁異常 Page_fault do_page_fault 處理缺頁中斷
15 Spurious_interrupt_bug do_spurious_interrupt_bug
16 協處理器出錯 Coprocessor_error do_coprocessor_error 發送SIGFPE信號
17 對齊檢查中斷 Alignment_check do_alignment_check 發送SIGBUS信號
0x80 系統調用 ia32_syscall
0xf9 內核調試 call_debug do_call_debug

外部中斷初始化

中斷控制器硬件APIC分為兩種:本地APIC和全局APIC。本地APIC集成在CPU內部,每個CPU都有一個,用於處理本地中斷請求,CPU可以通過APIC向其他CPU發送中斷,現在主要用於CPU之間的通信(IPI)。全局APIC主要是連接外部設備,用於外部設備的中斷。在內核中斷初始化的時候,會初始化三個與IPI相關中斷。

void __init init_IRQ(void)
{
    int i;
    /**
    * 該函數主要是初始化硬件
    * 1. 初始化本地APIC控制芯片
    * 2. 初始化8259A芯片
    /
    init_ISA_irqs();
    /*
     * 清空32以後的中斷向量表。(除了系統調用和內核調試用的中斷號)
     */
    for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
        int vector = FIRST_EXTERNAL_VECTOR + i;
        if (i >= NR_IRQS)
            break;
        if (vector != IA32_SYSCALL_VECTOR && vector != KDB_VECTOR) { 
            set_intr_gate(vector, interrupt[i]);
    }
    }
// 多處理器通信中斷
#ifdef CONFIG_SMP

    set_intr_gate(FIRST_DEVICE_VECTOR, interrupt[0]);
    set_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);
    set_intr_gate(INVALIDATE_TLB_VECTOR, invalidate_interrupt);
    set_intr_gate(CALL_FUNCTION_VECTOR, call_function_interrupt);
#endif  
// 本地APIC中斷
#ifdef CONFIG_X86_LOCAL_APIC
    /* self generated IPI for local APIC timer */
    set_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);
    set_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);
    set_intr_gate(ERROR_APIC_VECTOR, error_interrupt);
#endif
    setup_timer();

    if (!acpi_ioapic)
        setup_irq(2, &irq2);
}

總結如下:

中斷向量號 中斷名 異常事件 中斷處理函數 調用c函數 處理結果
0xfc RESCHEDULE_VECTOR 處理器間中斷, 用於cpu之間同學,其他cpu要求重新調度 reschedule_interrupt smp_reschedule_interrupt 將線程調度標誌置為需要重新調度。之後內核檢查標誌的時候會重新調度線程
0xfd INVALIDATE_TLB_VECTOR 處理器間中斷, 用於cpu之間通信,其他cpu要求TLB緩存失效 invalidate_interrupt smp_invalidate_interrupt cpu刷新TLB
0xfa CALL_FUNCTION_VECTOR 處理器間中斷, 用於cpu之間通信,讓另外的cpu調用某個函數 call_function_interrupt smp_call_function_interrupt 函數數據通過call_data_struct傳送,cpu會調用該函數
0xef LOCAL_TIMER_VECTOR APIC定期器中斷 apic_timer_interrupt smp_apic_timer_interrupt 觸發定時器的軟中斷
0xff SPURIOUS_APIC_VECTOR 偽中斷 spurious_interrupt smp_spurious_interrupt 忽略
0xfe ERROR_APIC_VECTOR APIC錯誤 error_interrupt smp_error_interrupt 打印錯誤

0xfa中斷說明:
當cpu需要另一個cpu執行某個函數時,只需要初始化

struct call_data_struct {
    void (*func) (void *info);
    void *info;
    atomic_t started;
    atomic_t finished;
    int wait;
};

的結構體,然後發出一個0xfa中斷即可。

軟中斷初始化

軟中斷初始化分為兩部分:

  1. 初始化定時器時,會打開TIMER_SOFTIRQ的軟中斷,並設置中斷處理函數為run_timer_softirq。
  2. softirq_init函數執行,會打開TASKLET_SOFTIRQ和HI_SOFTIRQ,處理函數分別為 tasklet_action和 tasklet_hi_action。

軟中斷的線程處理機制就不說了。

Linux x86_64內核中斷初始化