轉自:http://blog.csdn.net/duqi_2009/article/details/38009717
1、中斷處理程序與其他內核函數真正的區別在於,中斷處理程序是被內核調用來相應中斷的,而它們運行於中斷上下文(原子上下文)中,在該上下文中執行的代碼不可阻塞。中斷就是由硬件打斷操作系統。
2、異常與中斷不同,它在產生時必須考慮與處理器時鍾同步。異常被稱為同步中斷,例如:除0、缺頁異常、陷入內核(trap)引起系統調用處理程序異常。
3、不同的設備對應的中斷不同,而每個中斷都通過一個唯一的數字(中斷號)標識。
4、既想讓中斷處理程序運行得快,又想中斷處理程序完成的工作量多,為了在這兩個相悖的目標之間達到一種平衡,一般把中斷處理分為兩個部分。中斷處理程序是上半部(top half):接收到一個中斷,它就立刻開始執行,但只做有嚴格時限的工作,例如對接受的中斷進行應答或者復位硬件,這些工作都是在中斷被禁止的情況下完成的(上半部情況下,中斷被禁止);另一部分是下半部(bottom half):能夠被允許稍后完成的工作會推遲到下半部。
(1)為什么要用下半部?
中斷處理程序執行的時候,當前中斷號對應的中斷在所有處理器上都會被屏蔽;更糟糕的是,如果處理器程序是IRQF_DISABLED類型,它執行的時候會把本地的所有中斷都屏蔽。然而,縮短中斷被屏蔽的時間對系統的響應能力和性能都至關重要,所以,我們應該盡力縮短中斷處理程序的時間,辦法就是把一些工作放到以后去做。關鍵是:在下半部運行的時候,允許響應所有中斷。
(2)划分中斷上半部和下半部的借鑒原則:
- 如果一個任務對時間非常敏感,將其放在中斷處理程序中執行
- 如果一個任務和硬件相關,將其放在中斷處理程序中執行。
- 如果一個任務要保證部被其他中斷(特別是相同的中斷)打斷,將其放置在中斷處理程序中執行。
- 其他所有任務,考慮放置在下半部執行
5、例子,網卡驅動程序,當網卡接收到來自網路的數據包時,需要通知內核數據包到了。中斷處理程序(top half)立即開始執行:通知硬件,拷貝最新的網絡數據到內存,然后讀取網卡更多的數據包,這些工作非常緊迫,因為網卡上接受數據包的緩存大小固定;下半部:執行處理和操作數據包的其他工作。
6、Linux提供的實現下半部的機制:(上半部的實現機制只有一種:中斷處理程序)
在Linux內核2.6中,內核提供了三種不同形式的下半部實現機制:軟中斷、tasklet和工作隊列。這三種機制從2.3開始引入。軟中斷用的比較少,tasklet是下半部更常用的一種形式,但是,tasklet是基於軟中斷實現的。
(1)如果你想加入一個新的軟中斷,首先應該問問自己為什么用tasklet實現不了,目前只有兩個子系統(網絡和SCSI)直接使用軟中斷。軟中斷只有在那些執行頻率很高和連續性很高的情況下才需要使用。如果不需要擴展到多個處理器,那么就使用tasklet吧。tasklet本質上也是軟中斷,只不過同一個處理器程序的多個實例不能再多個處理器上同時運行。
下半部何時調用?內核在執行完中斷處理器程序以后,do_softirq()函數,於是軟中斷開始執行中斷處理程序留給它去完成的剩余任務。大部分tasklet和軟中斷都是在中斷處理程序中被設置成待處理狀態,所以最近一個中斷返回的時候就是執行do_softirq()的最佳時機。
(2)tasklet_action()和tasklet_hi_action()是tasklet處理的核心。
(3)ksoftirqd輔助線程:每個處理器都有一組輔助處理軟中斷(和tasklet)的內核線程,當內核中出現大量軟中斷的時候,這些內核進程就會輔助處理它們。當一個軟中斷正在執行時,可能會再次觸發它自己,內核目前采取的方案是:不會立即處理重新觸發的軟中斷,在大量軟中斷出現的時候,內核會喚醒一組內核線程(nice值是19)來處理這些負載。
- for(;;)
- {
- if(!softirq_pending(cpu)) //如果沒有軟中斷,則調用schedule()
- schedule();
- set_current_state(TASK_RUNNING);
- while(softirq_pending(cpu)){
- do_softirq();
- if(need_resched()) //如有必要重新調度,每次迭代之后都會schedule()以便讓更重要的進程得到處理機會
- schedule();
- }
- set_current_state(TASK_INTERRUPTIBLE);
- }
(4)工作隊列(work queue)是另一種將工作任務推后的方式,與軟中斷和tasklet都不相同。工作隊列把工作交給一個內核線程去執行——這個下半部總是在進程上下文中執行,最重要的,工作隊列允許重新調度甚至睡眠。所以,如果推后執行的任務需要睡眠,那么就選擇工作隊列;如果推后的任務不需要睡眠,那么就選擇軟件中斷或者tasklet。如果需要使用一個可以重新調度的實體來執行當前中斷的下半部任務,就應該使用工作隊列。工作隊列是唯一能在進程上下文中運行的下半部實現機制,也只有它才可以睡眠(關鍵是看你的任務是否需要睡眠)。盡管工作隊列的操作處理函數運行在進程上下文中,但是它不能訪問用戶空間,因為該內核線程在用戶空間沒有相關的內存映射。
注意:mmc驅動中用到了工作隊列~
Notice:通常在發生系統調用時,內核會代表用戶空間的進程運行,此時它才能訪問用戶空間,也只有在此時它才會映射用戶空間的內存。
(5)下半部機制的選擇
下半部 | 上下文 | 順序執行保障 |
軟中斷 | 中斷 | 沒有 |
tasklet | 中斷 | 同類型不能同時執行 |
工作隊列 | 進程 | 沒有(和進程上下文一樣被調度) |
從易用性角度來看:工作隊列 > tasklet > 軟中斷
總結:驅動程序的編寫者,需要做兩個選擇。首先,你是不是需要一個可調度的實體來執行需要推后完成的工作——從根本上來說,你需要推后的工作任務有休眠的需要嗎?要是有,那么工作隊列就是唯一的選擇;否則最好用tasklet。其次,如果必須專注於性能的提高,那么就考慮軟中斷吧~
(6)使用下半部機制時,即使是在一個單處理器的系統上,避免共享數據的訪問也是至關重要的。禁止下半部的函數有local_bh_disable()和local_bh_enable(),這兩個函數只能禁止和激活本地處理器的軟中斷和tasklet。因為工作隊列是在進程上下文中運行的,不會涉及異步執行的問題,所以也就沒必要禁止它們執行。
7、在驅動程序中,要申請中斷(注冊中斷處理程序)
- request_threaded_irq(unsigned int irq, irq_handler_t handler,
- irq_handler_t thread_fn,
- unsigned long flags, const char *name, void *dev);
irq 需要申請的irq編號,對於ARM體系,irq編號通常在平台級的代碼中事先定義好,有時候也可以動態申請。
handler 中斷服務回調函數,該回調運行在中斷上下文中,並且cpu的本地中斷處於關閉狀態,所以該回調函數應該只是執行需要快速響應的操作,執行時間應該盡可能短小,耗時的工作最好留給下面的thread_fn回調處理。
thread_fn 如果該參數不為NULL,內核會為該irq創建一個內核線程,當中斷發生時,如果handler回調返回值是IRQ_WAKE_THREAD,內核將會激活中斷線程,在中斷線程中,該回調函數將被調用,所以,該回調函數運行在進程上下文中,允許進行阻塞操作。
flags 控制中斷行為的位標志,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定義。
name 申請本中斷服務的設備名稱,同時也作為中斷線程的名稱,該名稱可以在/proc/interrupts文件中顯示。
dev 當多個設備的中斷線共享同一個irq時,它會作為handler的參數,用於區分不同的設備。free_irq()函數調用的時候,dev的作用就體現出來了。
8、irq_handler_t的類型定義如下:
- typedef irqreturn_t (*irq_handler_t)(int,void*);
用typedef 定義了一個函數指針類型irq_handler_t,指向的函數原型返回類型為 irqreturn_t ;它接收的參數類型就是int 和void* 兩個參數
9、request_threaded_irq()函數是可以睡眠的,因為request_threaded_irq()-->proc_mkdir()-->proc_create()-->kmalloc(),而kmalloc()是可以睡眠的。所以,不能在中斷上下文中調用該函數。
10、先初始化硬件,然后再注冊中斷處理程序,以防止中斷處理程序在設備初始化完成之前就開始執行。
11、free_irq():如果在給定的中斷線上沒有中斷處理程序,則注銷響應的處理程序,並禁用其中斷線。
12、中斷處理程序即使什么工作也不做,至少需要知道產生中斷的設備,告訴它已經收到中斷了;對於復雜的設備,可能還需要在中斷處理器程序中發送和接收數據,以及執行一些擴充的工作。這些擴充的工作盡可能被推遲到下半部(bottom half)去完成。
13、中斷線和中斷號是兩個相同的概念,irq
14、Linux中的中斷處理程序是無需重入的,當一個給定的中斷處理程序正在執行時,相應的中斷號在所有處理器上都是被屏蔽掉;所以,同一個中斷處理程序絕對不會被同時調用以處理嵌套中斷。
15、進程上下文:一種內核所處的操作模式,此時內核代表進程執行——例如,執行系統調用或者運行內核線程。在進程上下文中,可以通過current宏關聯當前進程。又因為進程是以進程上下文的形式連接到內核的,因此,進程上下文可以睡眠,也可以調用調度程序。
16、中斷上下文:與進程沒什么關系,與current宏也沒有關系,所以中斷上下文不可以睡眠,在中斷上下文中不可以調用任何可能睡眠的函數。
17、中斷處理程序打斷了其他的代碼的執行,所以中斷上下文中的代碼應該簡潔、迅速,盡可能把工作從中斷處理程序中分離出來,放在下半部執行。
18、中斷處理程序棧的設置是一個內核配置項,如果有的話,是1頁大小,即4KB
19、控制中斷系統的原因歸根結底是需要提供同步。禁止中斷提供保護機制,防止來自其他中斷程序的並發訪問,也能夠禁止內核搶占;鎖提供保護機制,防止來自其他處理器的並發訪問(SMP系統需要考慮)