1. 程式人生 > >[筆記分享] [OS] Linux的中斷處理

[筆記分享] [OS] Linux的中斷處理

Platform: msm8x60
Kernel: 2.6

介紹

Linux將中斷分為中斷上半部和下半部。上半部用來處理緊急的和硬體操作相關的,下半部用來處理能夠被允許推遲完成的中斷處理部分。兩者之間的界限依情況劃分。
異常和中斷不同,必須考慮時鐘的同步,也稱同步中斷,如除0、缺頁等。這裡我們只討論非同步中斷。

中斷處理程式註冊

註冊函式如下:

這裡寫圖片描述

irq:要分配的中斷號
handler: 中斷處理函式,中斷觸發時會被呼叫
flags:處理中斷標誌。有IRQF_DISABLED、IRQF_SHARED、IRQF_SAMPLE_RANDOM等幾種,這三種比較常用。IRQF_DISABLED表示進入中斷處理程式時禁止其他本地中斷,預設是中斷開啟的。IRQF_SHARED表示中斷線是共享的。
name:中斷名
dev: 中斷共享時用到,用來判斷是哪個中斷觸發。

request_irq()會導致睡眠,因為其在註冊過程中呼叫了kmalloc(),而這個函式是會引起睡眠的。因此不能用於中斷上下文中或者其他不允許阻塞的地方。

相對應的在釋放中斷處理程式的時候用下面函式釋放中斷:

這裡寫圖片描述

如果中斷時共享的,則僅刪除dev_id所對應的處理程式,直到最後一箇中斷被刪除的時候才禁用此中斷線。

中斷處理函式格式如下:
這裡寫圖片描述
其返回型別為irqreturn_t,可分兩種:IRQ_NONE和IRQ_HANDLED。當檢測到中斷正常時我們返回IRQ_HANDLED,否則返回IRQ_NONE。

另外,中斷處理程式是不用考慮重入的,因為當一箇中斷處理程式執行時,相同線上的中斷都會被遮蔽。當然,通常這時其他線上的中斷是被開啟的,這樣使得更高優先順序的中斷能被處理。

中斷上下文

和程序上下文類似,當核心在執行一箇中斷處理程式或者下半部時,核心處於中斷上下文。在程序上下文中,程序可以通過current巨集關聯到當前程序,也可以睡眠,也可以排程程式。但是在核心上下文中,其和程序沒什麼關係,和current巨集業不相關,不能睡眠,不然誰又知道什麼時候能喚醒如何喚醒呢?

程序有各自的核心棧,一般為兩頁大小,以前中斷和其共用核心棧,必須要非常節省。現在程序的核心棧縮小為一頁,而中斷又有了自己的中斷棧(全部中斷共用一頁),平均比使用核心棧要大得多。但是要注意的是在核心中要儘量少使用棧,否則可能導致益處。

中斷控制

當我們需要控制中斷內的資料要同步時,我們可以控制中斷的禁止或者禁止核心搶佔來實現。關於資料的同步,SMP的資料保護問題我們將在後面核心同步章節中講到,這裡我們只討論如何禁止中斷。

禁止和使能本地中斷函式如下:
這裡寫圖片描述

巨集的實現和平臺相關。注意,呼叫這兩個函式有潛在的危險,如在local_irq_disable()之前本來就是禁止中斷的,之後我們又相應地呼叫了local_irq_disalbe(),這樣一來中斷被我們打開了!

因此,我們用儲存操作中斷以前的中斷狀態的方法。在禁止中斷之前儲存中斷系統的狀態,然後在準備啟用中斷時,將中斷恢復到它們原來的狀態就可以了。函式如下:

這裡寫圖片描述

這兩個函式也和體系結構相關。注意,對local_irq_save()和local_irq_restore()的呼叫必須在同一函式中進行。
除了上述函式外,我們還有禁止指定中斷線函式,這裡就不作介紹了。

有時我們需要判斷當前是否處於中斷狀態,這時下面這幾個巨集可以派上用場了:

這裡寫圖片描述

In_irq():判斷是否處於硬中斷
In_softirq():判斷是否處於下半部
In_interrupt():判斷是否處於硬中斷或下半部

其實本質都是通過preemmpt_count來判斷,看下面實現就明白了。注意preempt_count還有一部分是用來對核心是否可搶佔進行計數的。

這裡寫圖片描述

中斷下半部

好了,中斷上半部用起來不是很難,咱們現在看下半部。相對來說,下半部處理可往後推遲的事情。如網絡卡驅動在硬中斷部分從網絡卡接收資料,然後在下半部對網絡卡資料進行處理。

當前核心,我們有三種機制實現下半部: 軟終端、tasklet和工作佇列。其中tasklet通過軟終端實現,而工作佇列和它們完全不同。

a) 軟中斷
軟中斷是在編譯時靜態分配的。它不能像tasklet那樣進行動態分配。由softirq_action 結構表示,如下:

這裡寫圖片描述

action()為軟中斷處理函式,另外還定義了一個包含32個該結構體的陣列:
這裡寫圖片描述

軟中斷型別列表如下:
這裡寫圖片描述

0優先順序最先執行,依次排列。一般我們自己註冊的軟中斷處於3和5之間。註冊方法如下:
這裡寫圖片描述

其實也就是將軟中斷處理函式加入到softirq_vec中去。舉例:
Open_softirq(NET_TX_SOFTIRQ, net_tx_action);

每個被註冊的軟中斷佔據一項,因此最多32個。

一個註冊號的軟中斷必須要觸發才能執行,通常在中斷處理程式返回前標誌它的軟中斷(rasie_softirq()),使其稍後被處理。待處理的軟中斷在以下地方被檢查:
1. 從一個硬體中斷程式碼處返回時
2. 在ksoftirqd核心執行緒中
3. 顯示檢查中
不管用什麼方法,軟中斷都要在do_softirq()中執行。

一個軟中斷不會搶佔另一個軟中斷,唯一可搶佔它的只有中斷處理程式,因為在處理軟中斷處理程式的時候,中斷時開啟的,但它和中斷處理程式一樣不能休眠。本地軟中斷被禁止,但是其他處理器上可執行軟中斷,甚至是同類的中斷,那麼處理函式就會被執行兩次,資料就會遭到破壞了。因此如果沒必要的話,我們就用tasklet, 它的同一個處理函式不會在兩個處理器上同時執行,這樣也就避免了加鎖的麻煩。

b) tasklet
tasklet基於軟中斷,雖然很相似,但介面更簡單,鎖保護要求也低,通常我們選擇使用tasklet。只有那些執行頻率很高的或者連續性要求很高的才用軟中斷。

Tasklet由兩類軟中斷代表: HI_SOFTIRQ和 TASKLET_SOFTIRQ。兩者區別在於前者優先順序高先執行而已。Tasklet結構如下:

這裡寫圖片描述

Next將註冊的tasklet給連結起來。
State有三種:TASKLET_STATE_SCHED表明已被排程,準備投入執行;TASKLET_STATE_RUN表明正在執行。
Count是tasklet引用計數器,0表示被啟用,設定為掛起,這樣才能執行。不為0表示被禁止,不許執行。

已排程的tasklet存在tasklet_vec或tasklet_hi_vec中,每個元素一個tasklet。由tasklet_schedule()或tasklet_hi_schedule()排程。當被排程時,核心就會喚醒之前註冊的HI_SOFTIRQ或TASKLET_SOFTIRQ這兩個軟中斷,然後執行所有已排程的tasklet。該函式保證只有一個同一類別的tasklet會被執行,甚至是在不同處理器上,但是允許不同類別的tasklet執行。

c) work queue
這種方式的下半部我們不怎麼用,這裡就不作詳細介紹了。我們只要知道它是將推遲的任務放在程序上下文中完成的。因此其造成的開銷最大,因為要牽扯到核心執行緒甚至上下文切換。

下面是三種下半部機制的比較:

這裡寫圖片描述