《中斷學習 —— 中斷處理tasklet機制》
應用場景:
中斷服務程式一般都是在中斷請求關閉的條件下執行的,以避免巢狀而使中斷控制複雜化。但是,中斷是一個隨機事件,它隨時會到來,如果關中斷的時間太長,CPU就不能及時響應其他的中斷請求,從而造成中斷的丟失。因此,Linux核心的目標就是儘可能快的處理完中斷請求,盡其所能把更多的處理向後推遲。例如,假設一個數據塊已經達到了網線,當中斷控制器接受到這個中斷請求訊號時,Linux核心只是簡單地標誌資料到來了,然後讓處理器恢復到它以前執行的狀態,其餘的處理稍後再進行(如把資料移入一個緩衝區,接受資料的程序就可以在緩衝區找到資料)。因此,核心把中斷處理分為兩部分:上半部(tophalf)和下半部(bottomhalf),上半部(就是中斷服務程式)核心立即執行,而下半部(就是一些核心函式)留著稍後處理。
首先,一個快速的“上半部”來處理硬體發出的請求,它必須在一個新的中斷產生之前終止。通常,除了在裝置和一些記憶體緩衝區(如果你的裝置用到了DMA,就不止這些)之間移動或傳送資料,確定硬體是否處於健全的狀態之外,這一部分做的工作很少。
下半部執行時是允許中斷請求的,而上半部執行時是關中斷的,這是二者之間的主要區別。
但是,核心到底什時候執行下半部,以何種方式組織下半部?
一、中斷處理的tasklet(小任務)機制
小任務是指對要推遲執行的函式進行組織的一種機制。
其資料結構為tasklet_struct,每個結構代表一個獨立的小任務,其定義如下:
Struct tasklet_struct { struct tasklet_struct *next; /*指向連結串列中的下一個結構*/ unsigned long state; /* 小任務的狀態*/ atomic_t count; /* 引用計數器*/ void(*func) (unsigned long); /* 要呼叫的函式*/ unsigned long data; /* 傳遞給函式的引數*/ };
結構中的func域就是下半部中要推遲執行的函式,data是它唯一的引數。State域的取值為TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任務已被排程,正準備投入執行,TASKLET_STATE_RUN表示小任務正在執行。TASKLET_STATE_RUN只有在多處理器系統上才使用,單處理器系統什麼時候都清楚一個小任務是不是正在執行(它要麼就是當前正在執行的程式碼,要麼不是)。Count域是小任務的引用計數器。如果它不為0,則小任務被禁止,不允許執行;只有當它為零,小任務才被啟用,並且在被設定為掛起時,小任務才能夠執行。
1. 宣告和使用小任務大多數情況下,為了控制一個尋常的硬體裝置,小任務機制是實現下半部的最佳選擇。小任務可以動態建立,使用方便,執行起來也比較快。
我們既可以靜態地建立小任務,也可以動態地建立它。選擇那種方式取決於到底是想要對小任務進行直接引用還是一個間接引用。如果準備靜態地建立一個小任務(也就是對它直接引用),使用下面兩個巨集中的一個:
DECLARE_TASKLET(name,func, data) DECLARE_TASKLET_DISABLED(name,func, data)
這兩個巨集都能根據給定的名字靜態地建立一個tasklet_struct結構。當該小任務被排程以後,給定的函式func會被執行,它的引數由data給出。這兩個巨集之間的區別在於引用計數器的初始值設定不同。第一個巨集把建立的小任務的引用計數器設定為0,因此,該小任務處於啟用狀態。另一個把引用計數器設定為1,所以該小任務處於禁止狀態。例如:
DECLARE_TASKLET(my_tasklet,my_tasklet_handler, dev); 這行程式碼其實等價於 structtasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0), tasklet_handler,dev};
這樣就建立了一個名為my_tasklet的小任務,其處理程式為tasklet_handler,並且已被啟用。當處理程式被呼叫的時候,dev就會被傳遞給它。
2.編寫自己的小任務處理程式小任務處理程式必須符合如下的函式型別:
void tasklet_handler(unsigned long data)
由於小任務不能睡眠,因此不能在小任務中使用訊號量或者其它產生阻塞的函式。但是小任務執行時可以響應中斷。
3. 排程自己的小任務通過呼叫tasklet_schedule()函式並傳遞給它相應的tasklt_struct指標,該小任務就會被排程以便適當的時候執行:
tasklet_schedule(&my_tasklet); /*把my_tasklet標記為掛起 */
在小任務被排程以後,只要有機會它就會盡可能早的執行。在它還沒有得到執行機會之前,如果一個相同的小任務又被排程了,那麼它仍然只會執行一次。
可以呼叫tasklet_disable()函式來禁止某個指定的小任務。如果該小任務當前正在執行,這個函式會等到它執行完畢再返回。呼叫tasklet_enable()函式可以啟用一個小任務,如果希望把以DECLARE_TASKLET_DISABLED()建立的小任務啟用,也得呼叫這個函式,如:
tasklet_disable(&my_tasklet); /*小任務現在被禁止,這個小任務不能執行*/ tasklet_enable(&my_tasklet); /* 小任務現在被啟用*/
也可以呼叫tasklet_kill()函式從掛起的佇列中去掉一個小任務。該函式的引數是一個指向某個小任務的tasklet_struct的長指標。在小任務重新排程它自身的時候,從掛起的佇列中移去已排程的小任務會很有用。這個函式首先等待該小任務執行完畢,然後再將它移去。
4.例項
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/kernel.h> #include <linux/interrupt.h> static struct t asklet_struct my_tasklet; static void tasklet_handler (unsigned long d ata) { printk(KERN_ALERT,"tasklet_handler is running./n"); } static int __init test_init(void) { tasklet_init(&my_tasklet,tasklet_handler,0); tasklet_schedule(&my_tasklet); return0; } static void __exit test_exit(void) { tasklet_kill(&tasklet); printk(KERN_ALERT,"test_exit is running./n"); } MODULE_LICENSE("GPL"); module_init(test_init); module_exit(test_exit);