天天看點

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

Platform: msm8x60

Kernel: 2.6

介紹

Linux将中斷分為中斷上半部和下半部。上半部用來處理緊急的和硬體操作相關的,下半部用來處理能夠被允許推遲完成的中斷處理部分。兩者之間的界限依情況劃分。

異常和中斷不同,必須考慮時鐘的同步,也稱同步中斷,如除0、缺頁等。這裡我們隻讨論異步中斷。

中斷處理程式注冊

注冊函數如下:

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

irq:要配置設定的中斷号

handler: 中斷處理函數,中斷觸發時會被調用

flags:進行中斷标志。有IRQF_DISABLED、IRQF_SHARED、IRQF_SAMPLE_RANDOM等幾種,這三種比較常用。IRQF_DISABLED表示進入中斷處理程式時禁止其他本地中斷,預設是中斷開啟的。IRQF_SHARED表示中斷線是共享的。

name:中斷名

dev: 中斷共享時用到,用來判斷是哪個中斷觸發。

request_irq()會導緻睡眠,因為其在注冊過程中調用了kmalloc(),而這個函數是會引起睡眠的。是以不能用于中斷上下文中或者其他不允許阻塞的地方。

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

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

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

中斷處理函數格式如下:

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

其傳回類型為irqreturn_t,可分兩種:IRQ_NONE和IRQ_HANDLED。當檢測到中斷正常時我們傳回IRQ_HANDLED,否則傳回IRQ_NONE。

另外,中斷處理程式是不用考慮重入的,因為當一個中斷處理程式運作時,相同線上的中斷都會被屏蔽。當然,通常這時其他線上的中斷是被打開的,這樣使得更高優先級的中斷能被處理。

中斷上下文

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

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

中斷控制

當我們需要控制中斷内的資料要同步時,我們可以控制中斷的禁止或者禁止核心搶占來實作。關于資料的同步,SMP的資料保護問題我們将在後面核心同步章節中講到,這裡我們隻讨論如何禁止中斷。

禁止和使能本地中斷函數如下:

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

宏的實作和平台相關。注意,調用這兩個函數有潛在的危險,如在local_irq_disable()之前本來就是禁止中斷的,之後我們又相應地調用了local_irq_disalbe(),這樣一來中斷被我們打開了!

是以,我們用儲存操作中斷以前的中斷狀态的方法。在禁止中斷之前儲存中斷系統的狀态,然後在準備激活中斷時,将中斷恢複到它們原來的狀态就可以了。函數如下:

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

這兩個函數也和體系結構相關。注意,對local_irq_save()和local_irq_restore()的調用必須在同一函數中進行。

除了上述函數外,我們還有禁止指定中斷線函數,這裡就不作介紹了。

有時我們需要判斷目前是否處于中斷狀态,這時下面這幾個宏可以派上用場了:

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

In_irq():判斷是否處于硬中斷

In_softirq():判斷是否處于下半部

In_interrupt():判斷是否處于硬中斷或下半部

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

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

中斷下半部

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

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

a) 軟中斷

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

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

action()為軟中斷處理函數,另外還定義了一個包含32個該結構體的數組:

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

軟中斷類型清單如下:

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

0優先級最先運作,依次排列。一般我們自己注冊的軟中斷處于3和5之間。注冊方法如下:

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

其實也就是将軟中斷處理函數加入到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結構如下:

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

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

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

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

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

繼續閱讀