中斷的上半部和下半部
中斷是系統硬體與處理器通訊的一種機制。當硬體裝置發生中斷的時候,核心會被打斷,並執行中斷對應的處理函數。在執行中斷服務程式的時候,核心處於中斷上下文。此時,如果不禁止中斷,該中斷處理常式仍有可能被其他中斷事件所打斷。因此,我們希望中斷服務程式執行的越快越好。而通常一個中斷服務程式要做很多的事情,比如網卡中斷髮生時,不僅要對網卡作應答,還要將網路資料包拷貝到系統記憶體,並作相應處理後交給合適的協議棧。這樣,既想速度快,又要完成大量的工作,兩者必須作出取捨。
核心對這個問題的處理比較巧妙:核心將整個的中斷處理流程分為了上半部和下半部。上半部的功能是"登記中斷",當一個中斷髮生時,它進行相應地硬體讀寫後就把中斷常式的下半部掛到該裝置的下半部執行隊列中去。因此,上半部執行的速度就會很快,可以服務更多的插斷要求。但是,僅有"登記中斷"是遠遠不夠的,因為中斷的事件可能很複雜。因此,Linux引入了一個下半部,來完成中斷事件的絕大多數使命。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的,下半部幾乎做了中斷處理常式所有的事情,而且可以被新的中斷打斷!下半部則相對來說並不是非常緊急的,通常還是比較耗時的,因此由系統自行安排運行時機,不在中斷服務上下文中執行。 核心提供了很多下半部執行的機制,如非強制中斷、tasklet和等待隊列等。
拿網卡來舉例,在linux核心中,當網卡一旦接受到資料,網卡會通過中斷告訴核心處理資料,核心會在網卡的中斷處理函數(也就是上半部)執行一些網卡硬體的必要設定,因為這是在中斷響應後急切要乾的事情。接著,核心調用對應的下半部函數來處理網卡接收到的資料,因為資料處理沒必要在中斷處理函數裡面馬上執行,可以將中斷讓出來做更緊迫的事情。
中斷處理函數
中斷處理常式使用硬體裝置驅動程式的組成部分,驅動程式通過request_irq()註冊並啟用一個中斷處理函數。而在驅動程式卸載時,通過free_irq()登出中斷處理函數,並釋放中斷線。需要注意的是,request_irq()可能會睡眠,因此不能在中斷上下文或者不允許阻塞的代碼中使用;必須在進程上下文中調用free_irq()。
中斷處理函數的原型為:
1: /**
2: * enum irqreturn
3: * @IRQ_NONE interrupt was not from this device
4: * @IRQ_HANDLED interrupt was handled by this device
5: * @IRQ_WAKE_THREAD handler requests to wake the handler thread
6: */
7: enum irqreturn {
8: IRQ_NONE = (0 << 0),
9: IRQ_HANDLED = (1 << 0),
10: IRQ_WAKE_THREAD = (1 << 1),
11: };
12:
13: typedef enum irqreturn irqreturn_t;
14:
15: typedef irqreturn_t (*irq_handler_t)(int, void *);
中斷處理函數是無需重入的,因為當一個中斷處理函數在執行時,該中斷線上的其他中斷都會被屏蔽掉,以防止在同一個中斷線上接收另一個中斷。
一個典型的中斷處理函數執行個體:RTC中斷處理函數
1: /*
2: * A very tiny interrupt handler. It runs with IRQF_DISABLED set,
3: * but there is possibility of conflicting with the set_rtc_mmss()
4: * call (the rtc irq and the timer irq can easily run at the same
5: * time in two different CPUs). So we need to serialize
6: * accesses to the chip with the rtc_lock spinlock that each
7: * architecture should implement in the timer code.
8: * (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
9: */
10:
11: static irqreturn_t rtc_interrupt(int irq, void *dev_id)
12: {
13: /*
14: * Can be an alarm interrupt, update complete interrupt,
15: * or a periodic interrupt. We store the status in the
16: * low byte and the number of interrupts received since
17: * the last read in the remainder of rtc_irq_data.
18: */
19:
20: spin_lock(&rtc_lock);
21: rtc_irq_data += 0x100;
22: rtc_irq_data &= ~0xff;
23: if (is_hpet_enabled()) {
24: /*
25: * In this case it is HPET RTC interrupt handler
26: * calling us, with the interrupt information
27: * passed as arg1, instead of irq.
28: */
29: rtc_irq_data |= (unsigned long)irq & 0xF0;
30: } else {
31: rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
32: }
33:
34: if (rtc_status & RTC_TIMER_ON)
35: mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
36:
37: spin_unlock(&rtc_lock);
38:
39: /* Now do the rest of the actions */
40: spin_lock(&rtc_task_lock);
41: if (rtc_callback)
42: rtc_callback->func(rtc_callback->private_data);
43: spin_unlock(&rtc_task_lock);
44: wake_up_interruptible(&rtc_wait);
45:
46: kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
47:
48: return IRQ_HANDLED;
49: }
50: #endif
中斷上下文
與進程上下文不同(核心代表進程執行操作,通過current宏關聯當前進程,可以睡眠,也可以調用發送器),當執行中斷處理常式或下半部時,核心處於中斷上下文中。中斷上下文與進程沒有什麼瓜葛,因此中斷上下文不可睡眠。中斷處理函數具有較嚴格的時間限制,因為它打斷了其他的代碼。應當盡量將工作從中斷處理常式中分離開來,放到下半部中執行,因為下半部可以在更合適的時間運行。
中斷處理機制
圖 中斷從硬體到核心的路由
- 中斷髮生時根據中斷控制器找到中斷號
- 中斷上半部執行:調用do_IRQ(),儲存目前狀態old_regs = set_irq_regs(regs),irq_enter(),調用中斷處理函數,最後irq_exit(),恢複原來狀態set_irq_regs(old_regs)
- 中斷下半部執行:非強制中斷和tasklet