天天看點

中斷的上半部和下半部

參閱《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; 
}
           

繼續閱讀