目錄php
0. 引言 1. Linux 中斷的概念 2. 中斷處理流程 3. Linux 中斷相關的源代碼分析 4. Linux 硬件中斷 5. Linux 軟中斷 6. 中斷優先級 7. CPU在關中斷狀態下編程要注意的事項
0. 引言html
中斷是現代計算機體系結構的重要組成部分,咱們回顧歷史,現代體系結構的基本輸入輸出方式有三種node
1. 程序查詢: CPU週期性詢問外部設備是否準備就緒。該方式的明顯的缺點就是浪費CPU資源,效率低下。可是在特定的場景下這種"程序查詢"的方式還有有它的用武之地的 例如,在網絡驅動中,一般接口(Interface)每接收一個報文,就發出一箇中斷。而對於高速網絡,每秒就能接收幾千個報文,在這樣的負載下,系統性能會受到極大的損害。爲了提升系統性能,內核開發者已經爲網絡子系統開發了一種可選的基於查詢的接口NAPI(表明new API)。當系統擁有一個高流量的高速接口時,系統一般會收集足夠多的報文,而不是立刻中斷CPU 2. 中斷方式 這是現代CPU最經常使用的與外圍設備通訊方式。相對於輪詢,該方式不用浪費稀缺的CPU資源,因此高效而靈活。中斷處理方式的缺點是每傳送一個字符都要進行中斷,啓動中斷控制器,還要保留和恢復現場以便能繼續原程序的執行,花費的工做量很大,這樣若是須要大量數據交換,系統的性能會很低 3. DMA方式 一般用於高速設備,設備請求直接訪問內存,不用CPU干涉。可是這種方式須要DMA控制器,增長了硬件成本。在進行DMA數據傳送以前,DMA控制器會向CPU申請總線控制 權,CPU若是容許,則將控制權交出,所以,在數據交換時,總線控制權由DMA控制器掌握,在傳輸結束後,DMA控制器將總線控制權交還給CPU
今天咱們要重點學習的就是"中斷"這種輸入輸出機制linux
Relevant Link:ios
http://blog.sina.com.cn/s/blog_5d0e8d0d01019cds.html
1. Linux 中斷的概念程序員
今天咱們談論"中斷",會發現這個詞在不少場景下都會以不一樣的身份出現,咱們今天對這些概念進行一個梳理web
對於這張圖,有一點須要說明的是,在linux 2.6內核以後,系統調用已經不使用int 0x80的方式進行了,關於這部分的知識,請參閱另外一篇文章shell
http://www.cnblogs.com/LittleHann/p/4111692.html
0x1: 中斷的分類編程
翻閱不一樣的書籍和內核相關資料,咱們會發現關於中斷的分類有不少種標準,事實狀況也就是這樣的,根據不一樣的觀察角度,能夠有以下的分類c#
1. 異步(外部中斷) OR 同步(內部中斷):根據發生中斷的時機來分類
1. 異步中斷(外部中斷) 外部中斷是由外部設備引起的中斷,這類中斷的特色是中斷的發生徹底是"瞬發"的,例如 1) 鼠標 2) 鍵盤 2. 同步中斷(內部中斷) 從CPU的角度看,內部中斷是一個同步事件,它是執行某條指令時產生的,例如 1) 缺頁異常 2) 除零異常 3) 系統調用systemcall trap
2. 可屏蔽中斷 OR 不可屏蔽中斷:根據處於中斷狀態中是否能夠被其餘中斷遞歸打斷來分類
中斷的屏蔽基於CPU中的"中斷屏蔽寄存器"進行實現
1. 可被屏蔽中斷:容許延遲響應 2. 不可被屏蔽中斷:高實時場景需求,必須當即被響應
3. 硬件中斷 OR 軟件中斷:根據中斷源來分類
1. 軟件中斷 1) Ring3代碼中觸發的指令中斷 2) 操做系統自身觸發的缺頁中斷,這是操做系統提供的對CPU中斷的一個接口,以中斷號的形式進行編號 2. 硬件中斷 1) 外設處理過程當中產生的,經過硬件控制器通知cpu本身的狀態變化 2) 外部硬件設備觸發的硬件中斷(電平信號) 3) 硬中斷應該很快完成,纔能有快的響應,因此將一部分能夠延遲的處理從硬中斷裏獨立出來,當硬中斷處理完以後再處理這部分,就是軟中斷
能夠看到,中斷歸根結底是CPU提供的一種"硬件機制",是CPU的引腳配置總線和中斷控制器在最底層的硬件實現,咱們以Linux爲例,來深刻學習一下中斷的概念
0x2: 中斷控制器
須要明白的是,中斷最本質的概念是一種硬件機制,從單片機到intel CPU都具有中斷機制,CPU(微控制器)提供了專門的引腳用於實現中斷功能,咱們以51單片機進行概念解釋,在其餘更高級的CPU上本質道理都是相同的
1. 51單片機中有5箇中斷源 1) 中斷號: 0、中斷源: 1(最高)、中斷入口地址:外部中斷0 0003H 2) 中斷號: 1、中斷源: 2、中斷入口地址: 定時器0 000BH 3) 中斷號: 2、中斷源: 3、中斷入口地址: 外部中斷1 0013H 4) 中斷號: 3、中斷源: 4、中斷入口地址: 定時器1 0018H 5) 中斷號: 4、中斷源: 5、中斷入口地址: 串口總段 0023H 2. Linux/Windows在內核中提供的"軟中斷"是一個封裝後的概念,其實是CPU給內核單獨提供了一箇中斷引腳,用於實現軟中斷,由程序員控制/或者內核進行中斷響應處理 3. 全部的中斷,包括軟中斷和外設的中斷響應都最終都須要經過CPU上的中斷引腳由硬件方式實現 4. 中斷源當發生中斷事件時,向對應的中斷引腳發送降低沿電平信號,這個動做將將對應的中斷寄存器置位 5. 51單片機有6箇中斷寄存器,分別是 1) 總選通中斷寄存器EA,相似於Linux中的"關CPU"中斷,當這個寄存器未置位時,全部中斷將沒法響應,即全部中斷引腳將不響應 2) 每一箇中斷源都和一箇中斷引腳相連,對應一箇中斷寄存器,只有EA置位的前提下,中斷源對應的中斷寄存器纔有效 6. 中斷優先級是在全部中斷寄存器"更後面"的一個選通邏輯,經過與非門的bit選通邏輯,來控制優先響應哪些中斷 7. 每一箇中斷源都對應一箇中斷源,這是一個內置的硬件例程(也能夠經過軟件方式寫入),咱們說的軟中斷也有一個硬件例程,而軟中斷號能夠理解爲傳入的參數,進入軟中斷例程後,程序會根據中斷號進行一次再分發,分發到相應的軟中斷子程序中 8. CPU在每一個時鐘週期的stage5 s2降低沿會檢查全部的中斷寄存器,以此判斷當前是否有中斷事件發生
中斷控制器是鏈接設備和 CPU 的橋樑,一個設備產生中斷後,須要通過中斷控制器的轉發,才能最終到達 CPU。時代發展至今,中斷控制器經歷了幾個階段
1. PIC(Programmable Interrupt Controller 可編程中斷控制器): 在 UP(Uni-processor 單處理器) 上應用較廣 2. APIC (Advanced Programmable Interrupt Controller 高級可編程中斷控制器): 隨着 SMP (Symmetric Multiple Processor 對稱多處理器) 的流行,APIC 已廣爲流行並將最終取代 PIC
8259A (PIC) 管腳圖
1. IR0~IR7 (Interrupt Request0~7): 用於鏈接設備 2. INT: 鏈接CPU,當有中斷請求時,拉高該管腳(提升電平)以通知 CPU 中斷的到來 3. INTA: 鏈接CPU,CPU經過該管腳應答中斷請求,並通知PIC提交中斷的vector(中斷向量)到數據線 4. CS: 片選,用於將兩個8259A串聯成可鏈接15個設備的PIC
8259A中的寄存器
1. ICW(Initialization Command Word): 初始化命令寄存器,用於初始化8259A 2. OCW(Operation Command Word 操做命令字): 用於控制8259A 3. IRR(Interrupt Request Register 中斷請求寄存器): 共 8bit,對應 IR0~IR7 八個中斷管腳。當某個管腳的中斷請求到來後,若該管腳沒有被屏蔽,IRR 中對應的 bit 被置1。表示 PIC 已經收到設備的中斷請求,但還未提交給 CPU。 4. ISR(In Service Register 服務中寄存器): 共 8bit,每 bit 對應 IR0~IR7 八個中斷管腳。當 IRR 中的某個中斷請求被髮送給 CPU 後,ISR 中對應的 bit 被置1。表示中斷已發送給 CPU,但 CPU 還未處理完 5. IMR(Interrupt Mask Register 中斷屏蔽寄存器): 共 8bit,每 bit 對應IR0~IR7 八個中斷管腳。用於屏蔽中斷。當某 bit 置1時,對應的中斷管腳被屏蔽(經過電路上的與非門實現)
arch/x86/kernel/i8259.c 中經過位運算來開啓和關閉中斷
void disable_8259A_irq(unsigned int irq) { unsigned int mask = 1 << irq; unsigned long flags; // 用 spinlock 鎖住 spin_lock_irqsave(&i8259A_lock, flags); // 將 IRQ 的相應位置1,屏蔽中斷 cached_irq_mask |= mask; if (irq & 8) outb(cached_slave_mask, PIC_SLAVE_IMR); else outb(cached_master_mask, PIC_MASTER_IMR); // 解開自旋鎖 spin_unlock_irqrestore(&i8259A_lock, flags); } void enable_8259A_irq(unsigned int irq) { unsigned int mask = ~(1 << irq); unsigned long flags; // 用 spinlock 鎖住 spin_lock_irqsave(&i8259A_lock, flags); // 將 IRQ 的相應位置0,開啓中斷 cached_irq_mask &= mask; if (irq & 8) // IR2 管腳負責 8259A 的級聯,爲0時使用主片,爲1時使用從片 outb(cached_slave_mask, PIC_SLAVE_IMR); else outb(cached_master_mask, PIC_MASTER_IMR); // 解開自旋鎖 spin_unlock_irqrestore(&i8259A_lock, flags); }
PIC 的每一個管腳具備優先級,鏈接號碼較小的設備具備較高的中斷優先級,在 PIC 默認的 Full Nested 模式下,經過 PIC 發起中斷的流程以下
1. 一個或多個 IR 管腳上產生電平信號,若對應的中斷沒有被屏蔽,IRR 中相應的 bit 被置1,表示已接收中斷請求 2. PIC 拉高 INT 管腳通知 CPU 中斷髮生 3. CPU 經過 INTA 管腳應答 PIC,表示中斷請求收到 4. PIC 收到 INTA 應答後,將 IRR 中具備最高優先級的 bit 清零,並設置 ISR 中對應的 bit 5. CPU 經過 INTA 管腳第二次發出脈衝,PIC 收到後計算最高優先級中斷的 vector,並將它提交到數據線上,vector用於以外的中斷例程的尋址 6. 等待 CPU 寫 EOI (End of Interrupt)。收到 EOI 後(表示中斷處理結束),ISR 中最高優先級的 bit 被清零(中斷例程處理結束)。若是 PIC 處於 AEOI 模式,當第二個 INTA 脈衝收到後,ISR 中最高優先級的 bit 自動清零
PIC 還有優先級輪轉模式,即 PIC 在服務完一個管腳以後將其優先級臨時下降,並升高未服務管腳的優先級,以實現相似輪詢的模式,避免一個管腳持續發出中斷致使其餘設備"餓死"
下圖是一個典型的 PIC 中斷分配,管腳基本上都被古董級設備佔據了
arch/x86/kernel/i8259.c 中 8259A 引腳的分配
void init_8259A(int auto_eoi) { unsigned long flags; i8259A_auto_eoi = auto_eoi; spin_lock_irqsave(&i8259A_lock, flags); outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */ outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */ /* * outb_pic - this has to work on a wide range of PC hardware. */ outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */ /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64, to 0x20-0x27 on i386 */ outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR); /* 8259A-1 (the master) has a slave on IR2 */ outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR); if (auto_eoi) /* master does Auto EOI */ outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR); else /* master expects normal EOI */ outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR); outb_pic(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */ /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */ outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR); /* 8259A-2 is a slave on master's IR2 */ outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR); /* (slave's support for AEOI in flat mode is to be investigated) */ outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR); if (auto_eoi) /* * In AEOI mode we just have to mask the interrupt * when acking. */ i8259A_chip.mask_ack = disable_8259A_irq; else i8259A_chip.mask_ack = mask_and_ack_8259A; udelay(100); /* wait for 8259A to initialize */ outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */ outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */ spin_unlock_irqrestore(&i8259A_lock, flags); }
從代碼中能夠看出,PIC 能接的設備數量實在太少了,並且不支持多處理器,爲了使用 8259A 級聯鏈接較多的設備,能夠採用兩種方式
1. IRQ 共享:中斷處理程序執行多箇中斷服務程序(ISR),每一個 ISR 是一個與共享 IRQ 線相關的函數 2. IRQ 共享須要知足兩個條件 1) 每一個 ISR 都願意共享 IRQ,即 request_irq() 時指定了 IRQF_SHARED 2) 全部 ISR 具備相同的觸發條件(電平觸發或邊沿觸發、高低電平或上下邊沿) 3. IRQ 動態分配:在可能的最後時刻,才把 IRQ 線分配給一個設備
固然,APIC 是現代的解決方案。即便是 APIC,也須要使用 IRQ 共享
這張圖的核心: I/O APIC 的組成爲
1. 一組 24 條 IRQ 線 2. 一張 24 項的中斷重定向表 3. 可編程寄存器,經過 APIC 總線發送和接收 APIC 信息的一個信息單元
與 8259A 不一樣,中斷優先級不與引腳號相關聯,中斷重定向表中的每一項均可以被單獨編程以指明中斷向量和優先級、目標處理器和選擇處理器的方式
來自外部硬件設備的中斷以兩種方式在可用 CPU 之間分發
1. 靜態分發 2. 動態分發
0x3: 中斷描述符
Intel 提供了三種類型的中斷描述符
1. 任務門 2. 中斷門 3. 陷阱門描述符
1. 系統將全部的中斷信號統一進行了編號(一共256個: 0 ~ 255),這個號稱爲中斷向量,它們統一組織成IDT,具體哪一個中斷向量表示哪一種中斷有的是規定好的,也有的是在給定範圍內自行設定的 2. 中斷向量和中斷服務程序的對應關係主要是由IDT(中斷向量表)負責。操做系統在IDT中設置好各類中斷向量對應的中斷描述符,從概念類型上來講,Linux對這些中斷號進行了一次分類,Linux 使用與 Intel 稍有不一樣的分類,把中斷描述符分爲五類 1) 中斷門(interrupt gate) 用戶態的進程不能訪問Intel中斷門(門的DPL字段爲0)。全部的Linux中斷處理程序都經過中斷門激活,並所有限制在內核態 set_intr_gate(n,addr) 上述系統調用在 IDT 的第 n 個表項插入一箇中斷門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置爲中斷處理程序的地址 addr,DPL 字段設置爲0 2) 系統門(system gate) 用戶態的進程能夠訪問Intel陷阱門(trap)(門的DPL字段爲3)。經過系統門來激活三個Linux異常處理程序,它們的向量分別對應用戶態下對應的彙編指令 2.1) 4: into 2.2) 5: bound 2.3) 128: int $0x80(系統調用) set_system_gate(n,addr) 3) 系統中斷門(system interrupt gate) 可以被用戶態進程訪問的Intel中斷門(門的DPL字段爲3)。與向量3相關的異常處理程序是由系統中斷門激活的,所以,在用戶態可使用匯編語言指令int3 set_system_intr_gate(n,addr) 4) 陷阱門(trap gate) 用戶態的進程不能訪問的一個Intel陷阱門(門的DPL字段爲0)。大部分Linux異常處理程序(例如除零、缺頁異常)都經過陷阱門來激活 set_trap_gate(n,addr) 5) 任務門(task gate) 不能被用戶態進程訪問的Intel任務門(門的DPL字段爲0)。Linux對"Double fault"異常的處理程序是由任務門激活的 set_task_gate(n,gdt) //IDT自己的位置是由idtr寄存器保存的,固然這個地址也是由OS填充的
門中的段選擇符中存放一個TSS的全局描述符表的指針,該TSS中包含要被激活的函數
在 IDT 中插入門的函數定義在 \linux-2.6.32.63\arch\x86\include\asm\desc.h 中,這些函數以不一樣的參數調用內部函數 _set_gate()。_set_gate 調用兩個內部函數
1. pack_gate: 設置門的數據結構 1) 中斷號 2) 門類型 3) 處理函數地址 4) DPL 5) ist 6) 目錄段寄存器 2. write_idt_entry: 宏定義爲 native_write_idt_entry,用 memcpy 將設置好的門寫入 IDT
\linux-2.6.32.63\arch\x86\include\asm\desc.h
static inline void _set_gate(int gate, unsigned type, void *addr, unsigned dpl, unsigned ist, unsigned seg) { gate_desc s; pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); /* * does not need to be atomic because it is only done once at * setup time */ write_idt_entry(idt_table, gate, &s); } static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func, unsigned dpl, unsigned ist, unsigned seg) { gate->offset_low = PTR_LOW(func); gate->segment = __KERNEL_CS; gate->ist = ist; gate->p = 1; gate->dpl = dpl; gate->zero0 = 0; gate->zero1 = 0; gate->type = type; gate->offset_middle = PTR_MIDDLE(func); gate->offset_high = PTR_HIGH(func); } #define write_idt_entry(dt, entry, g) \ native_write_idt_entry(dt, entry, g) static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate) { memcpy(&idt[entry], gate, sizeof(*gate)); }
0x4: 中斷數據結構
在 Linux 中,中斷描述符的核心數據結構是 include/linux/irq.h 中的 irq_desc 結構體。每一個 irq_desc 實例描述一條中斷線
struct irq_desc { //1. interrupt number for this descriptor unsigned int irq; //2. irq stats per cpu unsigned int *kstat_irqs; #ifdef CONFIG_INTR_REMAP //3. iommu with this irq struct irq_2_iommu *irq_2_iommu; #endif /* 4. highlevel irq-events handler [if NULL, __do_IRQ()],中斷事件處理函數 handle_irq 是函數指針,指向 kernel/irq/chip.c 中的中斷事件處理函數 1) handle_simple_irq 2) handle_level_irq 3) handle_fasteoi_irq 4) handle_edge_irq 5) handle_percpu_irq 這個函數指針是由 kernel/irq/chip.c 中的 __set_irq_handler() 設置 */ irq_flow_handler_t handle_irq; /* 5. low level interrupt hardware access,irq_chip 指針,描述了一些硬件信息 chip 是 irq_chip 結構體指針,include/linux/irq.h 中的 irq_chip 結構體定義了對每根中斷線的底層硬件操做 struct irq_chip { /* 1. name for /proc/interrupts 包含一個短的字符串,用於標識硬件控制器 1) IA-32: XTPIC 2) AMD64: IO-APIC */ const char *name; //2. start up the interrupt (defaults to ->enable if NULL) unsigned int (*startup)(unsigned int irq); //3. shut down the interrupt (defaults to ->disable if NULL) void (*shutdown)(unsigned int irq); //4. enable the interrupt (defaults to chip->unmask if NULL) void (*enable)(unsigned int irq); //5. disable the interrupt (defaults to chip->mask if NULL) void (*disable)(unsigned int irq); //6. start of a new interrupt void (*ack)(unsigned int irq); //7. mask an interrupt source void (*mask)(unsigned int irq); //8. ack and mask an interrupt source void (*mask_ack)(unsigned int irq); //9. unmask an interrupt source void (*unmask)(unsigned int irq); //10. end of interrupt - chip level void (*eoi)(unsigned int irq); //11. end of interrupt - flow level void (*end)(unsigned int irq); //12. set the CPU affinity on SMP machines int (*set_affinity)(unsigned int irq, const struct cpumask *dest); //13. resend an IRQ to the CPU int (*retrigger)(unsigned int irq); //14. set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ int (*set_type)(unsigned int irq, unsigned int flow_type); //15. enable/disable power-management wake-on of an IRQ int (*set_wake)(unsigned int irq, unsigned int on); //16. function to lock access to slow bus (i2c) chips void (*bus_lock)(unsigned int irq); //17. function to sync and unlock slow bus (i2c) chips void (*bus_sync_unlock)(unsigned int irq); /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD //18. release function solely used by UML void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ //19. obsoleted by name, kept as migration helper const char *typename; }; */ struct irq_chip *chip; //6. MSI descriptor struct msi_desc *msi_desc; //7. per-IRQ data for the irq_chip methods void *handler_data; //8. platform-specific per-chip private data for the chip methods, to allow shared chip implementations void *chip_data; /* IRQ action list */ /* 9. the irq action chain,irqaction 指針鏈 action 是 irqaction 結構體指針,指向一個 irqaction 鏈表。irqaction 在 include/linux/interrupt.h 中定義,每一個結構體描述一箇中斷處理程序 struct irqaction { // 中斷處理程序的函數指針 irq_handler_t handler; unsigned long flags; // 中斷處理程序名稱,顯示在 /proc/interrupts 中 const char *name; // 設備 ID void *dev_id; // 指向鏈表中的下一個 irqaction 結構體 struct irqaction *next; // 中斷通道號 int irq; // 在 /proc 文件系統中的目錄 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; }; */ struct irqaction *action; /* IRQ status */ /* 10. status information,IRQ 線狀態標誌 status 是描述 IRQ 線狀態的一組標誌。在同一文件中宏定義 #define IRQ_TYPE_NONE 0x00000000 /* Default, unspecified type */ #define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */ #define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */ #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) #define IRQ_TYPE_LEVEL_HIGH 0x00000004 /* Level high type */ #define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */ #define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */ #define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */ /* Internal flags */ #define IRQ_INPROGRESS 0x00000100 /* IRQ handler active - do not enter! */ #define IRQ_DISABLED 0x00000200 /* IRQ disabled - do not enter! */ #define IRQ_PENDING 0x00000400 /* IRQ pending - replay on enable */ #define IRQ_REPLAY 0x00000800 /* IRQ has been replayed but not acked yet */ #define IRQ_AUTODETECT 0x00001000 /* IRQ is being autodetected */ #define IRQ_WAITING 0x00002000 /* IRQ not yet seen - for autodetection */ #define IRQ_LEVEL 0x00004000 /* IRQ level triggered */ #define IRQ_MASKED 0x00008000 /* IRQ masked - shouldn't be seen again */ #define IRQ_PER_CPU 0x00010000 /* IRQ is per CPU */ #define IRQ_NOPROBE 0x00020000 /* IRQ is not valid for probing */ #define IRQ_NOREQUEST 0x00040000 /* IRQ cannot be requested */ #define IRQ_NOAUTOEN 0x00080000 /* IRQ will not be enabled on request irq */ #define IRQ_WAKEUP 0x00100000 /* IRQ triggers system wakeup */ #define IRQ_MOVE_PENDING 0x00200000 /* need to re-target IRQ destination */ #define IRQ_NO_BALANCING 0x00400000 /* IRQ is excluded from balancing */ #define IRQ_SPURIOUS_DISABLED 0x00800000 /* IRQ was disabled by the spurious trap */ #define IRQ_MOVE_PCNTXT 0x01000000 /* IRQ migration from process context */ #define IRQ_AFFINITY_SET 0x02000000 /* IRQ affinity was set from userspace*/ #define IRQ_SUSPENDED 0x04000000 /* IRQ has gone through suspend sequence */ #define IRQ_ONESHOT 0x08000000 /* IRQ is not unmasked after hardirq */ #define IRQ_NESTED_THREAD 0x10000000 /* IRQ is nested into another, no own handler thread */ #ifdef CONFIG_IRQ_PER_CPU # define CHECK_IRQ_PER_CPU(var) ((var) & IRQ_PER_CPU) # define IRQ_NO_BALANCING_MASK (IRQ_PER_CPU | IRQ_NO_BALANCING) #else # define CHECK_IRQ_PER_CPU(var) 0 # define IRQ_NO_BALANCING_MASK IRQ_NO_BALANCING #endif */ unsigned int status; /* nested irq disables */ //11. disable-depth, for nested irq_disable() calls unsigned int depth; /* nested wake enables */ //12. enable depth, for multiple set_irq_wake() callers unsigned int wake_depth; /* For detecting broken IRQs */ //13. stats field to detect stalled irqs,中斷計數 unsigned int irq_count; /* Aging timer for unhandled count */ //14. aging timer for unhandled count,沒法處理的中斷計數 unsigned long last_unhandled; //15. stats field for spurious unhandled interrupts unsigned int irqs_unhandled; //16. locking for SMP spinlock_t lock; #ifdef CONFIG_SMP //17. IRQ affinity on SMP,多處理器中的處理器親和性 cpumask_var_t affinity; //18. node index useful for balancing unsigned int node; #ifdef CONFIG_GENERIC_PENDING_IRQ //19. pending rebalanced interrupts cpumask_var_t pending_mask; #endif #endif //20. number of irqaction threads currently running atomic_t threads_active; //21. wait queue for sync_irq to wait for threaded handlers wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS //22. /proc/irq/ procfs entry,在 /proc 文件系統中的目錄 struct proc_dir_entry *dir; #endif //23. flow handler name for /proc/interrupts output,中斷名稱 const char *name; } ____cacheline_internodealigned_in_smp;
irq_desc 在 kernel/irq/handle.c 中被使用,此文件是 IRQ 機制的核心入口,描述了各中斷線
sourcecode\kernel\irq\handle.c
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { /* IRQ的最大數量主要取決於輔助CPU管理IRQ的輔助芯片 1. alpha: 32箇中斷 2. wildfire: 2048箇中斷 3. IA-64: 256箇中斷 4. IA-3二、8256A控制器: 16箇中斷 */ [0 ... NR_IRQS-1] = { .status = IRQ_DISABLED, // 默認屏蔽中斷 .chip = &no_irq_chip, // 沒有與 chip 相關聯 .handle_irq = handle_bad_irq, // 未知(壞的)IRQ 處理程序,輸出 IRQ 信息供調試,更新 CPU IRQ 次數計數器,迴應 IRQ .depth = 1, // 默認是第一層(沒有嵌套中斷) .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock), // 尚未自旋鎖 } };
綜上所述,內核中的中斷描述符表是一個 irq_desc 數組,數組的每一項描述一根中斷線的信息,包括芯片中斷處理程序、底層硬件操做函數、註冊的中斷處理程序鏈表等
中斷向量表能夠經過 /proc/interrupts 查看
CPU0 CPU1 0: 659558570 0 IO-APIC-edge timer 1: 8 0 IO-APIC-edge i8042 6: 2 0 IO-APIC-edge floppy 8: 0 0 IO-APIC-edge rtc 9: 0 0 IO-APIC-level acpi 12: 63 0 IO-APIC-edge i8042 169: 41216157 0 IO-APIC-level xen-platform-pci 177: 23 0 IO-APIC-level uhci_hcd:usb1 NMI: 0 0 LOC: 659562619 659562537 ERR: 0 MIS: 0
在Linux 2.6的內核開發期間,重構出了一個新的通用的IRQ子系統。它可以以統一的方式處理不一樣的中斷控制器和不一樣類型的中斷
1. 高層ISR(high-level interrupt service routines 高層中斷服務例程): irq_flow_handler_t handle_irq 針對設備驅動程序端(或其餘內核組件)的中斷,執行由此引發的全部必要的工做。例如,若是設備使用中斷通知一些數據已經到達,那麼高層ISR的工做應該是將數據複製到適當的位置 2. 中斷電流處理(interrup flow handling): struct irq_chip *chip 處理不一樣的中斷電流類型之間的各類差異,如 1) 邊沿觸發(edge-triggering): 硬件經過感知線路上的點位差來檢測中斷 2) 電平觸發(level-triggering): 根據特定的電勢值檢測中斷,與電勢是否改變無關 http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:struct irq_chip //搜索:struct irqaction 在2.6以後的內核代碼中,電流處理的實現已經被極大地簡化了,內核代碼封裝了大量的體系結構相關的代碼,並在高層提供了一個幾乎可使用於全部硬件的通用框架 2.1. 設置控制器硬件 內核提供了一系列的標準函數,用於註冊irq_chip和設置電流程序 1) extern int set_irq_chip(unsigned int irq, struct irq_chip *chip); 將一個IRQ芯片以irq_chip實例的形式關聯到某個特定的中斷 2) static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle); 3) static inline void set_irq_chained_handler(unsigned int irq, irq_flow_handler_t handle); 爲某個給定的IRQ編號設置電流處理程序 4) extern void set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip, irq_flow_handler_t handle); 5) extern void set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip, irq_flow_handler_t handle, const char *name); 這是一個快捷方式,它至關於連續調用上述的各函數 2.2. 電流處理 1) 邊沿觸發中斷 2) 電平觸發中斷 3) 其餘中斷類型 3. 芯片級硬件封裝(chip-level hardware encapsultion) 須要與在電子學層次上產生中斷的底層硬件直接通訊,該抽象層能夠視爲中斷控制器的某種"設備驅動程序"
咱們繼續回到irq_desc[NR_IRQS]這個保存中斷處理程序的全局數組上來,用於表示IRQ描述符的結構定義請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html
從內核中高層代碼的角度來看,每一個IRQ均可以由該結構徹底描述,事實上,操做系統的每一種機制在背後都必定有一個完善的數據結構提供支持,回到上面的那張IRQ子系統的架構圖
其中的3個抽象層在該結構(struct irq_desc)中表示以下
1. 電流層ISR: 由handle_irq提供 handler_data能夠指向任意數據,該數據能夠是特定於IRQ或處理程序的。每當發生中斷時,特定於體系結構的代碼都會調用handle_irq。該函數負責使用chip中提供的特定於控制器的方法,進行處理中斷所必需的一些底層操做 2. action提供了一個操做鏈: 須要在中斷髮生時執行 由中斷通知的設備驅動程序,能夠將與之相關的處理程序函數放置在此處 3. 電流處理和芯片相關操做被封裝在chip中 chip_data指向可能與chip相關的任意數據 4. name指定了電流層處理程序的名稱,將顯示在/proc/interrupts中。對邊沿觸發中斷,一般是"edge",對電平觸發中斷,一般是"level" 5. delth: 用於肯定IRQ電路是啓用的仍是禁用的 1) 0: 表示啓用 2) 正值: 表示禁用 6. irq_count、irq_unhandled字段提供了一些統計量,可用於檢測停頓和未處理,但持續發生的中斷。即假中斷(spurious interrupt) 7. status: 描述了IRQ的當前狀態 IRQ不只能夠在處理程序安裝期間改變其狀態,並且能夠在運行時改變,根據status當前的值,內核很容易獲知某個IRQ的狀態,而無需瞭解底層實現的硬件相關特性 irq.h中定義了各類表示當前狀態的常數,可用於描述IRQ電路當前的狀態。每一個常數表示位串中的一個置爲的標誌位(能夠同時設置)
0x5: 中斷的初始化
中斷機制的初始化分爲三步
1. arch/x86/kernel/head_32.S 中 setup_IDT 2. init/main.c 的 start_kernel() 中的 trap_init() 3. init/main.c 的 start_kernel() 中的 init_IRQ()
1. setup_IDT
\linux-2.6.32.63\arch\x86\kernel\head_32.S
setup_idt: lea ignore_int,%edx movl $(__KERNEL_CS << 16),%eax movw %dx,%ax /* selector = 0x0010 = cs */ movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ lea idt_table,%edi mov $256,%ecx rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx jne rp_sidt .macro set_early_handler handler,trapno lea \handler,%edx movl $(__KERNEL_CS << 16),%eax movw %dx,%ax movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ lea idt_table,%edi movl %eax,8*\trapno(%edi) movl %edx,8*\trapno+4(%edi) .endm set_early_handler handler=early_divide_err,trapno=0 set_early_handler handler=early_illegal_opcode,trapno=6 set_early_handler handler=early_protection_fault,trapno=13 set_early_handler handler=early_page_fault,trapno=14 ret
2. trap_init()
trap_init() 定義於 arch/x86/kernel/traps.c,做用是設置中斷向量
1. 初始化 APIC 映射表 2. 調用 set_trap_gate、set_intr_gate、set_task_gate、set_system_gate 等,初始化中斷描述符表 3. 調用 set_system_gate,初始化系統調用 4. 將已設置的中斷向量置保留位 5. 將已設置的系統調用置保留位 6. 初始化 CPU 做爲屏障 7. 執行 trap_init 的鉤子函數
3. init_IRQ
init_IRQ() 定義於 arch/x86/kernel/paravirt.c,由如下二者組成
1. paravirt_ops.init_IRQ() 2. native_init_IRQ(): 定義於 arch/x86/kernel/i8259.c。該函數主要將 IDT 未初始化的各項初始化爲中斷門
0x6: 內核接口
中斷處理程序不是編譯內核時就徹底肯定的,所以要爲開發者留下編程接口
2.6.17 內核引入了 generic IRQ 機制,支持 i38六、x86-64 和 ARM 三個體系結構。generic IRQ 層的引入,是爲了剝離 IRQ flow 和 IRQ chip 過於緊密的耦合。爲驅動開發者提供通用的 API 來 request/enable/disable/free 中斷,而不須要知道任何底層的中斷控制器細節,這些中斷 API 是在內核中用 EXPORT_SYMBOL 導出的
1. 請求中斷
/source/include/linux/interrupt.h
/* 1. irq: 中斷通道號,無符號整數 2. handler: 中斷處理程序的函數指針 (irq_return_t isr_func(int irq, void *dev_id)) 3. irqflags: 標誌位 1) IRQF_SHARED: 共享中斷通道 2) IRQF_DISABLED: 中斷處理程序執行時關中斷 3) IRQF_SAMPLE_RANDOM: 隨機發生中斷,可用於產生隨機數 4) IRQF_TRIGGER_LOW:2.6.26 中沒有,低電平有效 5) IRQF_TRIGGER_HIGH: 2.6.26 中沒有,高電平有效 6) IRQF_TRIGGER_RISING: 2.6.26 中沒有,上升沿有效 7) IRQF_TRIGGER_FALLING: 2.6.26 中沒有,降低沿有效 4. name: 名稱,顯示在 /proc/interrupts 中 5. dev_id:設備 ID,區分不一樣的設備 */ request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { return request_threaded_irq(irq, handler, NULL, flags, name, dev); }
2. 清除中斷
\linux-2.6.32.63\kernel\irq\manage.c
/** * free_irq - free an interrupt allocated with request_irq * @irq: Interrupt line to free * @dev_id: Device identity to free * * Remove an interrupt handler. The handler is removed and if the * interrupt line is no longer in use by any driver it is disabled. * On a shared IRQ the caller must ensure the interrupt is disabled * on the card it drives before calling this function. The function * does not return until any executing interrupts for this IRQ * have completed. * * This function must not be called from interrupt context. 1. irq: 中斷通道號,無符號整數 2. dev_id: 請求中斷時指定的設備 ID */ void free_irq(unsigned int irq, void *dev_id) { struct irq_desc *desc = irq_to_desc(irq); if (!desc) return; chip_bus_lock(irq, desc); kfree(__free_irq(irq, dev_id)); chip_bus_sync_unlock(irq, desc); } EXPORT_SYMBOL(free_irq);
3. 啓用中斷
void enable_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); unsigned long flags; if (!desc) return; chip_bus_lock(irq, desc); spin_lock_irqsave(&desc->lock, flags); __enable_irq(desc, irq, false); spin_unlock_irqrestore(&desc->lock, flags); chip_bus_sync_unlock(irq, desc); } EXPORT_SYMBOL(enable_irq);
4. 關閉中斷
void disable_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); if (!desc) return; disable_irq_nosync(irq); if (desc->action) synchronize_irq(irq); } EXPORT_SYMBOL(disable_irq);
5. 關閉中斷 (無等待)
disable_irq 會保證存在的 IRQ handler 完成操做,而 disable_irq_nosync 當即關中斷並返回。事實上,disable_irq 首先調用 disable_irq_nosync,而後調用 synchronize_irq 同步
void disable_irq_nosync(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); unsigned long flags; if (!desc) return; chip_bus_lock(irq, desc); spin_lock_irqsave(&desc->lock, flags); __disable_irq(desc, irq, false); spin_unlock_irqrestore(&desc->lock, flags); chip_bus_sync_unlock(irq, desc); } EXPORT_SYMBOL(disable_irq_nosync);
6. 同步中斷 (多處理器)
void synchronize_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); unsigned int status; if (!desc) return; do { unsigned long flags; /* * Wait until we're out of the critical section. This might * give the wrong answer due to the lack of memory barriers. */ while (desc->status & IRQ_INPROGRESS) cpu_relax(); /* Ok, that indicated we're done: double-check carefully. */ spin_lock_irqsave(&desc->lock, flags); status = desc->status; spin_unlock_irqrestore(&desc->lock, flags); /* Oops, that failed? */ } while (status & IRQ_INPROGRESS); /* * We made sure that no hardirq handler is running. Now verify * that no threaded handlers are active. */ wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active)); } EXPORT_SYMBOL(synchronize_irq);
7. 設置 IRQ 芯片
kernel/irq/chip.c: set_irq_chip()
8. 設置 IRQ 類型
kernel/irq/chip.c: set_irq_type()
9. 設置 IRQ 數據
kernel/irq/chip.c: set_irq_data()
10. 設置 IRQ 芯片數據
kernel/irq/chip.c: set_irq_chip_data()
Relevant Link:
http://home.ustc.edu.cn/~boj/courses/linux_kernel/2_int.html
2. 中斷處理流程
0x1: CPU的中斷處理流程
每一個可以發出中斷請求的硬件設備控制器都有一條名爲 IRQ 的輸出線。全部現有的 IRQ 線都與一個名爲可編程中斷控制器(PIC)的硬件電路的輸入引腳相連,可編程中斷控制器執行下列動做
1. 監視 IRQ 線,檢查產生的信號。若是有兩條以上的 IRQ 線上產生信號,就選擇引腳編號較小的 IRQ 線(優先級較高) 2. 若是一個引起信號出如今 IRQ 線上 1) 把接收到的引起信號轉換成對應的向量號 2) 把這個向量存放在中斷控制器的一個 I/O 端口(0x20、0x21),從而容許 CPU 經過數據總線讀此向量 3) 把引起信號發送處處理器的 INTR 引腳,即產生一箇中斷 4) 等待,直到 CPU 經過把這個中斷信號寫進可編程中斷控制器的一個 I/O 端口來確認它;當這種狀況發生時,清 INTR 線 3. 返回第1步,繼續循環
當執行了一條指令後,CS和eip這對寄存器包含下一條將要執行的指令的邏輯地址。在處理那條指令以前,CPU就會在相應的時鐘脈衝到來時從總線上讀取中斷請求對應的中斷向量(對於"異常、系統調用"那樣的"軟中斷",由於中斷向量是直接給出的,因此和經過IRQ(中斷請求)線發送的硬件中斷請求不一樣,不會再專門去取其對應的中斷向量),控制單元會檢查在運行前一條指令時是否已經發生了一箇中斷或異常。若是發生了一箇中斷或異常,那麼控制單元執行下列操做
1. 肯定與中斷或異常關聯的向量i(0 ≤ i ≤ 255) CPU根據獲得的中斷向量到IDT表裏找到該向量對應的"中斷描述符"(中斷門),中斷描述符裏保存着中斷服務程序的"段選擇符" 2. 根據取得的段選擇符到GDT中找相應的段描述符 讀由idtr寄存器指向的 IDT表中的第i項(IDT表項中包含的是一箇中斷門或一個陷阱門),從gdtr寄存器得到GDT的基地址,並在GDT中查找,以讀取IDT表項中的選擇符所標識的段描述符。這個描述符指定中斷或異常處理程序所在段的基地址 3. 確信中斷是由受權的(中斷)發生源發出的 首先將"當前特權級CPL"(存放在cs寄存器的低兩位)與"段描述符"(存放在GDT中)的描述符"特權級DPL"比較 1) 若是CPL小於DPL,就產生一個"General protection"異常,由於中斷處理程序的特權不能低於引發中斷的程序的特權(便可中斷) 2) 對於編程異常,則作進一步的安全檢查:比較CPL與處於IDT中的門描述符的DPL,若是DPL小於CPL,就產生一個"General protection"異常。這最後一個檢查能夠避免用戶應用程序訪問特殊的陷阱門或中斷門 4. 檢查是否發生了特權級的變化 也就是說,CPL是否不一樣於所選擇的段描述符的DPL。若是是,控制單元必須開始使用與新的特權級相關的棧。經過執行如下步驟來作到這點 1) 讀tr寄存器,以訪問運行進程的TSS段 2) 用與新特權級相關的棧段和棧指針的正確值裝載ss和esp寄存器。這些值能夠在TSS中找到 3) 在新的棧中保存ss和esp之前的值,這些值定義了與舊特權級相關的棧的邏輯地址 5. 若是故障已發生,用引發異常的指令地址裝載CS和eip寄存器,從而使得這條指令能再次被執行 7. 在棧中保存eflags、CS及eip的內容 8. 若是異常產生了一個硬件出錯碼,則將它保存在棧中 9. 裝載cs和eip寄存器,其值分別是IDT表中第i項門描述符的段選擇符和偏移量字段。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址 10. 創建一個適當的環境,即保存現場(entry path) CPU開始利用棧保護被暫停執行的程序的現場,進入路徑的關鍵任務有: 1) 從用戶態棧切換到和心態棧(內存棧的切換) 2) 保存用戶應用程序當前的寄存器狀態,以便在中斷活動結束後恢復 11. 調用處理程序自身: 跳轉到中斷服務程序的第一條指令開始執行 CPU利用中斷服務程序的段描述符將其第一條指令的地址加載到cs和eip寄存器中,開始執行中斷服務程序。這意味着先前的程序被暫停執行,中斷服務程序正式開始工做 12. 將系統還原,即還原現場(exit path): 中斷服務程序處理完畢,恢復執行先前中斷的程序 退出路徑的關鍵任務有: 1) 調度器是否應該選擇一個新進程代替舊的進程 2) 是否有信號必須投遞到原進程 3) 從中斷返回後,只有確認了以上1.)、2.)問題後,內核才能完成其常規任務,即還原寄存器集合、切換到用戶態棧、切換到適用於用戶應用程序的適當的處理器狀態
下圖顯示了整個流程
中斷例程尋址
中斷處理流程中的棧變化狀況
0x2: 保存中斷信息
Linux 內核的中斷處理機制自始至終貫穿着 "重要的事立刻作,不重要的事推後作" 的思想
中斷處理程序首先要作:
1. 將中斷號壓入棧中,以便找到對應的中斷服務程序 2. 將當前寄存器信息壓入棧中,以便中斷退出時恢復上下文 //這兩步都是不可重入的。所以在進入中斷服務程序時,CPU 已經自動禁止了本 CPU 上的中斷響應,即中斷響應的時候是關中斷的
0x3: 處理中斷
以中斷髮生時寄存器的信息爲參數,調用 arch/x86/kernel/irq32.c 中的 do_IRQ 函數
1. 保存寄存器上下文 2. 調用 irq_enter: \linux-2.6.32.63\kernel\softirq.c /* void irq_enter(void) { int cpu = smp_processor_id(); rcu_irq_enter(); if (idle_cpu(cpu) && !in_interrupt()) { /* * Prevent raise_softirq from needlessly waking up ksoftirqd * here, as softirq will be serviced on return from interrupt. */ local_bh_disable(); tick_check_idle(cpu); _local_bh_enable(); } __irq_enter(); } */ 3. 若是可用空間不足 1KB,可能會引起棧溢出,輸出內核錯誤信息 4. 若是 thread_union 是 4KB 的,進行一些特殊處理 5. 調用 desc->handle_irq(irq, desc),調用 __do_IRQ() (kernel/irq/handle.c) 6. 執行 irq_exit(),在 kernel/softirq.c 中: 1) 遞減中斷計數器 2) 檢查是否有軟中斷在等待執行,如有則執行軟中斷 3) 若是使用了無滴答內核看是否是該休息了 7. 恢復寄存器上下文,跳轉到 ret_from_intr (跳轉點早在 common_interrupt 中就被指定了)
在中斷處理過程當中,咱們反覆看到對自旋鎖的操做。在單處理器系統上,spinlock 是沒有做用的;在多處理器系統上,因爲同種類型的中斷可能連續產生,同時被幾個 CPU 處理(注意,應答中斷芯片是緊接着得到自旋鎖後,位於整個中斷處理流程的前部,所以在中斷處理流程的其他部分,中斷芯片能夠觸發新的中斷並被另外一個 CPU 開始處理),若是沒有自旋鎖,多個 CPU 可能同時訪問 IRQ 描述符,形成混亂。所以在訪問 IRQ 描述符的過程當中須要有 spinlock 保護
0x4: 從中斷返回
整個處理過程是持續佔有CPU的(除開中斷狀況下可能被新的中斷打斷外),這樣致使
1. 連續的低優先的中斷可能持續佔有 CPU, 而高優先的某些進程則沒法得到 CPU 對於這個問題,較新的 linux 內核增長了 ksoftirqd 內核線程,若是持續處理的軟中斷超過必定數量,則結束中斷處理過程,喚醒 ksoftirqd,由它來繼續處理 2. 中斷處理的這幾個階段中不能調用可能致使睡眠的函數 須要注意的是,關中斷的時候不能調用可能致使睡眠的函數,緣由以下 1) 睡眠是經過系統調用實現的 2) 系統調用是經過中斷陷門實現的用戶態/內核態切換 3) 在關中斷狀況下,CPU沒法響應這個陷門中斷請求 4) 睡眠系統調用的發起者一直處於等待睡眠喚醒,致使haung住 對於這個問題,linux 內核提供了 workqueue(工做隊列)機制,定義一個 work 結構(包含了處理函數),而後在上述的中斷處理的幾個階段的某一步中調用 schedule_work 函數,work 便被添加到 workqueue 中,等待處理。工做隊列有着本身的處理線程, 這些 work 被推遲到這些線程中去處理。處理過程只可能發生在這些工做線程中,不會發生在內核中斷處理路徑中,因此能夠睡眠
0x5: 編寫中斷處理程序
編寫一個簡單的中斷處理程序 (catchirq) 做爲內核模塊,演示捕獲網卡中斷
catchirq.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/timer.h> #define DEBUG #ifdef DEBUG #define MSG(message, args...) printk(KERN_DEBUG "catchirq: " message, ##args) #else #define MSG(message, args...) #endif MODULE_LICENSE("GPL"); MODULE_AUTHOR("boj"); int irq; char *interface; // module_param(name, type, perm) module_param(irq, int, 0644); module_param(interface, charp, 0644); int irq_handle_function(int irq, void *device_id) { static int count = 1; MSG("[%d] Receive IRQ at %ld\n", count, jiffies); count++; return IRQ_NONE; } int init_module() { if (request_irq(irq, irq_handle_function, IRQF_SHARED, interface, (void *)&irq)) { MSG("[FAILED] IRQ register failure.\n"); return -EIO; } MSG("[OK] Interface=%s IRQ=%d\n", interface, irq); return 0; } void cleanup_module() { free_irq(irq, &irq); MSG("IRQ is freed.\n"); }
Makefile
obj-m := catchirq.o KERNELDIR := /lib/modules/$(shell uname -r)/build default: make -C $(KERNELDIR) M=$(shell pwd) clean: make -C $(KERNELDIR) M=$(shell pwd) clean /* make sudo insmod catchirq.ko interface=eth1 irq=21 */
Relevant Link:
http://www.cnblogs.com/LittleHann/p/3850655.html http://www.cnblogs.com/LittleHann/p/3850653.html http://www.cnblogs.com/LittleHann/p/3871630.html http://blog.csdn.net/droidphone/article/details/7445825 http://blog.sina.com.cn/s/blog_5ffeae360100ftwt.html https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CB8QFjAB&url=%68%74%74%70%3a%2f%2f%64%6f%77%6e%6c%6f%61%64%2e%69%6e%74%65%6c%2e%63%6f%6d%2f%64%65%73%69%67%6e%2f%70%72%6f%63%65%73%73%6f%72%2f%6d%61%6e%75%61%6c%73%2f%32%35%33%36%36%35%2e%70%64%66&ei=mtoPVaCvN42F8gWaxoC4Bw&usg=AFQjCNHw995h0UJxe5XBo2nbeA1xam7QTg&bvm=bv.88528373,d.dGc&cad=rjt http://home.ustc.edu.cn/~boj/courses/linux_kernel/2_int.html
3. Linux 中斷相關的源代碼分析
中斷是一個CPU硬件級的機制,是CPU爲上層提供的一種響應機制,就像一棵樹的根,由於有了中斷,纔有了上層的一些頗有用的功能,例如
1. 系統調用(在2.6之後,系統調用不採用中斷而是採用sysenter進入內核了) 2. 鍵盤鼠標外設 3. 硬盤的數據傳輸 4. 網卡的數據通訊 ...
咱們從源代碼角度逐步分析一下CPU中斷的整個邏輯流程
0x1: 切換到和內核態
到內核態的切換,是基於每一箇中斷以後由處理器自動執行的彙編代碼的(暫不考慮Linux sysenter)。該代碼是C語言和彙編語言的混合體,位於:sourcecode\arch\x86\kernel\entry_32.S中,其中定義了各個入口點,在中斷髮生時處理器能夠將控制流轉到這些入口點。一般,只有那些最爲必要的操做直接在彙編語言代碼中執行,內核試圖儘快的返回到常規的C代碼,由於C代碼更容易處理。爲此,必須建立一個環境。在C語言中調用函數時,須要將所需的數據(返回地址和參數)按必定的順序放到棧上,在用戶態和內核態之間切換時,還須要將最重要的寄存器保存到棧上,以便之後恢復。這2個操做由平臺相關的彙編代碼執行。
sourcecode\arch\x86\kernel\entry_32.S
.. common_interrupt: addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */ SAVE_ALL TRACE_IRQS_OFF movl %esp,%eax call do_IRQ jmp ret_from_intr ENDPROC(common_interrupt) CFI_ENDPROC ..
在大多數平臺上,控制流接下來傳遞到C函數do_IRQ,其實現也是平臺相關的,但在Linux 2.6內核以後,狀況仍然獲得了很大的簡化,根據平臺不一樣,該函數的參數或者是處理器寄存器的集合
1. x86 \linux-3.15.5\arch\x86\kernel\irq.c __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs) 2. arm \linux-3.15.5\arch\arm\kernel\irq.c asmlinkage void __exception_irq_entry asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
pt_regs用於保存內核使用的寄存器集合,各個寄存器的值被依次壓棧(經過彙編代碼),在C函數調用以前,一直保存在棧上(爲了遵循C函數的調用約定)。pt_regs的定義能夠確保棧上的各個寄存器項與該結構的各個成員相對應,這些值並非僅僅保存用於後續的使用,C代碼也能夠讀取這些值
struct pt_regs的定義是平臺相關的,由於不一樣的處理器提供了不一樣的寄存器集合,pt_regs中包含了內核使用的寄存器,其中不包含的寄存器,可能只能由用戶態應用程序使用
1. IA-32 CPU體系結構下,pt_regs一般定義以下 \linux-3.15.5\arch\x86\include\asm\ptrace.h struct pt_regs { unsigned long bx; unsigned long cx; unsigned long dx; unsigned long si; unsigned long di; unsigned long bp; unsigned long ax; unsigned long ds; unsigned long es; unsigned long fs; unsigned long gs; unsigned long orig_ax; unsigned long ip; unsigned long cs; unsigned long flags; unsigned long sp; unsigned long ss; }; #else /* __i386__ */ struct pt_regs { unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long bp; unsigned long bx; /* arguments: non interrupts/non tracing syscalls only save up to here*/ unsigned long r11; unsigned long r10; unsigned long r9; unsigned long r8; unsigned long ax; unsigned long cx; unsigned long dx; unsigned long si; unsigned long di; unsigned long orig_ax; /* end of arguments */ /* cpu exception frame or undefined */ unsigned long ip; unsigned long cs; unsigned long flags; unsigned long sp; unsigned long ss; /* top of stack page */ }; 2. PA-Risc CPU體系結構下 \linux-3.15.5\arch\parisc\include\uapi\asm\ptrace.h struct pt_regs { unsigned long gr[32]; /* PSW is in gr[0] */ __u64 fr[32]; unsigned long sr[ 8]; unsigned long iasq[2]; unsigned long iaoq[2]; unsigned long cr27; unsigned long pad0; /* available for other uses */ unsigned long orig_r28; unsigned long ksp; unsigned long kpc; unsigned long sar; /* CR11 */ unsigned long iir; /* CR19 */ unsigned long isr; /* CR20 */ unsigned long ior; /* CR21 */ unsigned long ipsw; /* CR22 */ };
咱們知道,在IA-32體系結構的系統上,被引起中斷的編號(中斷號)保存在orig_eax的高8位中,這是個很重要的概念,即傳統的"基於80中斷的系統調用"、以及"外設引起的中斷",都必須將中斷號置入eax寄存器中,而後再觸發trap陷門
0x2: 執行中斷服務例程(ISR interrupt service routines)
在觸發了CPU中斷,ENTRY_32.S將寄存器集合的當前狀態傳遞到do_IRQ
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs) { //將一個指向寄存器集合的指針保存在一個全局的CPU變量中(中斷髮生以前,變量中保存的舊指針會保留下來,供後續使用)。須要訪問寄存器集合的中斷處理程序,能夠從該變量中訪問 struct pt_regs *old_regs = set_irq_regs(regs); /* high bit used in ret_from_ code */ unsigned vector = ~regs->orig_ax; unsigned irq; /* irq_enter負責更新一些統計量,對於具有動態時鐘週期特性的系統,若是系統已經有很長一段時間沒有發生時鐘中斷,則更新全局計時變量jiffiles 接下來,調用所述IRQ註冊的ISR的任務委託給體系結構無關的函數generic_handle_irq,它調用irq_desc[irq]->handle_irq來激活電流控制處理程序 */ irq_enter(); exit_idle(); irq = __this_cpu_read(vector_irq[vector]); if (!handle_irq(irq, regs)) { ack_APIC_irq(); if (irq != VECTOR_RETRIGGERED) { pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)\n", __func__, smp_processor_id(), vector, irq); } else { __this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED); } } /* irq_exit負責記錄一些統計量,另外還要調用do_softirq來處理任何待決的軟件IRQ */ irq_exit(); set_irq_regs(old_regs); return 1; }
在實現處理程序例程時,必需要注意一些要點,這些會極大地影響系統的性能和穩定性
1. 限制 須要注意的是,內核代碼的運行會處於兩種上下文狀態 1) 中斷上下文(interrupt context) 2) 常規上下文 在實現ISR(interrupt service routines)時,ISR必定是在"中斷上下文(interrupt context)"中執行的,爲了區分當前內核的這兩種狀況,內核提供了in_interrupt函數,用於指明當前是否在處理中斷 /* 中斷上下文和普通上下文的區別之處在於 0x1: 中斷是異步執行的,它能夠在任什麼時候間發生。於是從用戶空間來看,處理程序例程不是在一個明肯定義的環境中執行。這種狀況下,禁止訪問用戶空間,特別是與用戶空間地址之間來回複製內存數據的行爲。 例如:網絡驅動程序不能將接收到的數據直接轉發到等待的應用程序,由於內核沒法肯定等待數據的應用程序此時是否正在運行(是否得到CPU調度資源) 0x2: 中斷上下文中不能調用調度器,於是不能自願地放棄控制權 換句話說,這個時候不能使用自旋鎖等操做,由於在關中斷的狀況下,CPU已經不響應外部的中斷請求了,這個時候進行CPU調度會直接致使panic 0x3: 處理程序例程不能進入睡眠狀態。只有在外部事件致使狀態改變並喚醒進程時,才能解除睡眠狀態。但中斷上下文不容許中斷,進程睡眠後,內核只能永遠等待下去,由於也不能調用調度器,不能選擇進程來執行。 要特別注意的是:只確保處理程序例程的直接代碼不進入睡眠狀態這是不夠的,其中調用的全部過程和函數(以及這些函數的子函數/子過程,以此類推)都不能進入睡眠狀態 */ 2. 實現處理程序 中斷處理程序只能使用兩種返回值: 1) 正確地處理了IRQ: 返回IRQ_HANDLED 2) ISR不負責該ISR: 返回IRQ_NONE
0x3: 退出路徑
當ISR執行完畢後,代碼邏輯繼續回到ENTRY_32.S中,代碼對當前內核棧和寄存器進行現場恢復,準備回到用戶態繼續運行
ENTRY(resume_userspace) LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx andl $_TIF_WORK_MASK, %ecx # is there any work to be done on # int/exception return? jne work_pending jmp restore_all END(ret_from_exception)
Relevant Link:
深刻linux內核架構(中文版).pdf 14章
4. Linux 硬件中斷
0x1: 硬中斷的嵌套
linux下的硬中斷處理是能夠嵌套的,而且沒有優先級。也就是說,一箇中斷能夠打斷正在執行的中斷(同種中斷除外)。無優先級地支持硬中斷嵌套有兩個主要緣由
1. 短期內接受更多的中斷,能夠有大的設備控制吞吐量 2. 無優先級能夠簡化內核,提升移植性
0x2: 硬中斷Linux實現流程
硬中斷的具體linux實現流程是
1. 硬中斷的彙編處理-> 2. do_IRQ-> 3. handle_irq-> 4. handle_edge_irq(handle_level_irq)-> 5. handle_irq_event-> 6. 具體設備的硬中斷處理
同種中斷不嵌套是經過設置該種中斷的數據結構的IRQD_IRQ_INPROGRESS標誌位來屏蔽(本cpu和其它cpu的同種中斷)的,置位表示已經在處理該種中斷了,同種中斷會判斷此標誌而退出,同時置上IRQS_PENDING標誌位表示此種中斷還需繼續處理。本質上相似於軟中斷的防止嵌套的套路
硬中斷的提速很重要,無論是拆分出軟中斷仍是中斷線程化,都是但願硬中斷不要過多影響用戶進程(特別是實時進程),畢竟有些硬中斷和某些高優先級用戶進程相比,其實並不重要
Relevant Link:
http://blog.csdn.net/droidphone/article/details/7445825 http://mp.weixin.qq.com/s?__biz=MzAxNjM3MDkyOQ==&mid=205285282&idx=1&sn=c2ac2b0c70b75089984d8d38e423cd4a&wm=3333_2001#rd
5. Linux 軟中斷
回到以前的那張CPU中斷示意圖,CPU的中斷機制是CPU提供的一種硬件機制,從中斷源來講,有兩種中斷
1. 來自外設硬件的硬件中斷,電氣級別的中斷觸發 2. 由CPU自身執行中觸發的軟中斷
軟中斷(software interrupt)是徹底用軟件實現的,內核藉助軟中斷來獲知異常狀況的發生,而該狀況將在稍後由專門的處理程序例程解決。內核在do_IRQ末尾處理全部待決軟中斷,於是能夠確保軟中斷可以按期獲得處理。從一個更抽象的角度來看,能夠將軟中斷描述爲一種延遲到稍後時刻執行的內核活動。
例如,網卡接收數據包,從網卡產生中斷信號,CPU將網絡數據包拷貝到內核,而後進行協議棧的處理,最後將數據部分傳遞給用戶空間,這個過程均可以說是中斷處理函數須要作的部分,但硬件中斷處理僅僅作從網卡拷貝數據的工做,而協議棧的處理的工做就交給"可延遲執行"部分處理
而軟中斷,正是"可延遲處理"部分的一種機制,之因此叫軟中斷,是由於相似於硬件中斷的過程
1. 產生中斷信號 2. 維護軟中斷向量 3. 進行中斷處理
0x1: 軟中斷的執行流程
1. 初始化: 定義一個新的可延遲函數,這個操做一般在內核自身初始化或加載過模塊時進行 2. 激活: 標記一個可延遲函數爲"掛起"狀態(可延遲函數的下一輪調度中可執行),激活能夠在任什麼時候後進行(便是正在處理中斷) 3. 屏蔽: 有選擇的屏蔽一個可延遲函數,這樣,即便它被激活,內核也不執行它 4. 執行: 執行一個掛起的可延遲函數和同類型的其餘全部掛起的可延遲函數,執行是在特定的時間內進行的
0x2: 軟中斷的類型
enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS };
Linux內核須要將枚舉值轉化爲字符串數組,\linux-3.15.5\kernel\softirq.c
const char * const softirq_to_name[NR_SOFTIRQS] = { "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL", "TASKLET", "SCHED", "HRTIMER", "RCU" };
0x3: 軟中斷涉及的數據結構
軟中斷機制的核心部分是一個表(softirq_vec),它包含32個softirq_action類型的的數據項
\linux-3.15.5\include\linux\interrupt.h
struct softirq_action { //指向處理程序例程的指針,在軟中斷髮生時由內核執行該處理程序例程 void (*action)(struct softirq_action *); };
該數據結構的定義是和體系結構無關的,即它和下層的CPU中斷已經沒有直接耦合關係了,而軟中斷機制的整個實現也是如此
0x4: 軟中斷的初始化
咱們知道,內部中斷(軟中斷)和外部中斷(硬件中斷)的區別,對於內部中斷的初始化,主要是設置中斷向量表(IDT)。而對於外部中斷,除了中斷向量表以外,還要初始化中斷控制器(8259A),以及中斷控制器相關的管理結構
軟中斷必須先註冊,而後內核才能執行軟中斷
\linux-3.15.5\kernel\softirq.c
void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action; }
open_softirq在softirq_vec表中指定的位置寫入新的軟中斷,在softirq_init()函數中循環調用,對全局變量softirq_vec(軟中斷向量表)進行賦值設置
void __init softirq_init(void) { int cpu; for_each_possible_cpu(cpu) { per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head; per_cpu(tasklet_hi_vec, cpu).tail = &per_cpu(tasklet_hi_vec, cpu).head; } open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); }
0x5: 軟中斷的激活
\linux-3.15.5\kernel\softirq.c
asmlinkage __visible void __do_softirq(void) { unsigned long end = jiffies + MAX_SOFTIRQ_TIME; unsigned long old_flags = current->flags; int max_restart = MAX_SOFTIRQ_RESTART; struct softirq_action *h; bool in_hardirq; __u32 pending; int softirq_bit; int cpu; /* * Mask out PF_MEMALLOC s current task context is borrowed for the * softirq. A softirq handled such as network RX might set PF_MEMALLOC * again if the socket is related to swap */ current->flags &= ~PF_MEMALLOC; /* 首先要確認當前不處於中斷上下文中,若是處於中斷上下文,則當即結束,由於軟中斷用於執行ISR中非時間關鍵部分,因此其代碼自己必定不能在中斷處理程序內調用 經過local_softirq_pending,肯定當前CPU軟中斷位圖中所置位的比特位 */ pending = local_softirq_pending(); account_irq_enter_time(current); __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); in_hardirq = lockdep_softirq_start(); cpu = smp_processor_id(); restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); local_irq_enable(); h = softirq_vec; while ((softirq_bit = ffs(pending))) { unsigned int vec_nr; int prev_count; h += softirq_bit - 1; vec_nr = h - softirq_vec; prev_count = preempt_count(); kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); h->action(h); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count, preempt_count()); preempt_count_set(prev_count); } rcu_bh_qs(cpu); h++; pending >>= softirq_bit; } local_irq_disable(); pending = local_softirq_pending(); if (pending) { if (time_before(jiffies, end) && !need_resched() && --max_restart) goto restart; wakeup_softirqd(); } lockdep_softirq_end(in_hardirq); account_irq_exit_time(current); __local_bh_enable(SOFTIRQ_OFFSET); WARN_ON_ONCE(in_interrupt()); tsk_restore_flags(current, old_flags, PF_MEMALLOC); }
softirq_vec中的action函數在一個while循環中針對各個待決的軟中斷被調用。在處理了全部標記出的軟中斷以後,內核檢查在此期間是否有新的軟中斷標記到位圖中,這種操做會一直持續下去,直至在執行全部處理程序以後沒有新的未處理軟中斷未知
0x6: 軟中斷的屏蔽
0x7: 軟中斷的執行
0x8: 軟中斷的嵌套
同種軟中斷不能夠嵌套,但能夠並行在不一樣CPU上,即便是同種類型
Relevant Link:
http://rock3.info/blog/2013/11/20/%E8%BD%AF%E4%B8%AD%E6%96%AD%E5%8E%9F%E7%90%86/ http://blog.csdn.net/droidphone/article/details/7518428http://www.crashcourse.ca/wiki/index.php/Softirqs
6. 中斷優先級
在CPU硬件這個概念級別上,CPU對中斷的處理順序以下
1. 對內核來講,假設當前有兩個設備同時發出中斷請求 2. CPU首先處理優先級最高的中斷 3. 中斷控制器向CPU報告優先級高的中斷(中斷A),低優先級的中斷(中斷B)被延遲 4. 內核把當前運行級別提高到高優先級的IRQL 5. 在高優先級的中斷處理例程中,若是出現了一個比當前優先級更高優先級的IRQL請求(中斷C),則將當前中斷處理例程(中斷A)放入就緒隊列掛起 6. 當最高優先級的中斷處理例程(中斷C)處理完畢以後,而後處理延遲的調度請求(中斷A、中斷B) /* 中斷A->中斷C->中斷A->中斷B */ 7. 當所有中斷都處理完畢後,就會處理延遲的軟件中斷(即在do_IRQ的末尾檢測是否有軟中斷須要處理) 8. 最後再返回用戶態
在CPU的中斷處理過程當中,有一個隱含的優先級,對於Linux內核咱們一樣能夠模仿windows內核使用IRQL優先級的概念,把統一的優先級稱爲IRQL,每一個級別有一個對應的IRQL,優先級別越高,對應的IRQL越高
0x1: 高級可編程中斷控制器(Advanced Programmable Interrupt Controller APIC)
8259A只適用於單處理器,爲了知足多處理器的須要,出現了高級可編程中斷控制器
大多數x86平臺都包含了APIC,每個CPU內部都有一個本地APIC,本地APIC能夠產生時鐘中斷,能夠向其餘的處理髮送處理器間中斷等。系統能夠有一個或多個I/O APIC,每一個I/O APIC支持24箇中斷出入信號,I/O APIC和Local APIC之間經過總線鏈接
APIC中的每一個LINTn和IRQn分別有一個64位配置寄存器,被稱爲Interrupt Redirection Table,這些寄存器被映射到內存地址空間
詳細的信息請參閱《Intel® 64 and IA-32 Architectures Developer's Manual: Vol. 3A》 http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html
0x2: APIC初始化
系統啓動階段須要根據配置,從新設置CPU的中斷向量等相關信息,這些配置是硬件相關的,爲此Intel指定了MultiProcessor Specification。它規定了主板設計者必須把硬件相關的信息按照統一的數據格式集成到BIOS中,這樣操做系統就能夠經過BIOS獲取到相關信息
\linux-3.15.5\arch\x86\kernel\mpparse.c
void __init default_find_smp_config(void) { unsigned int address; /* * FIXME: Linux assumes you have 640K of base ram.. * this continues the error... * * 1) Scan the bottom 1K for a signature * 2) Scan the top 1K of base RAM * 3) Scan the 64K of bios */ if (smp_scan_config(0x0, 0x400) || smp_scan_config(639 * 0x400, 0x400) || smp_scan_config(0xF0000, 0x10000)) return; /* * If it is an SMP machine we should know now, unless the * configuration is in an EISA bus machine with an * extended bios data area. * * there is a real-mode segmented pointer pointing to the * 4K EBDA area at 0x40E, calculate and scan it here. * * NOTE! There are Linux loaders that will corrupt the EBDA * area, and as such this kind of SMP config may be less * trustworthy, simply because the SMP table may have been * stomped on during early boot. These loaders are buggy and * should be fixed. * * MP1.4 SPEC states to only scan first 1K of 4K EBDA. */ address = get_bios_ebda(); if (address) smp_scan_config(address, 0x400); }
\linux-3.15.5\arch\x86\include\asm\bios_ebda.h
/* * Returns physical address of EBDA. Returns 0 if there is no EBDA. */ static inline unsigned int get_bios_ebda(void) { /* * There is a real-mode segmented pointer pointing to the * 4K EBDA area at 0x40E. */ unsigned int address = *(unsigned short *)phys_to_virt(0x40E); address <<= 4; return address; /* 0 means none */ }
BIOS會提供一個Intel中規定的MP table,它的頭4個字節爲_MP_,smp_scan_config()就是根據在BIOS數據區中尋找這個表,若是找到就打印:Scan for SMP in [mem %#010lx-%#010lx]
/source/arch/x86/kernel/irqinit.c
static void __init apic_intr_init(void) { smp_intr_init(); #ifdef CONFIG_X86_THERMAL_VECTOR alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt); #endif #ifdef CONFIG_X86_MCE_THRESHOLD alloc_intr_gate(THRESHOLD_APIC_VECTOR, threshold_interrupt); #endif #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC) /* self generated IPI for local APIC timer */ alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt); /* IPI for X86 platform specific use */ alloc_intr_gate(X86_PLATFORM_IPI_VECTOR, x86_platform_ipi); #ifdef CONFIG_HAVE_KVM /* IPI for KVM to deliver posted interrupt */ alloc_intr_gate(POSTED_INTR_VECTOR, kvm_posted_intr_ipi); #endif /* IPI vectors for APIC spurious and error interrupts */ alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt); alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt); /* IRQ work interrupts: */ # ifdef CONFIG_IRQ_WORK alloc_intr_gate(IRQ_WORK_VECTOR, irq_work_interrupt); # endif #endif }
在apic_intr_init()中,內核爲Local APIC設置了一些新的中斷處理函數,對Local APIC中的配置寄存器進行配置後,這樣當Local APIC產生中斷時,就會調用對應的中斷處理函數了
7. CPU在關中斷狀態下編程要注意的事項
0x1: 關中斷下內核內存申請的限制
典型的在kprobe的CPU關中斷狀況下,使用kmalloc、vmalloc須要注意一些限制
1. 不能使用vmalloc 1) vmalloc可能致使缺頁中斷 2) vmalloc在內部使用了kmalloc(.., PAGE_KERNEL),這可能致使在申請內存的時候的睡眠,由於這個使用CPU沒法響應中斷,隨機致使kernel panic 2. 若是使用kmalloc 1) 不能使用PAGE_KERNEL,緣由和vmalloc的"2)"是相同的 2) 必須使用GFP_ATOMIC
關於kmalloc、vmalloc的相關知識,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/4113830.html
0x2: 關中斷下內核中禁止使用信號量這種鎖
因爲信號量可能引發進程切換,可是在中斷環境中是不容許進程切換的,不然會引發Kernel Panic
爲了解決這個問題,應該採用自旋鎖(spinlock)
關於Linux內核中鎖的相關知識,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/4116368.html
0x3: 關中斷下內核中禁止使用copy_from_user、strncpy_from_user進行KernelSpace <-> UserSpace之間複製內存數據
在中斷狀態下,禁止使用copy_from_user、strncpy_from_user進行與用戶空間地址之間來回複製內存數據的行爲,由於
1. copy_from_user、strncpy_from_user都是在進行從用戶態到內核態的內存複製 2. 內核沒法肯定等待數據的應用程序此時是否正在運行(是否得到CPU調度資源) 3. 在copy_from_user、strncpy_from_user運行中,有必定機率發生當前正在複製的UserSpace內存被Page Out,這個時候進行內存複製就會觸發缺頁中斷(Page Fault),缺頁中斷會引起一個CPU中斷 4. 在Kprobe關中斷狀況下,回調Handler函數中引起的CPU中斷是沒法獲得響應的 5. copy_from_user、strncpy_from_user在複製過程當中引起的Page Fault,不能獲得響應,只會被自身彙編代碼中的.fixup函數段捕獲,並打印: do_page_fault....,但其實這個時候已是不正常現象了 6. Linux在發生了Page Fault以後,不會馬上Kernel Panic,而是繼續運行,可是此時系統已經處於不正常狀態了,若是繼續運行,最終可能致使Panic
copy_from_user的內核代碼分析
1. http://lxr.free-electrons.com/source/arch/x86/include/asm/uaccess.h#L688 2. http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L681 3. http://lxr.free-electrons.com/source/arch/x86/include/asm/uaccess.h#L88 4. http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L583 5. http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L531
strncpy_from_user的內核源代碼分析
http://www.cs.fsu.edu/~baker/devices/lxr/http/source/2.6.25.8/linux/arch/x86/lib/usercopy_64.c?v=. http://www.verydemo.com/demo_c378_i61436.html http://www.tuicool.com/articles/uYzAzy http://www.hep.by/gnu/kernel/kernel-api/API-strncpy-from-user.html http://blog.csdn.net/justlinux2010/article/details/8972754 https://www.kernel.org/doc/htmldocs/device-drivers/API-might-sleep.html
爲了重現這種觀點,能夠嘗試編譯並運行如下代碼
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> static char buf[] = "hello"; static char buf1[10]; int fileOp(void) { struct file *fp; mm_segment_t fs; loff_t pos; printk("hello enter\n"); fp = filp_open("test", O_RDWR | O_CREAT, 0644); if (IS_ERR(fp)) { printk("create file error\n"); return -1; } fs = get_fs(); set_fs(KERNEL_DS); pos = 0; vfs_write(fp, buf, sizeof(buf), &pos); pos = 0; vfs_read(fp, buf1, sizeof(buf), &pos); printk("read: %s\n", buf1); filp_close(fp, NULL); set_fs(fs); } int __init hello_init(void) { __asm__("cli"); fileOp(); return 0; } void __exit hello_exit(void) { printk("hello exit\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); //在關中斷狀況下觸發中斷會致使Kernel Panic
代碼作了以下的事情
1. 代碼關閉了CPU的硬件中斷EFLAGS開關,此時CPU屏蔽一切可屏蔽中斷 2. 向磁盤寫文件,這是個絕對是觸發硬件中斷的操做,這個時候CPU沒法響應這個硬件中斷 3. 此時引起的PAGE FAULT缺頁中斷處於無人應答狀態 4. LINUX內核選擇忽略這個PAGE FAULT繼續運行,可是這個內存是一個Bad Address 5. 處於"異常狀態"的Linux內核最終在某個時刻PANIC奔潰
Relevant Link:
http://blog.csdn.net/yangdelong/article/details/5491097 http://oss.org.cn/kernel-book/ldd3/ch06s02.html http://blog.csdn.net/eroswang/article/details/3529750 http://blog.csdn.net/ce123_zhouwei/article/details/8454226
Copyright (c) 2014 LittleHann All rights reserved