第一部分移步傳送門召喚!!:/content/24564144.html
上回說了Linux內核實現中斷會把中斷分為
兩部分進行處理,上回講了上部分,這回講下部分的設計思路
下半部的實現機制
軟中斷
tasklet:是通過軟中斷實現的,但和軟中斷有所不同
工作隊列
講上面幾個實現機制之前先講一個古老的方法,現在版本的內核雖然已經不再食用了,但是思想還在繼續使用
最早的Linux只提供了“bottom half”這種機制實現下半部分,被稱為BH,實現簡單粗暴,設置一個全局變量(32位整數),表示一個32個節點的鏈表隊列,哪位設置為1證明哪個bottom half就可以執行了。
軟中斷第一個先將軟中斷實現下半部分機制,要想將這個機制,必須得先說明軟中斷的實現方式,軟中斷實在編譯期間靜態分配的,kernel/softirq.c中定義了一個包含有32個結構體的數組
static struct softirq_action softirq_vec[NR_SOFTIRQS],並且有一個對應的32位整數
u32 pending,用來表示每個軟中斷的狀態(不要嫌少,一般根本用不了那麼多,一般9個10個就夠用了,為什麼這麼少?很少有用軟中斷處理下半部分的,能用tasklet的地方絕不會使用軟中斷)
把軟中斷放進剛才說的32個長度的結構體數組中就完成了軟中斷的注冊,想要執行軟中斷必須先標記注冊好的軟中斷,這個過程被稱為觸發軟中斷,通常,
中斷處理程序(就是上半部分)會在返回之前標記它的軟中斷,所以不必擔心,然後在合適的時刻就會執行該軟中斷
合適的時刻:1.從一個硬件中斷代碼處返回時;2.在ksoftirqd內核線程中;3.在那些顯示檢查和執行待處理的軟中斷的代碼中;
不管是上面哪個時刻,軟中斷最終都是會被執行的,調用do_softirq()該函數會循環遍歷(循環檢查pending的每一個位,所以循環最多只能執行32次)
tasklet因為takslet是使用軟中斷實現的,所以tasklet本身就是個軟中斷,我們是通過tasklet來實現下半部的機制的,所以在處理方式上和軟中斷十分的相似,tasklet由tasklet結構體表示,每一個結構體單獨代表一個tasklet,它的定義如下
struct tasklet_struct
{
stauct tasklet_struct *next;//鏈表中的下一個節點
unsigned long state;//tasklet的狀態
atomic_t count;//引用計數器
void (*func)(unsigned long);//tasklet處理函數
unsigned long data;//給tasklet處理函數的參數
};
其中tasklet的狀態一共只有三種:0,TASKLET_STATE_SCHED,TASKLET_STATE_RUN,只能在這三種之間取值,0表示啥也沒有等待調度,SCHED表示已經調度,RUN表示該tasklet正在運行。
已經被調度的tasklet結構體存放在兩種單處理器數據結構當中,分別是tasklet_vec(普通優先級的tasklet)和tasklet_hi_vec(高優先級的tasklet),幾乎沒區別,只是優先級不一樣,調度的步驟如下
檢查tasklet的狀態是否為TASKLET_STATE_SCHED,如果是,就證明不需要調度了,直接返回
調用_tasklet_schedule()函數進行調度
保存中斷狀態,禁止本地中斷,防止數據被其他中斷拿去更改
頭插加入鏈表,就剛才說的那兩個優先級不同的鏈表
喚起tasklet中斷(封裝好的軟中斷)
恢復中斷並返
運行的步驟如下:
禁止中斷,檢測兩個鏈表裡面有沒有東西
把當前處理器的該鏈表設置為NULL(意思就是我要把鏈表裡的東西全弄完,先置成NULL)
允許相應中斷
循環遍歷tasklet鏈表上的每一個節點
如果是多處理器系統,查看節點狀態如果是RUN就證明在其他處理器上運行中,直接跳到下一個節點(因為同一時間裡,相同類型的tasklet只有一個能執行)
如果當前節點的狀態不是RUN,就設置成RUN,以防其他處理器調用
檢查count是不是0(看看別人是否正在占用)如果不是0則被禁止,跳到下一個掛起的tasklet去
安全確保,開始執行
一直循環,直到沒有tasklet了(因為我們把鏈表置為NULL了,必須把拿出來的東西處理完)
其實tasklet給人的感覺就是一個對軟中斷的封裝的簡單接口而已。。
每個處理器都有一組輔助處理軟中斷(當然也就包括tasklet)的內核線程,那麼什麼時候執行這些軟中斷呢,上面在軟中斷部分也闡述了,但是這樣有個問題,那就是軟中斷如果繼續調軟中斷,就會不停的執行軟中斷。。這樣在處理器負載很嚴重的時候就不太好了,會導致用戶空間進程饑餓,還有一種方案,那就是並不立即處理軟中斷,而是等待一段時間,但是在處理器比較閒的時候這麼做很顯然不太好,因為完全可以立即執行你卻讓處理器閒著。作為改進,
當大量軟中斷出現的時候,內核會喚醒一組內核線程來處理這些負載,關鍵來了,
這些帶著軟中斷的線程的優先級會被設置到最低的優先級上(nice值取最高為19),這樣的會在處理器比較忙的時候,這些軟中斷不會跟用戶空間進程爭奪處理器資源,而且最終一定會被執行,處理器空閒的時候也可以直接得到運行。
[b]工作隊列[/b]
工作隊列是另外一種比較新的將工作推後的形式,和之前的兩種處理方式不同,它會把工作交給一個內核線程去執行,這就意!味!著!是由進程上下文來處理了!就可以睡眠了!!(中斷是不允許睡眠的)所以很簡單就可以在這兩種方法之間做出選擇。
每一個處理器都有一個對應的工作者線程
struct workqueue_struct
{
struct cpu_work_queue_struct cpu_wq[NR_CPUS];
struct list_head list;
const char *name;
int sinqlethread;
int freezeable;
int rt;
};
struct cpu_workqueue_struct
{
spinlck_t lock;//鎖保護這種結構
struct list_head worklist;//工作列表
wait_queue_head_t more_work;
struct work_struct *current_struct;
struct workqueue_struct *wq;//關聯工作隊列結構
task_t *thread;//關聯線程
};
表示工作的數據結構
struct work_struct
{
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
這些工作的結構體被連城鏈表,當鏈表上的所有工作都做完了之後,線程就會休眠
實現方式也很簡單,
線程首先把自己設置為休眠狀態(只是設置,並沒有立即進入休眠)並把自己加入等待隊列
如果工作鏈表是空的,就用schedule()調度函數進入睡眠狀態
如果鏈表中有對象,線程就不會睡眠了,就把自己的狀態改為TASK_RUNNING,然後從等待隊列中出來
如果鏈表非空,執行那些被退後的下半部分應該干的工作(就是循環一直找。。。)
來個結構圖
下半部機制的選擇這三種看上去都不錯,那麼應該怎麼選擇呢如果你對共享有很高的要求,雖然比較麻煩,但還是使用軟中斷吧,因為可以各種操作(雖然保障這些很麻煩)如果你不是對共享有那麼高的要求,推薦使用tasklet,因為兩種同類型的tasklet不能同時並行如果你想在進程上下文中解決下半部分的問題,使用工作隊列吧,當然如果你想睡眠,你也沒得選了* 全劇終*