中斷是指在CPU正常運行期間,因爲內外部事件或由程序預先安排的事件引發的CPU暫時中止正在運行的程序,轉而爲該內部或外部事件或預先安排的事件服務的程序中去,服務完畢後再返回去繼續運行被暫時中斷的程序。Linux中一般分爲外部中斷(又叫硬件中斷)和內部中斷(又叫異常)。node
在實地址模式中,CPU把內存中從0開始的1KB空間做爲一箇中斷向量表。表中的每一項佔4個字節。可是在保護模式中,有這4個字節的表項構成的中斷向量表不知足實際需求,因而根據反映模式切換的信息和偏移量的足夠使得中斷向量表的表項由8個字節組成,而中斷向量表也叫作了中斷描述符表(IDT)。在CPU中增長了一個用來描述中斷描述符表寄存器(IDTR),用來保存中斷描述符表的起始地址。linux
由上述中判定義可知,系統中斷向量表中共可保存256箇中斷向量入口,即IDT中包含的256箇中斷描述符(對應256箇中斷向量)。編程
而0-31號中斷向量被intel公司保留用來處理異常事件,不能另做它用。對這 0-31號中斷向量,操做系統只需提供異常的處理程序,當產生一個異常時,處理機就會自動把控制轉移到相應的處理程序的入口,運行相應的處理程序;而事實 上,對於這32個處理異常的中斷向量,2.6版本的 Linux只提供了0-17號中斷向量的處理程序,其對應處理程序參見下表、中斷向量和異常事件對應表;也就是說,17-31號中斷向量是空着未用的。網絡
中斷向量號 | 異常事件 | 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 | (intel保留) | Spurious_interrupt_bug |
16 | 協處理器出錯 | Coprocessor_error |
17 | 對齊檢查中斷 | Alignment_check |
0-31號中斷向量已被保留,那麼剩下32-255共224箇中斷向量可用。 這224箇中斷向量又是怎麼分配的呢?2.6版本的Linux中,除了0x80 (SYSCALL_VECTOR)用做系統調用總入口以外,其餘都用在外部硬件中斷源上,其中包括可編程中斷控制器8259A的15個irq;事實上,當 沒有定義CONFIG_X86_IO_APIC時,其餘223(除0x80外)箇中斷向量,只利用了從32號開始的15個,其它208個空着未用。數據結構
外部設備當須要操做系統作相關的事情的時候,會產生相應的中斷。併發
設備經過相應的中斷線向中斷控制器發送高電平以產生中斷信號,而操做系統則會從中斷控制器的狀態位取得那根中斷線上產生的中斷。並且只有在設備在對某一條中斷線擁有控制權,才能夠向這條中斷線上發送信號。也因爲如今的外設愈來愈多,中斷線又是很寶貴的資源不可能被一一對應。所以在使用中斷線前,就得對相應的中斷線進行申請。不管採用共享中斷方式仍是獨佔一箇中斷,申請過程都是先講全部的中斷線進行掃描,得出哪些沒有別佔用,從其中選擇一個做爲該設備的IRQ。其次,經過中斷申請函數申請相應的IRQ。最後,根據申請結果查看中斷是否可以被執行。app
中斷中核心處理數據結構爲irq_desc,它完整的描述了一條中斷線,Linux 2.6。22.6中源碼以下。
ide
irq_desc定義在include/linux/irq.h中
函數
/** * struct irq_desc - interrupt descriptor * * @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()] * @chip: low level interrupt hardware access * @msi_desc: MSI descriptor * @handler_data: per-IRQ data for the irq_chip methods * @chip_data: platform-specific per-chip private data for the chip * methods, to allow shared chip implementations * @action: the irq action chain * @status: status information * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple set_irq_wake() callers * @irq_count: stats field to detect stalled irqs * @irqs_unhandled: stats field for spurious unhandled interrupts * @lock: locking for SMP * @affinity: IRQ affinity on SMP * @cpu: cpu index useful for balancing * @pending_mask: pending rebalanced interrupts * @dir: /proc/irq/ procfs entry * @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP * @name: flow handler name for /proc/interrupts output */ struct irq_desc { irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */ unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; spinlock_t lock; #ifdef CONFIG_SMP cpumask_t affinity; unsigned int cpu; #endif #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE) cpumask_t pending_mask; #endif #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif const char *name; } ____cacheline_internodealigned_in_smp;
其相關聯的幾個結構體以下:spa
定義在include/linux/ interrupt.h中的中斷行動結構體:struct irqaction
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
定義在include/linux 中的:irq_chip 芯片相關的處理函數集合
/** * struct irq_chip - hardware interrupt chip descriptor * * @name: name for /proc/interrupts * @startup: start up the interrupt (defaults to ->enable if NULL) * @shutdown: shut down the interrupt (defaults to ->disable if NULL) * @enable: enable the interrupt (defaults to chip->unmask if NULL) * @disable: disable the interrupt (defaults to chip->mask if NULL) * @ack: start of a new interrupt * @mask: mask an interrupt source * @mask_ack: ack and mask an interrupt source * @unmask: unmask an interrupt source * @eoi: end of interrupt - chip level * @end: end of interrupt - flow level * @set_affinity: set the CPU affinity on SMP machines * @retrigger: resend an IRQ to the CPU * @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ * @set_wake: enable/disable power-management wake-on of an IRQ * * @release: release function solely used by UML * @typename: obsoleted by name, kept as migration helper */ struct irq_chip { const char *name; unsigned int (*startup)(unsigned int irq); //中斷開始 void (*shutdown)(unsigned int irq); //中斷關閉 void (*enable)(unsigned int irq); //中斷使能 void (*disable)(unsigned int irq); //中斷禁用 void (*ack)(unsigned int irq); void (*mask)(unsigned int irq); void (*mask_ack)(unsigned int irq); void (*unmask)(unsigned int irq); void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, cpumask_t dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); int (*set_wake)(unsigned int irq, unsigned int on); /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
咱們指望讓中斷處理程序運行得快,並想讓它完成的工做量多,這兩個目標相互制約,如何解決——上下半部機制。
咱們把中斷處理切爲兩半。中斷處理程序是上半部——接受中斷,他就當即開始執行,但只有作嚴格時限的工做。可以被容許稍後完成的工做會推遲到下半部去,此後,在合適的時機,下半部會被開終端執行。上半部簡單快速,執行時禁止一些或者所有中斷。
下半部稍後執行,並且執行期間能夠響應全部的中斷。這種設計可使系統處於中斷屏蔽狀態的時間儘量的短,以此來提升系統的響應能力。上半部只有中斷處理程序機制,而下半部的實現有軟中斷實現,tasklet實現和工做隊列實現。
咱們用網卡來解釋一下這兩半。當網卡接受到數據包時,通知內核,觸發中斷,所謂的上半部就是,及時讀取數據包到內存,防止由於延遲致使丟失,這是很急迫的工做。讀到內存後,對這些數據的處理再也不緊迫,此時內核能夠去執行中斷前運行的程序,而對網絡數據包的處理則交給下半部處理。
1) 若是一個任務對時間很是敏感,將其放在中斷處理程序中執行;
2) 若是一個任務和硬件有關,將其放在中斷處理程序中執行;
3) 若是一個任務要保證不被其餘中斷打斷,將其放在中斷處理程序中執行;
4) 其餘全部任務,考慮放置在下半部執行。
軟中斷做爲下半部機制的表明,是隨着SMP(share memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了必定的機制)。軟中斷通常是「可延遲函數」的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是由於要知足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,軟中斷執行中斷處理程序留給它去完成的剩餘任務,並且能夠在多個CPU上並行執行,使得總的系統效率能夠更高。它的特性包括:
a)產生後並非立刻能夠執行,必需要等待內核的調度才能執行。軟中斷不能被本身打斷,只能被硬件中斷打斷(上半部)。
b)能夠併發運行在多個CPU上(即便同一類型的也能夠)。因此軟中斷必須設計爲可重入的函數(容許多個CPU同時操做),所以也須要使用自旋鎖來保護其數據結構。
tasklet是經過軟中斷實現的,因此它自己也是軟中斷。
軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須循環完全部的中斷類型,才能最終執行對應的處理函數。顯然當年開發人員爲了保證輪詢的效率,因而限制中斷個數爲32個。
爲了提升中斷處理數量,順道改進處理效率,因而產生了tasklet機制。
Tasklet採用無差異的隊列機制,有中斷時才執行,免去了循環查表之苦。Tasklet做爲一種新機制,顯然能夠承擔更多的優勢。正好這時候SMP愈來愈火了,所以又在tasklet中加入了SMP機制,保證同種中斷只能在一個cpu上執行。在軟中斷時代,顯然沒有這種考慮。所以同一種軟中斷能夠在兩個cpu上同時執行,極可能形成衝突。
總結下tasklet的優勢:
(1)無類型數量限制;
(2)效率高,無需循環查表;
(3)支持SMP機制;
它的特性以下:
1)一種特定類型的tasklet只能運行在一個CPU上,不能並行,只能串行執行。
2)多個不一樣類型的tasklet能夠並行在多個CPU上。
3)軟中斷是靜態分配的,在內核編譯好以後,就不能改變。但tasklet就靈活許多,能夠在運行時改變(好比添加模塊時)。
上面咱們介紹的可延遲函數運行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),因而致使了一些問題:軟中斷不能睡眠、不能阻塞。因爲中斷上下文出於內核態,沒有進程切換,因此若是軟中斷一旦睡眠或者阻塞,將沒法退出這種狀態,致使內核會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必需要運行在進程上下文中,例如訪問磁盤數據塊的函數。所以,可阻塞函數不能用軟中斷來實現。可是它們每每又具備可延遲的特性。
上面咱們介紹的可延遲函數運行在中斷上下文中,因而致使了一些問題,說明它們不可掛起,也就是說軟中斷不能睡眠、不能阻塞,緣由是因爲中斷上下文出於內核態,沒有進程切換,因此若是軟中斷一旦睡眠或者阻塞,將沒法退出這種狀態,致使內核會整個僵死。所以,可阻塞函數不能用軟中斷來實現。可是它們每每又具備可延遲的特性。並且因爲是串行執行,所以只要有一個處理時間較長,則會致使其餘中斷響應的延遲。爲了完成這些不可能完成的任務,因而出現了工做隊列,它可以在不一樣的進程間切換,以完成不一樣的工做。
若是推後執行的任務須要睡眠,那麼就選擇工做隊列,若是不須要睡眠,那麼就選擇軟中斷或tasklet。工做隊列能運行在進程上下文,它將工做託付給一個內核線程。工做隊列說白了就是一組內核線程,做爲中斷守護線程來使用。多箇中斷能夠放在一個線程中,也能夠每一箇中斷分配一個線程。咱們用結構體workqueue_struct表示工做者線程,工做者線程是用內核線程實現的。而工做者線程是如何執行被推後的工做——有這樣一個鏈表,它由結構體work_struct組成,而這個work_struct則描述了一個工做,一旦這個工做被執行完,相應的work_struct對象就從鏈表上移去,當鏈表上再也不有對象時,工做者線程就會繼續休眠。由於工做隊列是線程,因此咱們可使用全部能夠在線程中使用的方法。
Linux中的軟中斷和工做隊列是中斷上下部機制中的下半部實現機制。
1.軟中斷通常是「可延遲函數」的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進城切換,軟中斷不能被本身打斷,只能被硬件中斷打斷(上半部),能夠併發的運行在多個CPU上。因此軟中斷必須設計成可重入的函數,所以也須要自旋鎖來保護其數據結構。
2.工做隊列中的函數處在進程上下文中,它能夠睡眠,也能被阻塞,可以在不一樣的進程間切換,以完成不一樣的工做。
可延遲函數和工做隊列都不能訪問用戶的進程空間,可延時函數在執行時不可能有任何正在運行的進程,工做隊列的函數有內核進程執行,他不能訪問用戶空間地址。