在系統初始化期間,trap_init()函數將對中斷描述符表IDT進行第二次初始化(第一次只是建一張IDT表,讓其指向ignore_intr函數),而在這次初始化期間,系統的0~19號中斷(用于分NMI和異常的中斷向量)均被設置好。與此同時,用于系統調用的0x80號向量也已被設置。
然而,對于外部中斷的初始化 卻沒有在這個函數中進行。而是在函數init_IRQ中。
仔細想一想內核這樣做,的確是使代碼清晰又有條理。
1)trap_init -----> 內部中斷異常和NMI(中斷向量號:0~19)
2)?init_IRQ?? -----> 外部可屏蔽中斷??(中斷向量號:32~127,129~238)
在init_IRQ函數中,對IDT中斷描述符表進行了第三次完善(把相應的外部中斷對應的中斷向量進行填充)
410 for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
411 int vector = FIRST_EXTERNAL_VECTOR + i;
412 if (i >= NR_IRQS)
413 break;
414 if (vector != SYSCALL_VECTOR)
415 set_intr_gate(vector, interrupt[i]);
416 }
上述代碼解釋:
FIRST_EXTERNAL_VECTOR = 0x16;
設置中斷向量號32---NR_IRQS相應的中斷處理程序地址為數組interrupt[i]的內容。(除去128號中斷向量,已經這個向量號已經被用于系統調用)。
我們看一下interrupt數組的定義:這段程序時用匯編寫的。
540 .data
541 ENTRY(interrupt)
542 .text
543
544 vector=0
545 ENTRY(irq_entries_start)
546 RING0_INT_FRAME
547 .rept NR_IRQS
548 ALIGN
549 .if vector
550 CFI_ADJUST_CFA_OFFSET -4
551 .endif
552 1: pushl $~(vector)
553 CFI_ADJUST_CFA_OFFSET 4
554 jmp common_interrupt
555 .data
556 .long 1b
557 .text
558 vector=vector+1
559 .endr
上面的代碼開起來很亂,我們整理一下,使其更加易讀,但是又不失其本質。
.data
ENTRY(interrupt)
.long 1b //注意 如果把數據段放在這里那么1b要改為1f了,而且失去循環執行NR_IRQS次的功能。???????????????????? //我們只能人為的把他們想象成被循環執行了NR_IRQS次 :)
.text
vector=0
ENTRY(irq_entries_start)
.rept NR_IRQS
1: pushl $~(vector)
jmp common_interrupt
vector=vector+1
我們把數據段放在一起,代碼段放在一起。注意這里的代碼段是用來初始化數據段的。
我們看到:interrupt作為一個內存標簽,其內容為代碼段標號1所表示的地址。同時我們也注意到這個interrupt數組的所有項的內容都是一樣的:全部為“標號1”的符號地址。
每次外部中斷來臨時,硬件自動根據PIC或者APIC送出來的中斷類型碼(中斷向量號)去查找中斷描述符表的相應項,然后得到interrupt[n]的內容,繼而轉去執行標號1地址的代碼,而標號1的代碼僅將中斷號取反后壓入棧中,后立馬跳到common_interupt標號處去執行。至于為什么要把中斷向量號取反后壓棧,則是由于內核用正的相應號去表示系統調用號,用負號來表示中斷號。
細心的你有可能發現了一個問題:上述代碼片段只是向interrupt表示的內存地址處存入大量重復的4字節數據,卻沒有象C語言定義數組那樣定義interrupt[NR_IRQS-1],那么set_intr_gate這樣的函數是如何確切的指導interrupt是一個數組呢,而且數組的內容是函數指針?
例如:set_intr_gate(vector,interrupt[i]);是如何取interrupt[i]內容呢?
啰嗦了這么半天...
如下一個聲明就搞定了。[])(void);//函數指針數組
566 common_interrupt:
567 SAVE_ALL
568 TRACE_IRQS_OFF
569 movl %esp,%eax
570 call do_IRQ
571 jmp ret_from_intr
572 CFI_ENDPROC
common_interrupt首先執行宏SAVE_ALL保存中斷處理程序可能用到的寄存器(注意:cs eip ss esp由硬件自動保存)然后把棧頂指針傳給eax寄存器后(eax寄存器內容將作為do_IRQ函數的參數)調用do_IRQ進行中斷處理,返回后
執行ret_from_intr從中斷返回。