LINUX內核隨記(一)——理解中斷(1)

該文章轉載自http://www.cnblogs.com/hustcat/archive/2009/08/11/1543889.html

一直認爲,理解中斷是理解內核的開始。中斷已經遠遠超過僅僅爲外圍設備服務的範疇,它是現代體系結構的重要組成部分。
1、基本輸入輸出方式
現代體系結構的基本輸入輸出方式有三種:
(1)程序查詢:
CPU週期性詢問外部設備是否準備就緒。該方式的明顯的缺點就是浪費CPU資源,效率低下。
但是,不要輕易的就認爲該方式是一種不好的方式(漂亮的女人不一定好,不漂亮的女人通常很可愛),通常效率低下是由於CPU在大部分時間沒事可做造成的,這種輪詢方式自有應用它的地方。例如,在網絡驅動中,通常接口(Interface)每接收一個報文,就發出一箇中斷。而對於高速網絡,每秒就能接收幾千個報文,在這樣的負載下,系統性能會受到極大的損害。
爲了提高系統性能,內核開發者已經爲網絡子系統開發了一種可選的基於查詢的接口NAPI(代表new API)。當系統擁有一個高流量的高速接口時,系統通常會收集足夠多的報文,而不是馬上中斷CPU。
(2)中斷方式
這是現代CPU最常用的與外圍設備通信方式。相對於輪詢,該方式不用浪費稀缺的CPU資源,所以高效而靈活。中斷處理方式的缺點是每傳送一個字符都要進行中斷,啓動中斷控制器,還要保留和恢復現場以便能繼續原程序的執行,花費的工作量很大,這樣如果需要大量數據交換,系統的性能會很低。
(3)DMA方式
通常用於高速設備,設備請求直接訪問內存,不用CPU干涉。但是這種方式需要DMA控制器,增加了硬件成本。在進行DMA數據傳送之前,DMA控制器會向CPU申請總線控制 權,CPU如果允許,則將控制權交出,因此,在數據交換時,總線控制權由DMA控制器掌握,在傳輸結束後,DMA控制器將總線控制權交還給CPU。

2、中斷概述
2.1、中斷向量
X86支持256箇中斷向量,依次編號爲0~255。它們分爲兩類:
(1)異常,由CPU內部引起的,所以也叫同步中斷,不能被CPU屏蔽;它又分爲Faults(可更正異常,恢復後重新執行),Traps(返回後執行發生trap指令的後一條指令)和Aborts(無法恢復,系統只能停機);
(2)中斷,由外部設備引起的。它又分爲可屏蔽中斷(INTR)和非可屏蔽中斷(NMI)。
Linux對256箇中斷向量分配如下:
(1)0~31爲異常和非屏蔽中斷,它實際上被Intel保留。
(2)32~47爲可屏蔽中斷。
(3)餘下的48~255用來標識軟中斷;Linux只用了其中一個,即128(0x80),用來實現系統調用。當用戶程序執行一條int 0x80時,就會陷入內核態,並執行內核函數system_call(),該函數與具體的架構相關。
2.2、可屏蔽中斷
X86通過兩個級連的8259A中斷控制器芯片來管理15個外部中斷源,如圖所示:


外部設備要使用中斷線,首先要申請中斷號(IRQ),每條中斷線的中斷號IRQn對應的中斷向量爲n+32,IRQ和向量之間的映射可以通過中斷控制器商端口來修改。X86下8259A的初始化工作及IRQ與向量的映射是在函數init_8259A()(位於arch/i386/kernel/i8259.c)完成的。
CPU通過INTR引腳來接收8259A發出的中斷請求,而且CPU可以通過清除EFLAG的中斷標誌位(IF)來屏蔽外部中斷。當IF=0時,禁止任何外部I/O請求,即關中斷(對應指令cli)。另外,中斷控制器有一個8位的中斷屏蔽寄存器(IMR),每位對應8259A中的一條中斷線,如果要禁用某條中斷線,相應的位置1即可,要啓用,則置0。
IF標誌位可以使用指令STI和CLI來設置或清除。並且只有當程序的CPL<=IOPL時纔可執行這兩條指令,否則將引起一般保護性異常(通常來說,in,ins,out,outs,cli,sti只有在CPL<=IOPL時才能執行,這些指令稱爲I/O敏感指令)。
以下一些操作也會影響IF標誌位:
    (1)PUSHF指令將EFLAGS內容存入堆棧,且可以在那裏修改。POPF可將已經修改過的內容寫入EFLAGS寄存器。
    (2)任務切換和IRET指令會加載EFLAGS寄存器。因此,可修改IF標誌。
    (3)通過中斷門處理一箇中斷時,IF標誌位被自動清除,從而禁止可盡屏蔽中斷。但是,陷阱門不會復位IF。
2.3、異常及非屏蔽中斷
異常就是CPU內部出現的中斷,也就是說,在CPU執行特定指令時出現的非法情況。非屏蔽中斷就是計算機內部硬件出錯時引起的異常情況。從上圖可以看出,二者與外部I/O接口沒有任何關係。Intel把非屏蔽中斷作爲異常的一種來處理,因此,後面所提到的異常也包括了非屏蔽中斷。在CPU執行一個異常處理程序時,就不再爲其他異常或可屏蔽中斷請求服務,也就是說,當某個異常被響應後,CPU清除EFLAG的中IF位,禁止任何可屏蔽中斷(IF不能禁止異常和非可屏蔽中斷)。但如果又有異常產生,則由CPU鎖存(CPU具有緩衝異常的能力),待這個異常處理完後,才響應被鎖存的異常。我們這裏討論的異常中斷向量在0~31之間,不包括系統調用(中斷向量爲0x80)。

2.4、中斷描述符表
2.4.1、中斷描述符
在實地址模式中,CPU把內存中從0開始的1K字節作爲一箇中斷向量表。表中的每個表項佔四個字節,由兩個字節的段地址和兩個字節的偏移量組成,這樣構成的地址便是相應中斷處理程序的入口地址。但是,在保護模式下,由四字節的表項構成的中斷向量表顯然滿足不了要求。這是因爲,除了兩個字節的段描述符,偏移量必用四字節來表示;要有反映模式切換的信息。因此,在保護模式下,中斷向量表中的表項由8個字節組成,中斷向量表也改叫做中斷描述符表IDT(Interrupt Descriptor Table)。其中的每個表項叫做一個門描述符(gate descriptor),“門”的含義是當中斷髮生時必須先通過這些門,然後才能進入相應的處理程序。門描述符的一般格式如下:

中斷描述符表中可放三類門描述符:
(1)中斷門(Interrupt gate)
其類型碼爲110,它包含一箇中斷或異常處理程序所在的段選擇符和段內偏移。控制權通過中斷門進入中斷處理程序時,處理器清IF標誌,即關中斷,以避免嵌套中斷的發生。中斷門中的DPL(Descriptor Privilege Level)爲0,因此,用戶態的進程不能訪問Intel的中斷門。所有的中斷處理程序都由中斷門激活,並全部限制在內核態。設置中斷門的代碼如下:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->//n爲中斷向量號,addr爲中斷處理程序地址,位於arch/i386/kernel/traps.c
void set_intr_gate(unsigned int n, void *addr)
{  
//type=14,dpl=0,selector=__KERNEL_CS
    _set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
}
Idt_table爲中斷描述符表,其定義位於arch/i386/kernel/traps.c中,如下:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->//中斷描述符表
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {00}, };
//描述符結構
struct desc_struct {
    unsigned 
long a,b;
};
(2)陷阱門(Trap gate)
其類型碼爲111,與中斷門類似,其唯一的區別是,控制權通過陷阱門進入處理程序時維持IF標誌位不變,也就是說,不關中斷。其設置代碼如下:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->static void __init set_trap_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table
+n,15,0,addr,__KERNEL_CS);
}
(3)任務門(Task gate)
IDT中的任務門描述符格式與GDT和LDT中的任務門格式相同,含有一個任務TSS段的選擇符,該任務用於處理異常或中斷,Linux用於處理Double fault。其設置代碼如下:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->static void __init set_task_gate(unsigned int n, unsigned int gdt_entry)
{
    _set_gate(idt_table
+n,5,0,0,(gdt_entry<<3));
}
它們各自的格式如下:

此外,在Linux中還有系統門(System gate),用於處理用戶態下的異常overflow,bound以及系統調用int 0x80;以及系統中斷門(system interrupt gate),用來處理int3,這樣彙編指令int3就能在用戶態下調用。
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->static void __init set_system_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table
+n,15,3,addr,__KERNEL_CS);
}
//設置系統調用門描述符,在trap.c中被trap_init()調用
set_system_gate(SYSCALL_VECTOR,&system_call);

//設置系統中斷門
static inline void set_system_intr_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table
+n, 143, addr, __KERNEL_CS);
}


//位於arch/i386/kernel/traps.c
void __init trap_init(void)
{
set_trap_gate(
0,&divide_error);
    set_intr_gate(
1,&debug);
    set_intr_gate(
2,&nmi);
    
//系統中斷門
    set_system_intr_gate(3&int3); /* int3-5 can be called from all */
    
//系統門
    set_system_gate(4,&overflow);
    set_system_gate(
5,&bounds);
    
    set_trap_gate(
6,&invalid_op);
    set_trap_gate(
7,&device_not_available);
    set_task_gate(
8,GDT_ENTRY_DOUBLEFAULT_TSS);
    set_trap_gate(
9,&coprocessor_segment_overrun);
    set_trap_gate(
10,&invalid_TSS);
    set_trap_gate(
11,&segment_not_present);
    set_trap_gate(
12,&stack_segment);
    set_trap_gate(
13,&general_protection);
    set_intr_gate(
14,&page_fault);
    set_trap_gate(
15,&spurious_interrupt_bug);
    set_trap_gate(
16,&coprocessor_error);
    set_trap_gate(
17,&alignment_check);
#ifdef CONFIG_X86_MCE
    set_trap_gate(
18,&machine_check);
#endif
    set_trap_gate(
19,&simd_coprocessor_error);

    set_system_gate(SYSCALL_VECTOR,
&system_call);
}
2.4.2、中斷描述表初始化
中斷描述表的最終初始化是init/main.c中的start_kernel()中完成的
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->asmlinkage void __init start_kernel(void)
{
//陷阱門初始化
    trap_init();
    
//中斷門初始化
    init_IRQ();
    
//軟中斷初始化
    softirq_init();
}
中斷門的設置是在init_IRQ()中完成的,如下:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->//位於arch/i386/kernel/i8259.c
void __init init_IRQ(void)
{
    
//調用init_ISA_irqs
    pre_intr_init_hook();
     
//設置中斷門
    for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
        
int vector = FIRST_EXTERNAL_VECTOR + i;
        
if (i >= NR_IRQS)
            
break;
        
//跳過系統調用的向量
        if (vector != SYSCALL_VECTOR) 
            set_intr_gate(vector, interrupt[i]);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章