Linux中斷機制
1.中斷概念
中斷是指在CPU正常運行期間,由於內外部事件或由程序預先安排的事件引起的CPU暫時停止正在運行的程序,轉而為該內部或外部事件或預先安排的事件服務的程序中去,服務完畢後再返回去繼續運行被暫時中斷的程序。Linux中通常分為外部中斷(又叫硬件中斷)和內部中斷(又叫異常)。
在實地址模式中,CPU把內存中從0開始的1KB空間作為一個中斷向量表。表中的每一項占4個字節。但是在保護模式中,有這4個字節的表項構成的中斷向量表不滿足實際需求,於是根據反映模式切換的信息和偏移量的足夠使得中斷向量表的表項由8個字節組成,而中斷向量表也叫做了中斷描述符表(IDT)。在CPU中增加了一個用來描述中斷描述符表寄存器(IDTR),用來保存中斷描述符表的起始地址。
2. Linux中斷處理
2.1 系統中斷號
由上述中斷定義可知,系統中斷向量表中共可保存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個空著未用。
2.2 中斷請求
2.2.1 中斷請求概述
外部設備當需要操作系統做相關的事情的時候,會產生相應的中斷。
設備通過相應的中斷線向中斷控制器發送高電平以產生中斷信號,而操作系統則會從中斷控制器的狀態位取得那根中斷線上產生的中斷。而且只有在設備在對某一條中斷線擁有控制權,才可以向這條中斷線上發送信號。也由於現在的外設越來越多,中斷線又是很寶貴的資源不可能被一一對應。因此在使用中斷線前,就得對相應的中斷線進行申請。無論采用共享中斷方式還是獨占一個中斷,申請過程都是先講所有的中斷線進行掃描,得出哪些沒有別占用,從其中選擇一個作為該設備的IRQ。其次,通過中斷申請函數申請相應的IRQ。最後,根據申請結果查看中斷是否能夠被執行。
2.2.2 中斷相關結構
中斷中核心處理數據結構為irq_desc,它完整的描述了一條中斷線,Linux 2.6。22.6中源碼如下。
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;irq_desc
其相關聯的幾個結構體如下:
定義在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; };
2.2.3 中斷請求實現
上下半部機制
我們期望讓中斷處理程序運行得快,並想讓它完成的工作量多,這兩個目標相互制約,如何解決——上下半部機制。
我們把中斷處理切為兩半。中斷處理程序是上半部——接受中斷,他就立即開始執行,但只有做嚴格時限的工作。能夠被允許稍後完成的工作會推遲到下半部去,此後,在合適的時機,下半部會被開終端執行。上半部簡單快速,執行時禁止一些或者全部中斷。
下半部稍後執行,而且執行期間可以響應所有的中斷。這種設計可以使系統處於中斷屏蔽狀態的時間盡可能的短,以此來提高系統的響應能力。上半部只有中斷處理程序機制,而下半部的實現有軟中斷實現,tasklet實現和工作隊列實現。
我們用網卡來解釋一下這兩半。當網卡接受到數據包時,通知內核,觸發中斷,所謂的上半部就是,及時讀取數據包到內存,防止因為延遲導致丟失,這是很急迫的工作。讀到內存後,對這些數據的處理不再緊迫,此時內核可以去執行中斷前運行的程序,而對網絡數據包的處理則交給下半部處理。
上下半部劃分原則
1) 如果一個任務對時間非常敏感,將其放在中斷處理程序中執行;
2) 如果一個任務和硬件有關,將其放在中斷處理程序中執行;
3) 如果一個任務要保證不被其他中斷打斷,將其放在中斷處理程序中執行;
4) 其他所有任務,考慮放置在下半部執行。
下半部實現機制之軟中斷
軟中斷作為下半部機制的代表,是隨著SMP(share memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了一定的機制)。軟中斷一般是“可延遲函數”的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因為要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,軟中斷執行中斷處理程序留給它去完成的剩余任務,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:
a)產生後並不是馬上可以執行,必須要等待內核的調度才能執行。軟中斷不能被自己打斷,只能被硬件中斷打斷(上半部)。
b)可以並發運行在多個CPU上(即使同一類型的也可以)。所以軟中斷必須設計為可重入的函數(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其數據結構。
下半部實現機制之tasklet
tasklet是通過軟中斷實現的,所以它本身也是軟中斷。
軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須循環完所有的中斷類型,才能最終執行對應的處理函數。顯然當年開發人員為了保證輪詢的效率,於是限制中斷個數為32個。
為了提高中斷處理數量,順道改進處理效率,於是產生了tasklet機制。
Tasklet采用無差別的隊列機制,有中斷時才執行,免去了循環查表之苦。Tasklet作為一種新機制,顯然可以承擔更多的優點。正好這時候SMP越來越火了,因此又在tasklet中加入了SMP機制,保證同種中斷只能在一個cpu上執行。在軟中斷時代,顯然沒有這種考慮。因此同一種軟中斷可以在兩個cpu上同時執行,很可能造成沖突。
總結下tasklet的優點:
(1)無類型數量限制;
(2)效率高,無需循環查表;
(3)支持SMP機制;
它的特性如下:
1)一種特定類型的tasklet只能運行在一個CPU上,不能並行,只能串行執行。
2)多個不同類型的tasklet可以並行在多個CPU上。
3)軟中斷是靜態分配的,在內核編譯好之後,就不能改變。但tasklet就靈活許多,可以在運行時改變(比如添加模塊時)。
下半部實現機制之工作隊列(work queue)
上面我們介紹的可延遲函數運行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致內核會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必須要運行在進程上下文中,例如訪問磁盤數據塊的函數。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。
上面我們介紹的可延遲函數運行在中斷上下文中,於是導致了一些問題,說明它們不可掛起,也就是說軟中斷不能睡眠、不能阻塞,原因是由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致內核會整個僵死。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。而且由於是串行執行,因此只要有一個處理時間較長,則會導致其他中斷響應的延遲。為了完成這些不可能完成的任務,於是出現了工作隊列,它能夠在不同的進程間切換,以完成不同的工作。
如果推後執行的任務需要睡眠,那麽就選擇工作隊列,如果不需要睡眠,那麽就選擇軟中斷或tasklet。工作隊列能運行在進程上下文,它將工作托付給一個內核線程。工作隊列說白了就是一組內核線程,作為中斷守護線程來使用。多個中斷可以放在一個線程中,也可以每個中斷分配一個線程。我們用結構體workqueue_struct表示工作者線程,工作者線程是用內核線程實現的。而工作者線程是如何執行被推後的工作——有這樣一個鏈表,它由結構體work_struct組成,而這個work_struct則描述了一個工作,一旦這個工作被執行完,相應的work_struct對象就從鏈表上移去,當鏈表上不再有對象時,工作者線程就會繼續休眠。因為工作隊列是線程,所以我們可以使用所有可以在線程中使用的方法。
Linux軟中斷和工作隊列的作用是什麽
Linux中的軟中斷和工作隊列是中斷上下部機制中的下半部實現機制。
1.軟中斷一般是“可延遲函數”的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進城切換,軟中斷不能被自己打斷,只能被硬件中斷打斷(上半部),可以並發的運行在多個CPU上。所以軟中斷必須設計成可重入的函數,因此也需要自旋鎖來保護其數據結構。
2.工作隊列中的函數處在進程上下文中,它可以睡眠,也能被阻塞,能夠在不同的進程間切換,以完成不同的工作。
可延遲函數和工作隊列都不能訪問用戶的進程空間,可延時函數在執行時不可能有任何正在運行的進程,工作隊列的函數有內核進程執行,他不能訪問用戶空間地址。
Linux中斷機制