在 中斷子系統之softirq中有介紹過,linux將中斷處理分成了兩部分,tasklet是下半部比較常用的機制,一般具有以下特點:
1.tasklet既可以動態分配,也可以靜態聲明,且數量不限。
2.tasklet只可以在一個CPU上同步地執行,不同的tasklet可以在不同地CPU上同步地執行。
3.tasklet在中斷上下文執行,因此不能被阻塞和休眠。
tasklet結構體:
struct tasklet_struct
{
struct tasklet_struct *next; /* 指向鏈表下一個tasklet */
unsigned long state; /* tasklet狀態 */
atomic_t count; /* 禁止計數器,調用tasklet_disable()會增加此數,tasklet_enable()減少此數 */
void (*func)(unsigned long); /* 處理函數 */
unsigned long data; /* 處理函數使用的數據 */
};
tasklet狀態主要分爲以下兩種:
enum
{
TASKLET_STATE_SCHED,
TASKLET_STATE_RUN
};
TASKLET_STATE_SCHED表示該tasklet是否在某個CPU上被調度。TASKLET_STATE_RUN表示該taskle正在某個cpu上執行。這兩個狀態主要就是用於防止tasklet同時在幾個CPU上運行和在一個CPU上交錯執行。
tasklet的定義
1.靜態定義tasklet
初始化tasklet並enable
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
初始化tasklet並disable
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
2.動態分配
struct tasklet_struct name;
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{
t->next = NULL; //
t->state = 0; //設置爲未調度 未運行
atomic_set(&t->count, 0); //默認使能
t->func = func; //調用函數
t->data = data; //調用函數參數
}
tasklet的調度
使用tasklet_shedule(),即可完成tasklet的調度。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
/*&t->state的第0位置爲1*/
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
/*關中斷並保存中斷狀態*/
local_irq_save(flags);
/*將tasklet掛入鏈表的尾部*/
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
/*觸發一個軟中斷*/
raise_softirq_irqoff(TASKLET_SOFTIRQ);
/*打開中斷並恢復中斷狀態*/
local_irq_restore(flags);
}
EXPORT_SYMBOL(__tasklet_schedule);
tasklet的執行
在系統初始化時註冊了兩個tasklet,
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
觸發軟中斷後會執行tasklet_action:
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
/*關中斷*/
local_irq_disable();
/*獲取該CPU上的tasklet鏈表*/
list = __this_cpu_read(tasklet_vec.head);
/*清空該CPU上的該軟中斷的taskleti鏈表*/
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
/*開中斷*/
local_irq_enable();
/*此時該CPU的tasklet鏈表爲空*/
while (list) {
struct tasklet_struct *t = list;
list = list->next;
/*檢查並該tasklet爲TASKLET_STATE_RUN*/
if (tasklet_trylock(t)) {
/*檢查是否被禁止*/
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
/*執行該tasklet的func函數*/
t->func(t->data);
/*清除tasklet running狀態*/
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
/*關中斷*/
local_irq_disable();
/*對於trylock失敗的節點,重新添加至鏈表尾部*/
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
從上面代碼可以看出,如果在執行過程中,某個tasklet的state爲TASKLET_STATE_RUN狀態,則把此tasklet加入到原先已清空的tasklet鏈表的末尾,然後設置__softirq_pending變量,這樣,在下次循環軟中斷的過程中,會再次運行這個tasklet。
這樣做的目的主要是:假設兩個CPU都需要執行同一個tasklet,中斷在CPU1上得到了響應,並且它的tasklet放到了CPU1的tasklet_vec上進行執行,而當中斷的tasklet上正在執行時,此中斷再次發生,並在CPU2上進行了響應,此時CPU2將此中斷的tasklet放到CPU2的tasklet_vec上,並執行到此中斷的tasklet。如果直接從鏈表刪除,那兩次中斷要觸發的tasket, 只在CPU0上執行了一次,第二次中斷中要觸發的tasket被丟失了
使用示例
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
void test_do_tasklet()
{
printk("this is a tasklet test!\n");
}
DECLARE_TASKLET(test_tasklet, test_do_tasklet, 0);
int test_task_irq = 0;
static irqreturn_t test_interrupt(int irq, void *data)
{
tasklet_schedule(&test_tasklet);
}
static int __init test_init(void)
{
int ret = 0;
ret = request_irq(test_task_irq, test_interrupt, IRQF_SHARED, "test_tasklet" , NULL);
if (ret)
printk("error allocating irq!\n");
return ret;
}
static void __exit test_exit(void)
{
free_irq(test_task_irq, test_interrupt);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");