上篇博文對中斷描述符表(IDT)中異常和非屏蔽中斷部分的初始化做了說明,這篇文章將分析外部中斷部分的初始化。
在上篇博文中,可以看到,內核在setup_once匯編片段中,對中斷和異常部分做了初步的初始化,用early_idt_handlers函數的地址來初始化異常門描述符,用ignore_int函數地址來初始化剩下的中斷門描述符。接著,內核在trap_init函數中對IDT做了進一步的初始化,用有效的異常處理程序來初始化中斷向量號為0-31的描述符。細心的你應該可以發現,在這一步初始化過程中,僅僅對異常和非屏蔽中斷做了初始化(也就是中斷向量號前32個),并沒有對后256-32=224個中斷門描述符初始化,也就是說后244個中斷門描述符依然指向的是ignore_int這個無用的函數。下面將分析中斷門描述符的最終初始化。首先介紹下interrupt全局數組,該數組中裝有了所有的中斷處理程序,如下所示:(arch/x86/kernel/entrt_32.S)
1 .section .init.rodata,"a" 2 ENTRY(interrupt) 3 .section .entry.text, "ax" 4 .p2align 5 5 .p2align CONFIG_X86_L1_CACHE_SHIFT 6 ENTRY(irq_entries_start) 7 RING0_INT_FRAME 8 vector=FIRST_EXTERNAL_VECTOR 9 .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/710 .balign 3211 .rept 712 .if vector < NR_VECTORS13 .if vector <> FIRST_EXTERNAL_VECTOR14 CFI_ADJUST_CFA_OFFSET -415 .endif16 1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */17 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 618 jmp 2f19 .endif20 .PRevious21 .long 1b22 .section .entry.text, "ax"23 vector=vector+124 .endif25 .endr26 2: jmp common_interrupt27 .endr28 END(irq_entries_start)29 30 .previous31 END(interrupt)32 .previous
這段代碼定義了一個interrupt全局數組,如果不懂ATT匯編的話,這段代碼看起來非常吃力。下面筆者粗略分析下這段代碼,第1行聲明了一個數據段,第2行給這個數據段起了個名字,叫做‘inerrupt’,第3行又聲明了一個代碼段,該代碼段被包在了前邊的數據段當中,從第6行可看出這個代碼段名字叫做‘irq_entries_start’。接著4-5行說明了代碼段對齊的方式,接下來第7行給vector進行賦值,vector=32,實際上,interrup這個數組中存放的全是外部中斷,沒有異常,異常初始化已經在trap_init函數中完成了,而外部中斷的向量號從32開始,所以vector賦值32。接下來在第9,11行大家可以看到出現了偽指令.rept,這個偽指令是循環的意思,你可以把它當成for循環去理解,指令后邊的數字是循環次數。這個偽指令實際上告訴編譯器要把后邊的內容在內存中復制若干次。第9行的(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7值為32,要求其后的內容被循環復制32次。因此第9行和第11行合起來,就相當于一個雙重for循環,總共循環32*7=224次,這剛好就是外部中斷向量號的數量,每執行一次內部循環,就將一個外部中斷處理程序放入了一個數組元素中。接著第20行出現了.previous偽指令。該指令的意思是返回到上一個段中,在這里就是要返回到interrupt數據段中,第21行,在interrupt數據段中定義了一個long型數據,值為標號1,標號1實際上就是第16行代碼的地址。接著第22行又回到了當前代碼段中,讓vector自加1,然后第25行進入內重循環的下一次循環。第26行,標號2的這個指令夾在了內外兩重循環之間,說明每執行7次內循環就要將jmp common_interrupt復制一次。然后第27行進入外重循環的下一次循環??偣矆绦?2*7次循環后,這段代碼就結束了。通過使用.preivous偽指令,最終實際上就定義了兩個數組,一個是interrput數組,該數組的每個元素均為.long 1b(第21行),另外一個數組是irq_entries_start,該數組每個元素中放入了若干條匯編指令(16-18行,26行)。這就是.previous的用處,每在irq_entries_start數組中初始化完一個元素,立馬返回到interrupt數組中定義一個指向irq_entries_start數組中剛初始化過的元素的指針(.long 1b),作為interrupt數組的元素。最終interrput數組中存放了224個指針(每個中斷處理程序的地址),分別指向了irq_entries_start數組中的對應元素。irq_entries_start數組每個元素存放的是幾條匯編指令(這些匯編指令就是中斷處理程序的開頭公共部分)。而且,通過第26行,可以看到,irq_entries_start數組每個元素都包含jmp common_interrupt指令,跳入到一段公共的代碼中。
上邊的工作,內核只是把所有的外部中斷處理程序用兩個數組管理起來了,接下來,就要在IDT(中斷描述符表)中初始化所有外部中斷的門描述符。代碼如下:(arch/x86/kernel/irqinit.c)
1 void __init native_init_IRQ(void) 2 { 3 int i; 4 5 /* Execute any quirks before the call gates are initialised: */ 6 x86_init.irqs.pre_vector_init(); 7 8 apic_intr_init(); 9 10 /*11 * Cover the whole vector space, no vector can escape12 * us. (some of these will be overridden and become13 * 'special' SMP interrupts)14 */15 i = FIRST_EXTERNAL_VECTOR;16 for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {17 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */18 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);19 }20 21 if (!acpi_ioapic && !of_ioapic)22 setup_irq(2, &irq2);23 24 #ifdef CONFIG_X86_3225 irq_ctx_init(smp_processor_id());26 #endif27 }
從16-19行,可以看出,用interrupt數組中存放的所有中斷處理程序地址來初始化IDT的中斷門描述符。set_intr_gate函數上篇博文已經分析過來,這里不再分析。
至此,所有IDT中的異常和中斷門描述符就初始化完成了。
新聞熱點
疑難解答