1.tasklet概述
下半部和退后執行的工作,軟中斷的使用只在那些執行頻率很高和連續性要求很高的情況下才需要。在大多數情況下,為了控制一個尋常的硬件設備,tasklet機制都是實現自己下半部的最佳選擇。其實tasklet是利用軟中斷實現的一種下半部機制。tasklet和軟中斷在本質上很相似,行為表現也相近。tasklet有兩類中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。這兩者之間唯一的區別在於HI_SOFTIRQ類型的軟中斷先於TASKLET_SOFTIRQ類型的軟中斷執行。
2.tasklet的定義
tasklet由tasklet_struct結構表示,每個結構體單獨代表一個tasklet,在<linux/interrupt.h>中定義為:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
1.next:鏈表中的下一個tasklet;
2.state:tasklet的狀態。有三個取值:0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之間的取值。TASKLET_STATE_SCHED表明tasklet已經被調度,正准備投入運行,TASKLET_STATE_RUN表示該tasklet正在運行。
3.count:tasklet的引用計數,如果它不為0,則tasklet被禁止,不允許執行;只有當它為0時,tasklet才被激活,並且在被設置為掛起(TASKLET_STATE_SCHED)狀態時,該tasklet才能夠被執行。
4.結構體中的func成員是tasklet的處理程序,data是它唯一的參數。
3.tasklet的調度
內核使用tasklet_schedule()函數來執行tasklet的調度,已調度的tasklet存放在兩個單處理器數據結構:tasklet_vec(普通的tasklet)和tasklet_hi_vec(高優先級的tasklet)。這兩個數據結構都是由tasklet_struct結構體構成的鏈表。鏈表中的每個tasklet_struct 代表一個不同的tasklet。
tasklet是由tasklet_schedule()和tasklet_hi_shedule()函數進行調度的,它們接受一個指向tasklet_struct結構的指針作為參數。兩個函數非常相似(區別在於一個使用TASKLET_SOFTIRQ,一個使用HI_SOFTIRQ()),接下來我們來看下tasklet_schedule()的內核源碼:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
這個函數檢查tasklet的狀態,如果為TASKLET_STATE_SCHED,則表示已經被調度過了,直接返回。如果沒有調度的話就會去執行__tasklet_schedule(t);
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags); //保存IF標志的狀態,並禁用本地中斷
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t; //下面的這兩行代碼就是為該tasklet分配per_cpu變量
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ); //觸發軟中斷,讓其在下一次do_softirq()的時候,有機會被執行
local_irq_restore(flags); //恢復前面保存的標志
}
由上面的代碼可以看出,每次中斷只會向其中的一個cpu注冊,而不是所有的cpu。完成注冊后的tasklet由tasklet_action()函數來執行。
4.執行tasklet
前面說過tasklet被放在一個全局的tasklet_vec的鏈表中,鏈表中的元素是tasklet_struct結構體。內核中有個ksoftirqd()的內核線程,它會周期的遍歷軟中斷的向量列表,如果發現哪個軟中斷向量被掛起了(pending),就執行相應的處理函數。tasklet對應的處理函數就是tasklet_action,這個函數在系統啟動初始化軟中斷時,就在軟中斷向量表中注冊。tasklet_action()遍歷全局的tasklet_vec鏈表。鏈表中的元素為tasklet_struct結構體
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).head; //得到當前處理器上的tasklet鏈表tasklet_vec或者tasklet_hi_vec.
__get_cpu_var(tasklet_vec).head = NULL; //將當前處理器上的該鏈表設置為NULL, 達到清空的效果。
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
local_irq_enable(); //允許響應中斷
//循環遍歷獲得鏈表上的每一個待處理的tasklet
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
在遍歷執行時,在tasklet_trylock()和tasklet_unlock()這一段函數中,完成的功能是:首先會去檢查count值時否為0,前面已經分析過,當值不為0的時候,說明該tasklet被禁止,如果沒有被禁止,則執行其注冊的函數,首先會檢查tasklet_state的標志位是否是TASKLET_STATE_RUN狀態,如果是,則表示該任務已經在別的處理器上運行,如果沒有運行,則將其狀態標志設置為TASKLET_STATE_RUN,這樣別的處理器就不會再去執行它了,這就保證了在同一時間里,相同類型的tasklet只能有一個在運行。
5.Tsklet提供的API
5.1.聲明一個Tasklet
靜態創建
聲明一個tasklet,可以使用下面<linux/interrupt.h>中定義的兩個宏中的一個:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
這兩個宏都能根據給定的名字靜態的創建一個tasklet_struct結構。當該tasklet被調度后,給定的函數func會被執行,data為其參數。這兩個宏的區別在於前者前面一個宏把創建的tasklet的引用計數器設置為0,該tasklet處於激活狀態,另一個把引用計數器設置為1,所以該tasklet處於禁止狀態。
動態創建
也可以使用一個間接的引用(一個指針)賦給一個動態創建的tasklet_struct結構的方式來初始化一個tasklet_init()函數:
tasklet_init(t, tasklet_handler, dev) 動態創建
5.2.編寫tasklet處理函數
因為是軟中斷實現的,這就意味着不能在tasklet處理函數中使用信號量或者一些阻塞的函數。兩個相同的tasklet絕不會同時執行,所以,如果你的tasklet和其他的tasklet或者是軟中斷共享了數據,你必須進行相應的鎖保護。
5.3.調度
只有通過調度才能使我們的tasklet有機會被執行,這就使用上面提到的tasklet_shedule()函數。
tasklet_schedule(&my_tasklet);
5.4.禁止或者激活一個tasklet
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
可以用來禁止指定的tasklet,不過它無須再返回前等待tasklet執行完畢,這么做往往不太安全,因為你無法估計該tasklet是否仍在執行。
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_disable()函數來禁止某個指定的tasklet,如果該tasklet當前正在執行,這個函數會等到它執行完畢再返回。
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
tasklet_enable()函數可以激活一個tasklet。
5.5.刪除一個tasklet
通過調用tasklet_kill()函數從掛起的隊列中去掉一個tasklet。該函數的參數是一個指向某個tasklet的tasklet_struct的指針。這個函數會等待tasklet執行完畢,然后再將它移除。該函數可能會引起休眠,所以要禁止在中斷上下文中使用。
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
6.編寫自己的tasklet任務
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
char my_tasklet_data[]="my_tasklet_function was called";
/* Bottom Half Function */
void my_tasklet_function( unsigned long data )
{
printk( "%s\n", (char *)data );
}
//聲明tasklet
DECLARE_TASKLET( my_tasklet, my_tasklet_function,(unsigned long) &my_tasklet_data );
int init_module( void )
{
/* Schedule the Bottom Half */
printk("diable my_tasklet\n");
tasklet_disable(&my_tasklet);
// printk("enable my_tasklet\n");
// tasklet_enable(&my_tasklet);
tasklet_schedule( &my_tasklet );
return 0;
}
void cleanup_module( void )
{
/* Stop the tasklet before we exit */
tasklet_kill( &my_tasklet );
return;
}
MODULE_AUTHOR("support@ingben.com");
MODULE_DESCRIPTION ("tasklet of buttom half test2");
MODULE_LICENSE ("GPL v2");