Linux x86_64內核中斷初始化
阿新 • • 發佈:2018-03-31
linkage struct 內核調試 tasklet 多處理器 錯誤 star should action
中斷分類
Linux系統中,中斷分為:
硬中斷:由外部設備或者執行異常產生的需要快速處理的中斷。如缺頁中斷、定時器硬件中斷。
根據內部產生還是外部產生,分為:- 異常:異常是內部產生的中斷,不可屏蔽。
- 外部中斷:外部中斷是由外部設備產生的,可以屏蔽。
軟中斷:
? 軟中斷是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,÷_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中斷即可。
軟中斷初始化
軟中斷初始化分為兩部分:
- 初始化定時器時,會打開TIMER_SOFTIRQ的軟中斷,並設置中斷處理函數為run_timer_softirq。
- softirq_init函數執行,會打開TASKLET_SOFTIRQ和HI_SOFTIRQ,處理函數分別為 tasklet_action和 tasklet_hi_action。
軟中斷的線程處理機制就不說了。
Linux x86_64內核中斷初始化