下半部

i.  下半部

1.  下半部

執行與中斷處理密切相關但中斷處理程序本身不執行的工作。

2. 爲什麼用下半部

中斷處理流程(上半部)的侷限性

a) 中斷處理程序以異步方式執行並且可能會打斷重要代碼(也有可能是其他中斷處理程序)的執行。爲了避免被打斷的代碼停止時間過長,中斷處理程序執行得越快越好。

b) 如果當前有一箇中斷處理程序正在執行,在最好的情況下,該中斷同級的其他中斷會被屏蔽,在最壞的情況下,當前處理器上所有其他中斷都會被屏蔽。因此,讓其執行得越快越好。

c) 由於中斷處理程序要對硬件操作,它們通常有很高的時限要求。

d) 中斷處理程序不能運行在進程上下文,不能阻塞。限制了其所作的事情。

中斷處理流程(上半部)負責對硬件迅速響應並完成時間要求嚴格的操作。對時間要求寬鬆的任務,推遲到中斷被激活以後再去運行。

下半部的優勢:推遲運行強調的是不是馬上運行,不需要指明一個確切的時間,只要把這些任務推遲一點,讓它們在系統不太繁忙併且中斷恢復後執行。通常下半部在中斷處理程序一返回馬上運行。下半部執行的關鍵在於當它們運行的時候會,允許響應所有的中斷。

3. 上半部,下半部任務劃分

對於上半部和下半部之間劃分工作,儘管不存在某種嚴格的規則,但還是有一些提示:

a) 一個任務對時間非常敏感,將其放在上半部。

b)  一個任務和硬件相關,將其放在上半部。

c) 一個任務保證不被其他中斷(特別是相同的中斷)打斷,將其放在上半部。

d) 其他所有任務,考慮放在下半部執行。

4. 下半部實現機制

下半部實現:軟中斷,tasklet,工作隊列。

 

Softirq

Tasklet

Work Queue

執行上下文

延後的工作,運行於中斷上下文

延後的工作,運行於中斷上下文

延後的工作,運行於進程上下文

可重用

可以在不同的CPU上同時運行

不能在不同的CPU上同時運行,

但是不同的CPU可以運行不同的tasklet

可以在不同的CPU上同時運行

睡眠

不能睡眠

不能睡眠

可以睡眠

搶佔

不能搶佔/調度

不能搶佔/調度

可以搶佔/調度

易用性

不容易使用

容易使用

容易使用

何時使用

如果延後的工作不會睡眠,

而且有嚴格的可擴展性或速度要求

如果延後的工作不會睡眠

如果延後的工作會睡眠

 

ii. 軟中斷

軟中斷保留給系統中對時間要求最嚴格以及最重要的下半部使用。網絡和SCSI直接使用軟中斷。內核定時器和tasklet都是建立在軟中斷上面。下面以網絡子系統爲例說明如何使用軟中斷。

1.  軟中斷處理程序

static void net_tx_action(struct softirq_action *h);

static void net_rx_action(struct softirq_action *h)

2.  註冊軟中斷處理程序

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

3. 觸發軟中斷

raise_softirq(NET_TX_SOFTIRQ);

raise_softirq(NET_RX_SOFTIRQ);

raise_softirq()函數將一個軟中斷設置爲掛起狀態,讓它在下次調用do_softirq()函數時運行處理程序。

4. 執行軟中斷

在合適的時刻,軟中斷會運行。在下列地方,待處理的軟中斷會被檢查和執行:

a)      從一個硬件中斷代碼返回時。

b)      在ksoftirqd內核線程中。

c)      顯式檢查和執行待處理的軟中斷代碼中,如網絡子系統。

軟中斷在do_softirq()中執行。如果有待處理的軟中斷,此函數會循環遍歷每一個,調用它們的處理函數。核心代碼如下:


h->action(h)執行實際的軟中斷處理函數

iii. Tasklet

大多數情況下,爲了控制一個尋常的硬件設備,tasklet機制都是實現下半部的最佳選擇。Tasklet可以動態創建,使用方便,執行起來還算快。

1.  Tasklet的實現

a) 註冊tasklet

在softirq_init()函數中調用open_softirq(TASKLET_SOFTIRQ, tasklet_action);和open_softirq(HI_SOFTIRQ,tasklet_hi_action);函數註冊tasklet處理函數。

b) 觸發tasklet action函數

Tasklet_schedule()和task_hi_schedule函數進行調度tasklet。下面爲兩個函數實現的簡單說明:

1)  檢查tasklet的狀態是否爲TASK_STATE_SCHED.如果是,說明tasklet已經被調度過。函數立即返回。

2) 保存中斷狀態, 然後禁止本地中斷。

3) 把需要調度的tasklet加到每個處理器的tasklet_vec鏈表或tasklet_hi_vec鏈表的表頭上。

4) 喚起TASKLET_SOFTIRQ 或HI_SOFTIRQ軟中斷,這樣在下一次調用do_softirq()函數時執行此tasklet

5) 恢復中斷到原狀態返回。

c) asklet_action()和tasklet_hi_action()軟中斷處理函數

這兩個函數是tasklet處理的核心。下面是簡單實現:

1)      禁止中斷,並檢索保存當前處理器tasklet_vec和tasklet_hi_vec鏈表。

2)      清空當前處理器的鏈表。

3)      允許響應中斷。

4)      循環遍歷獲得鏈表上每一個待處理的tasklet.

5)      如果是多處理器系統。檢查TASKLET_STATE_RUN來判斷這個tasklet是否真正運行在其他處理器上。如果真正運行,就不要執行,跳到下一個待處理的tasklet去。(同一時間,相同類型的tasklet只能有一個執行)。

6)      如果當前tasklet沒有執行,將其狀態標誌設置爲TASKLET_STATE_RUN,這樣其它處理器不會再去執行它。

7)      檢查count是否爲0,確保tasklet沒有被禁止。如果被禁止,跳到下一個tasklet。

8)      執行tasklet的處理程序。

9)      Tasklet執行完畢,清除tasklet的state域的TASKLET_STATE_RUN狀態標誌。

10)  重複執行下一個tasklet,直到沒有剩餘的等待處理的tasklet。

核心代碼如下:


2.      使用tasklet

a) asklet處理程序

void short_do_tasklet(unsigned long);

b) 聲明tasklet

靜態創建:DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);

動態創建:tasklet_init(t, short_do_tasklet, dev);

c) 調度tasklet

tasklet_schedule(&short_tasklet);

d) 禁用或激活tasklet

Tasklet_disable(&short_tasklet);

如果該tasklet正在運行,這個函數會等到它執行完畢再返回。

Tasklet_disable_nosync(&short_tasklet);

無須在返回前等待tasklet執行完畢。

Tasklet_enable(&short_tasklet);

e) 去掉tasklet

從掛起的隊列中去掉一個tasklet。這個函數等待該tasklet執行完畢,然後將它移去。該函數可能會引起休眠,禁止在中斷上下文中使用。

iv. Ksoftirqd

Problem:如果軟中斷出現的頻率很高,再加上它們又有將自己重新設置爲可執行狀態的能力,就會導致用戶空間進程無法獲得足夠的處理器時間,處於飢餓狀態。如果單純的對重新觸發的軟中斷採取不立即處理的策略,也無法接受。

Solution: 1. 只要還有被觸發並等待處理的軟中斷,本次執行就負責處理,重新觸發的軟中斷也在本次執行返回前處理。這樣可以保證即時處理。但系統可能一直處理軟中斷,不能完成用戶任務。這樣,用戶空間根本不能容忍有明顯的停頓出現。2. 選擇不處理重新觸發的軟中斷。從中斷返回的時候,檢查掛起的軟中斷並處理,但重新觸發的軟中斷不馬上處理,等一段時間,到下一個軟中斷執行時處理。但比較空閒的系統中,立即處理軟中斷纔是比較好的做法。

結論:設計軟中斷時,做一個折中。當大量軟中斷出現的時候,內核會喚醒一組內核線程來處理這些負載。這些線程在最低的優先級上運行,這能避免和其他重要的任務搶奪資源。

每個處理器都有一個這樣的線程。此線程的名字叫ksoftirqd/n。一旦該線程被初始化,它就會執行類似下面的死循環:


v. 工作隊列

工作隊列是另外一種將工作推後執行的形式。它把工作推後,交給內核線程去執行—運行在進程上下文。由於執行於進程上下文,它允許重新調度或睡眠。

1. 工作隊列的實現

a) 數據結構間的關係

  

b) worker_thread()函數

    初始化結束後,這個函數執行一個死循環並開始休眠。當有操作被插入到隊列後,線程被喚醒,以便執行下半部操作。此函數核心流程如下:

  

c) run_workqueue()函數

   

2.  使用工作隊列

a) 靜態創建: DECLARE_WORK(name, void(*func)(void *)data);

   動態創建:INIT_WORK(struct work_struct *work,void(*func)(void*), void *data);

b) 工作隊列處理函數

   Void work_handler(void *data);

   該函數運行在進程上下文中,允許響應中斷,不持有任何鎖。

c) 對工作隊列進行調度

   Schedule_work(&work);

   Schedule_delayed_work(&work, delay);

d) 刷新操

    排入隊列的工作會在工作者線程下一次被喚醒的時候執行。但在繼續下一步工作之前,有可能需要保證一些操作已經執行完畢(卸載)。基於以上 考慮,內核提供了用於刷新指定工作隊列的函數。

  Void flush_scheduled_work(void);

e) 創建新的隊列

   Struct workqueue_struct *create_workqueue(const char * name);

   Queue_work(&wq, &work) à Schedule_work(&work);

   Queue_delayed_work(&wq,&work,delay) àSchedule_delayed_work(&work,delay);

   Flush_workqueue(&wq) -> flush_scheduled_work(void);

 

vi. 在下半部加鎖

1.  asklet之間同步:加鎖

2. 軟中斷之間共享數據:加鎖

3. 進程上下文和下半部共享數據:加鎖並禁止下半部的處理

   本地和SMP的保護並且防止死鎖

   禁止下半部:void local_bh_diable()

  

   激活下半部:void local_bh_enable()

  

4. 中斷上下文和下半部共享數據:加鎖並禁止中斷。

   本地和SMP的保護並且防止死鎖

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章