linux內核筆記--中斷子系統之tasklet

中斷子系統之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");

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章