linux設備驅動程序學習(7) tasklet,工作隊列,共享隊列

tasklet

tasklet  vs  內核定時器

相同:始終在中斷期間運行,始終會在調度他們的同一CPU上運行,而且都接收一個unsigned long參數

不同:不可以要求tasklet在某一給定的時間執行

tasklet對中斷處理例程來說尤其有用。中斷處理例程必須儘可能快的管理硬件中斷,而大部分數據管理則可以安全的延遲到其後的時間。

實際上,與內核定時器類似,tasklet也會在“軟件中斷”上下文以原子模式執行。軟件中斷指打開硬件中斷的同時執行某些異步任務的內核機制。

tasklet以數據結構形式存在,並在使用前必須初始化。調用特定的函數或者使用特定的宏來聲明該結構,即可完成tasklet的初始化:

#include<linux/interrupt.h>

struct tasklet_struct{

         /*......*/

         void(*func)(unsigned long);

         unsigned long data;

};

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

#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 }

void tasklet_disable(struct tasklet_struct *t);
/*函數暫時禁止給定的 tasklet被 tasklet_schedule 調度,直到這個 tasklet 被再次被enable;若這個 tasklet 當前在運行, 這個函數忙等待直到這個tasklet退出*/


void tasklet_disable_nosync(struct tasklet_struct *t);
/*和tasklet_disable類似,但是tasklet可能仍然運行在另一個 CPU */


void tasklet_enable(struct tasklet_struct *t);
/*使能一個之前被disable的 tasklet;若這個 tasklet 已經被調度, 它會很快運行。 tasklet_enable 和tasklet_disable必須匹配調用, 因爲內核跟蹤每個 tasklet 的"禁止次數"*/


void tasklet_schedule(struct tasklet_struct *t);
/*調度 tasklet 執行,如果tasklet在運行中被調度, 它在完成後會再次運行; 這保證了在其他事件被處理當中發生的事件受到應有的注意. 這個做法也允許一個 tasklet 重新調度它自己*/


void tasklet_hi_schedule(struct tasklet_struct *t);
/*和tasklet_schedule類似,只是在更高優先級執行。當軟中斷處理運行時, 它處理高優先級 tasklet 在其他軟中斷之前,只有具有低響應週期要求的驅動才應使用這個函數, 可避免其他軟件中斷處理引入的附加週期*/


void tasklet_kill(struct tasklet_struct *t);
/*確保了 tasklet 不會被再次調度來運行,通常當一個設備正被關閉或者模塊卸載時被調用。如果 tasklet 正在運行, 這個函數等待直到它執行完畢。若 tasklet 重新調度它自己,則必須阻止在調用 tasklet_kill 前它重新調度它自己,如同使用 del_timer_sync*/

 

tasklet特性:

1.一個tasklet可在稍後被禁止或者重新啓用;只有啓用的次數和禁止的次數相同時,tasklet纔會被執行。

2.和定時器類似,tasklet可以自己註冊自己。

3.tasklet可被調度以在通常的優先級或者高優先級執行。高優先級的tasklet總會優先執行。

4.如果系統負荷不重,則tasklet會立即執行,但始終不會晚於下一個定時器滴答

5.一個tasklet可以和其它tasklet併發,但對自身來講是嚴格串行處理的,也就是說,同一tasklet永遠不會在多個處理器上同時運行:tasklet始終會調度自己在同一CPU上運行;

工作隊列

表面來看,工作隊列類似於tasklet:允許內核代碼請求某個函數在將來的時間被調用。

但其實還是有很多不同:

1.tasklet在軟中斷上下文中運行,因此,所有的tasklet代碼都是原子的。相反,工作隊列函數在一個特殊的內核進程上下文中運行,因此他們有更好的靈活性

尤其是,工作隊列可以休眠!

2.tasklet始終運行在被初始提交的統一處理器上,但這只是工作隊列的默認方式

3.內核代碼可以請求工作隊列函數的執行延遲給定的時間間隔

4.tasklet 執行的很快, 短時期, 並且在原子態, 而工作隊列函數可能是長週期且不需要是原子的,兩個機制有它適合的情形。

 

兩者的關鍵區別:tasklet會在很短的時間內很快執行,並且以原子模式執行,而工作隊列函數可以具有更長的延遲並且不必原子化。兩種機制有各自適合的情形。

 

工作隊列有 struct workqueue_struct 類型,在 <linux/workqueue.h> 中定義。一個工作隊列必須明確的在使用前創建,宏爲:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

 

每個工作隊列有一個或多個專用的進程("內核線程"), 這些進程運行提交給這個隊列的函數。 若使用 create_workqueue, 就得到一個工作隊列它在系統的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統性能有影響,如果單個線程就足夠則使用 create_singlethread_workqueue 來創建工作隊列。

提交一個任務給一個工作隊列,在這裏《LDD3》介紹的內核2.6.10和我用的新內核2.6.22.2已經有不同了,老接口已經不能用了,編譯會出錯。這裏我只講2.6.22.2的新接口,至於老的接口我想今後內核不會再有了。從這一點我們可以看出內核發展。

 

/*需要填充work_struct或delayed_work結構,可以在編譯時完成, 宏如下: */

struct work_struct {
    atomic_long_t data;
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    struct list_head entry;
    work_func_t func;
};

struct delayed_work {
    struct work_struct work;
    struct timer_list timer;
};

DECLARE_WORK(n, f)    
/*n 是聲明的work_struct結構名稱, f是要從工作隊列被調用的函數*/
DECLARE_DELAYED_WORK(n, f)
/*n是聲明的delayed_work結構名稱, f是要從工作隊列被調用的函數*/

/*若在運行時需要建立 work_struct 或 delayed_work結構, 使用下面 2 個宏定義:*/
INIT_WORK(struct work_struct *work, void (*function)(void *));
PREPARE_WORK(struct work_struct *work, void (*function)(void *));
INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));

/* INIT_* 做更加全面的初始化結構的工作,在第一次建立結構時使用. PREPARE_* 做幾乎同樣的工作, 但是它不初始化用來連接 work_struct或delayed_work 結構到工作隊列的指針。如果這個結構已經被提交給一個工作隊列, 且只需要修改該結構,則使用 PREPARE_* 而不是 INIT_* */

/*有 2 個函數來提交工作給一個工作隊列:*/
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);

/*每個都添加work到給定的workqueue。如果使用 queue_delay_work, 則實際的工作至少要經過指定的 jiffies 纔會被執行。 這些函數若返回 1 則工作被成功加入到隊列; 若爲0,則意味着這個 work 已經在隊列中等待,不能再次加入*/

在將來的某個時間, 這個工作函數將被傳入給定的 data 值來調用。這個函數將在工作線程的上下文運行, 因此它可以睡眠 (你應當知道這個睡眠可能影響提交給同一個工作隊列的其他任務) 工作函數不能訪問用戶空間,因爲它在一個內核線程中運行, 完全沒有對應的用戶空間來訪問。
取消一個掛起的工作隊列入口項可以調用:

int cancel_delayed_work(struct delayed_work *work);
void cancel_work_sync(struct work_struct *work)

如果這個入口在它開始執行前被取消,則返回非零。內核保證給定入口的執行不會在調用 cancel_delay_work 後被初始化. 如果 cancel_delay_work 返回 0, 但是, 這個入口可能已經運行在一個不同的處理器, 並且可能仍然在調用 cancel_delayed_work 後在運行. 要絕對確保工作函數沒有在 cancel_delayed_work 返回 0 後在任何地方運行, 你必須跟隨這個調用來調用:

void flush_workqueue(struct workqueue_struct *queue);

在 flush_workqueue 返回後, 沒有在這個調用前提交的函數在系統中任何地方運行。
而cancel_work_sync會取消相應的work,但是如果這個work已經在運行那麼cancel_work_sync會阻塞,直到work完成並取消相應的work。

當用完一個工作隊列,可以去掉它,使用:

void destroy_workqueue(struct workqueue_struct *queue);

共享隊列

在許多情況下, 設備驅動不需要它自己的工作隊列。如果你只偶爾提交任務給隊列, 簡單地使用內核提供的共享的默認的隊列可能更有效。若使用共享隊列,就必須明白將和其他人共享它,這意味着不應當長時間獨佔隊列(不能長時間睡眠), 並且可能要更長時間才能獲得處理器。

使用的順序:
(1) 建立 work_struct 或  delayed_work

static struct work_struct jiq_work;
static struct delayed_work jiq_work_delay;

    /* this line is in jiq_init() */
INIT_WORK(&jiq_work, jiq_print_wq);
INIT_DELAYED_WORK(&jiq_work_delay, jiq_print_wq);

(2)提交工作

int schedule_work(&jiq_work);/*對於work_struct結構*/
int schedule_delayed_work(&jiq_work_delay, delay);/*對於delayed_work結構*/

/*返回值的定義和 queue_work 一樣*/

若需取消一個已提交給工作隊列入口項, 可以使用 cancel_delayed_work和cancel_work_sync, 但刷新共享隊列需要一個特殊的函數:

void flush_scheduled_work(void);

因爲不知道誰可能使用這個隊列,因此不可能知道 flush_schduled_work 返回需要多長時間。

 

 

發佈了32 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章