中斷的上半部和下半部

參閱《linux內核設計與實現》
中斷處理分爲上半部和下半部
中斷處理的上半部和下半部都是不允許出現睡眠和阻塞的。但是對於下半部,並不是一刀切,不同下半部的實現方式有的不允許睡眠和阻塞(軟中斷和tasklet),有的是可以的(工作隊列)
上半部:一般中斷的中斷處理函數爲上半部,需要立即執行且耗時少的操作(時間太長,且如果該中斷的標誌是IRQF_DISABLED的話,會禁掉所有本地中斷,這個函數時間長的話會對系統的性能早成嚴重影響)
下半部:由於上半部只能執行耗時少的操作,所以耗時長的操作就放在下半部,兩個的界限並不是很明顯,取決於我們要將哪個操作放在上半部還是下半部

##上半部:
中斷的上半部是中斷處理函數,要求做耗時少的動作,儘量迅速,一定不能休眠和阻塞
##下半部:
參閱《linux內核設計與實現》
==以下如未特殊申明,則中斷均爲硬件中斷,軟中斷即爲軟中斷

目前中斷下半部的實現機制有
1:軟中斷2.tasklets3.工作隊列
其中1,2應該同屬於軟中斷,因爲tasklets的實現也是使用軟中段實現的,其中使用了軟中斷的0號中斷號,優先級很高
3.的實現原理是內核線程

以前的認知是下半部一定是不可以睡眠和阻塞的
但是實質上中斷的下半部的處理函數中是根據不同的實現方式來決定允不允許使用睡眠和可以阻塞的程序的.
其中軟中斷和tasklet是不可以睡眠和阻塞的。
原因:
1,軟中斷的處理程序允許本身被一個處理器執行的時候,另外一個處理器也也可以運行(允許響應中斷)這個處理程序,如果有睡眠和阻塞的話,另外一個就運行不了了。
這也牽扯到一個軟中段處理程序數據共享的問題
2:tasklet是使用了軟中斷而來,軟中斷本身就不能睡眠和阻塞,因此tasklet也不可以有睡眠和阻塞
工作隊列是可以睡眠和阻塞的。
原因:
工作隊列的本質是使用了一個內核線程,也就是利用了進程的上下文,而進程的上下文是允許有阻塞和睡眠的

軟中斷:
軟中斷機制的起源是來源於中斷的下半部(即需要推後執行的事情,中斷的上半部是中斷處理函數)
軟中斷的使用:
1:註冊軟中斷,綁定軟中斷的軟中斷處理函數
2:觸發;這裏的觸發並不是直接運行軟中斷的處理函數,而是對這個軟中斷號進行置位標記
3:執行
軟中斷的使用場景
1:在中斷的下半部是用軟中斷中的軟中斷處理函數去處理耗時的操作
對於這種場景,假設已經註冊了一個軟中斷,一般在中斷的中斷處理函數中進行該軟中斷號的一個觸發(只是標記,並不執行)。當中斷處理函數返回後,系統會調用do_softirq()
去執行軟中斷的中斷處理函數,然後清零該軟中斷號的標記

2:直接當成另外一種中斷的機制去使用,不過這裏的註冊,觸發都ok,但是執行軟中斷的操作do_softirq()可不可以自己調用還沒有嘗試過,不過原理上就這樣

不過,軟中斷是的資源比較稀缺(軟中斷號只有32個)而且一部分系統的模塊已經佔用可一些軟中斷號,並且由於對鎖的要求比較高,所以應該慎重使用,因爲同樣的功能,使用
tasklet更方便安全,tasklet是對軟中斷的一個更好的封裝使用

tasklet:
tasklet的實質是利用了軟中斷的0號,5號軟中斷號來進行的。

工作隊列(work queue):
工作隊列可以把工作推後,交由一個內核線程去執行,它總是在進程的上下文中進行的。所以工作隊列是允許睡眠的一種中斷下半部實現方式。
工作隊列的本質是創建一個一個普通的內核線程,我們稱爲工作者線程。
以下是幾個工作隊列抽象出來的數據結構
1:workqueue_struct表示一個工作這線程,每個cpu都會擁有一個工作者線程,
2:隊列上的工作爲work_struct
當我們將一個任務加入一個工作隊列時候,內核會爲每一個任務建立一個work_queue結構體表示該任務。同時將該結構體和相應的工作者線程結構體workqueue_struct關聯起來,
將該結構體加入工作線程結構體的工作列表裏。當有work被插入這個隊列的時候,這個工作者線程就會喚醒,去遍歷這個鏈表上的所有任務,執行完後,將該任務
從鏈表上拿走,工作者線程繼續休眠。
系統在啓動的時候會爲每一個cpu建立一個缺省的工作隊列,這個工作隊列實際上創建了一個內核線程,名字爲kworker/:cpu_id,例如[kworker/0:0],[kworker/1:0]等,這個內核 線程去輪詢操作掛在這個工作隊列中的任務。
我們也可以自己創建一個工作隊列,老的內核,每創建一個工作對列就要開啓一個內核線程去維護這個工作隊列,這會浪費資源,新的內核:
在整個創建workqueue的流程當中我們可以看到,它並沒有爲新的workqueue去創建一個工作者線程,而是將wq與cpu_workqueue_struct關聯起來,在這個cpu_workqueue_struct結構中還關聯了gcwq,然後把workqueue放到workqueues鏈表上去。
當我們創建了一個任務(work_struct)以後,可以選擇將這個任務掛在內核缺省的工作隊列上或者我們自己創建的工作隊列上。
NOTE:工作中遇到很多的情況是driver中使用了工作隊列,然後上層在close該節點的時候發現clsoe失敗,串口終端無響應被佔用,定位發現在內核的release函數中掛掉了。
此時發現是因爲沒有清理driver中創建的工作隊列或者線程,此時需要:

flush_workqueue(dev->work_queue);
destroy_workqueue(dev->work_queue);

但是有的時候發現上述動作做了,任然會卡在這裏,此時一般有兩種情況:
1.工作隊列的任務中使用了while(1)並且沒有sleep,導致cpu被佔用,不能清除該任務
2.工作隊列的任務中使用睡眠機制的函數,(例如使用了等待隊列或者shedule_timeout的變體)導致任務睡眠,而flush_workqueue(dev->work_queue);並不能清掉帶有睡眠屬性的
工作隊列。
此時對於有睡眠屬性的任務,清理的時候需要去掉任務的睡眠狀態,然後再去清理資源

if(priv->as_status)//判斷一下等待隊列的狀態是否還是等待狀態,如果還在等待狀態則執行喚醒操作
{
		wake_up_interruptible(&priv->as_sync_wait_queue);//先喚醒睡眠
}
flush_workqueue(dev->work_queue);
destroy_workqueue(dev->work_queue);

如何在合適的場景使用這三種機制?
1.如果推後的工作需要睡眠和阻塞,就用工作隊列,如果沒有睡眠就用軟中斷和tasklet

有一點一定要清楚,雖然上述的軟中斷,tasklet,工作隊列,是在中斷的下半部被提出來的,中斷的下半部一定是依賴這些來實現,
但並不表示,他們只能在中斷的下半部使用,在其他的場景,仍然可以單獨的使用這些

問題:在中斷中使用下半部的場景?
把握一點:tasklet的創建初始化以及調度都放在中斷處理程序當中
//中斷處理程序,越快返回越好

static irqreturn_t wq_irq_handler(int irq, void *dev)
 { struct wq_dev mydev;
 static int count = 0;
 mydev = *(struct wq_dev*)dev; 
printk("key:%d\n",count); 
printk("ISR is working...\n"); 
if(count < 10) 
{ 
printk("------%d start-------\n",count+1);
 printk("the interrupt handler is working\n"); 
printk("the most of interrupt work will be done by following fasklet...\n");
 tasklet_init(&wq_tasklet, wq_tasklet_handler, 0);//動態創建一個tasklet結構 
tasklet_schedule(&wq_tasklet);//tasklet會被掛起,等待機會被執行 
printk("the top half has been done and bottom half will be processed...\n"); 
} c
ount++; return IRQ_HANDLED; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章