工作隊列一般用來做滯后的工作,比如在中斷里面要做很多事,但是比較耗時,這時就可以把耗時的工作放到工作隊列。說白了就是系統(tǒng)延時調(diào)度的一個自定義函數(shù)。
工作隊列是實現(xiàn)延遲的新機(jī)制,從 2.5 版本 Linux 內(nèi)核開始提供該功能。不同于微線程一步到位的延遲方法,工作隊列采用通用的延遲機(jī)制, 工作隊列的處理程序函數(shù)能夠休眠(這在微線程模式下無法實現(xiàn))。 工作隊列可以有比微線程更高的時延,并為任務(wù)延遲提供功能更豐富的 API。 從前,延遲功能通過 keventd 對任務(wù)排隊來實現(xiàn), 但是現(xiàn)在由內(nèi)核工作線程 events/X 來管理。
工作隊列提供一個通用的辦法將任務(wù)延遲到 bottom halves。 處于核心的是工作隊列(結(jié)構(gòu)體 workqueue_struct), 任務(wù)被安排到該結(jié)構(gòu)體當(dāng)中。 任務(wù)由結(jié)構(gòu)體 work_struct 來說明, 用來鑒別哪些任務(wù)被延遲以及使用哪個延遲函數(shù)(參見 圖 3)。 events/X 內(nèi)核線程(每 CPU 一個)從工作隊列中抽取任務(wù)并激活一個 bottom-half 處理程序(由處理程序函數(shù)在結(jié)構(gòu)體 work_struct 中指定)。
圖 3. 工作隊列背后的處理過程
由于 work_struct 中指出了要采用的處理程序函數(shù), 因此可以利用工作隊列來為不同的處理程序進(jìn)行任務(wù)排隊。 現(xiàn)在,讓我們看一下能夠用于工作隊列的 API 函數(shù)。
工作隊列 API
工作隊列 API 比微線程稍復(fù)雜,主要是因為它支持很多選項。 我們首先探討一下工作隊列,然后再看一下任務(wù)和變體。
通過 圖 3 可以回想工作隊列的核心結(jié)構(gòu)體是隊列本身。 該結(jié)構(gòu)體用于將任務(wù)安排出 top half ,進(jìn)入 bottom half ,從而延遲它的執(zhí)行。 工作隊列通過宏調(diào)用生成 create_workqueue,返回一個 workqueue_struct 參考值。當(dāng)用完一個工作隊列,可以通過調(diào)用函數(shù) destroy_workqueue 來去掉它(如果需要):
struct workqueue_struct *create_workqueue( name ); struct workqueue_struct *create_singlethread_workqueue(const char *name); void destroy_workqueue( struct workqueue_struct * );
一個工作隊列必須明確的在使用前創(chuàng)建,若使用 create_workqueue, 就得到一個工作隊列它在系統(tǒng)的每個處理器上有一個專用的線程。在很多情況下,過多線程對系統(tǒng)性能有影響,如果單個線程就足夠則使用 create_singlethread_workqueue 來創(chuàng)建工作隊列。 通過工作隊列與之通信的任務(wù)可以由結(jié)構(gòu)體 work_struct 來定義。 通常,該結(jié)構(gòu)體是用來進(jìn)行任務(wù)定義的結(jié)構(gòu)體的第一個元素(后面有相關(guān)例子)。 工作隊列 API 提供三個函數(shù)來初始化任務(wù)(通過一個事先分配的緩存); 參見 清單 6。 宏 INIT_WORK 提供必需的初始化數(shù)據(jù)以及處理程序函數(shù)的配置(由用戶傳遞進(jìn)來)。 如果開發(fā)人員需要在任務(wù)被排入工作隊列之前發(fā)生延遲,可以使用宏 INIT_DELAYED_WORK 和 INIT_DELAYED_WORK_DEFERRABLE。
清單 6. 任務(wù)初始化宏
INIT_WORK( work, func ); INIT_DELAYED_WORK( work, func ); INIT_DELAYED_WORK_DEFERRABLE( work, func );
INIT_* 做更加全面的初始化結(jié)構(gòu)的工作,在第一次建立結(jié)構(gòu)時使用。 任務(wù)結(jié)構(gòu)體的初始化完成后,接下來要將任務(wù)安排進(jìn)工作隊列。 可采用多種方法來完成這一操作(參見 清單 7)。 首先,利用 queue_work 簡單地將任務(wù)安排進(jìn)工作隊列(這將任務(wù)綁定到當(dāng)前的 CPU)。 或者,可以通過 queue_work_on 來指定處理程序在哪個 CPU 上運行。 兩個附加的函數(shù)為延遲任務(wù)提供相同的功能(其結(jié)構(gòu)體裝入結(jié)構(gòu)體 work_struct 之中,并有一個 計時器用于任務(wù)延遲 )。
清單 7. 工作隊列函數(shù)
int queue_work( struct workqueue_struct *wq, struct work_struct *work ); int queue_work_on( int cpu, struct workqueue_struct *wq, struct work_struct *work ); int queue_delayed_work( struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay ); int queue_delayed_work_on( int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay );
每個都添加work到給定的workqueue。如果使用 queue_delay_work, 則實際的工作至少要經(jīng)過指定的 jiffies 才會被執(zhí)行。 這些函數(shù)若返回 1 則工作被成功加入到隊列; 若為0,則意味著這個 work 已經(jīng)在隊列中等待,不能再次加入。 可以使用全局的內(nèi)核全局工作隊列,利用 4 個函數(shù)來為工作隊列定位。 這些函數(shù)(見 清單 8)模擬 清單 7,只是不需要定義工作隊列結(jié)構(gòu)體。
清單 8. 內(nèi)核全局工作隊列函數(shù)
int schedule_work( struct work_struct *work ); int schedule_work_on( int cpu, struct work_struct *work ); int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay ); int scheduled_delayed_work_on( int cpu, struct delayed_work *dwork, unsigned long delay );
還有一些幫助函數(shù)用于清理或取消工作隊列中的任務(wù)。想清理特定的任務(wù)項目并阻塞任務(wù), 直到任務(wù)完成為止, 可以調(diào)用 flush_work 來實現(xiàn)。 指定工作隊列中的所有任務(wù)能夠通過調(diào)用 flush_workqueue 來完成。 這兩種情形下,調(diào)用者阻塞直到操作完成為止。 為了清理內(nèi)核全局工作隊列,可調(diào)用 flush_scheduled_work。
int flush_work( struct work_struct *work ); int flush_workqueue( struct workqueue_struct *wq ); void flush_scheduled_work( void );
在 flush_workqueue 返回后, 沒有在這個調(diào)用前提交的函數(shù)在系統(tǒng)中任何地方運行。 還沒有在處理程序當(dāng)中執(zhí)行的任務(wù)可以被取消。 調(diào)用 cancel_work_sync 將會終止隊列中的任務(wù)或者阻塞任務(wù)直到回調(diào)結(jié)束(如果處理程序已經(jīng)在處理該任務(wù))。 如果任務(wù)被延遲,可以調(diào)用 cancel_delayed_work_sync。
int cancel_work_sync( struct work_struct *work ); int cancel_delayed_work_sync( struct delayed_work *dwork );
最后,可以通過調(diào)用 work_pending 或者 delayed_work_pending 來確定任務(wù)項目是否在進(jìn)行中。
work_pending( work ); delayed_work_pending( work );
這就是工作隊列 API 的核心。在 ./kernel/workqueue.c 中能夠找到工作隊列 API 的實現(xiàn)方法, API 在 ./include/linux/workqueue.h 中定義。 下面我們看一個工作隊列 API 的簡單例子。
工作隊列簡單例子
下面的例子說明了幾個核心的工作隊列 API 函數(shù)。 如同微線程的例子一樣,為方便起見,可將這個例子部署在內(nèi)核模塊上下文。
首先,看一下將用于實現(xiàn) bottom half 的任務(wù)結(jié)構(gòu)體和處理程序函數(shù)(參見 清單 9)。 首先您將注意到工作隊列結(jié)構(gòu)體參考的定義 (my_wq)以及 my_work_t 的定義。 my_work_t 類型定義的頭部包括結(jié)構(gòu)體 work_struct 和一個代表任務(wù)項目的整數(shù)。 處理程序(回調(diào)函數(shù))將 work_struct 指針引用改為 my_work_t 類型。 發(fā)送出任務(wù)項目(來自結(jié)構(gòu)體的整數(shù))之后,任務(wù)指針將被釋放。
清單 9. 任務(wù)結(jié)構(gòu)體和 bottom-half 處理程序
#include <linux/kernel.h> #include <linux/module.h> #include <linux/workqueue.h>
MODULE_LICENSE("GPL");
static struct workqueue_struct *my_wq;
typedef struct { struct work_struct my_work; int x; } my_work_t;
my_work_t *work, *work2;
static void my_wq_function( struct work_struct *work) { my_work_t *my_work = (my_work_t *)work; printk( "my_work.x %d\n", my_work->x ); kfree( (void *)work ); return; }
清單 10 是 init_module 函數(shù), 該函數(shù)從使用 create_workqueue API 函數(shù)生成工作隊列開始。 成功生成工作隊列之后,創(chuàng)建兩個任務(wù)項目(通過 kmalloc 來分配)。 利用 INIT_WORK 來初始化每個任務(wù)項目,任務(wù)定義完成, 接著通過調(diào)用 queue_work 將任務(wù)安排到工作隊列中。 top-half 進(jìn)程(在此處模擬)完成。如同清單 10 中所示,任務(wù)有時會晚些被處理程序處理。
清單 10. 工作隊列和任務(wù)創(chuàng)建
int init_module( void ) { int ret;
my_wq = create_workqueue("my_queue"); if (my_wq) {
/* Queue some work (item 1) */ work = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL); if (work) { INIT_WORK( (struct work_struct *)work, my_wq_function ); work->x = 1; ret = queue_work( my_wq, (struct work_struct *)work ); }
/* Queue some additional work (item 2) */ work2 = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL); if (work2) { INIT_WORK( (struct work_struct *)work2, my_wq_function ); work2->x = 2; ret = queue_work( my_wq, (struct work_struct *)work2 ); } }
return 0; }
最終的元素在 清單 11 中展示。 在模塊清理過程中,會清理一些特別的工作隊列(它們將保持阻塞狀態(tài)直到處理程序完成對任務(wù)的處理), 然后銷毀工作隊列。
清單 11. 工作隊列清理和銷毀
void cleanup_module( void ) { flush_workqueue( my_wq ); destroy_workqueue( my_wq ); return; }
參考: 內(nèi)核的工作隊列使用方法
工作隊列一般用來做滯后的工作,比如在中斷里面要做很多事,但是比較耗時,這時就可以把耗時的工作放到工作隊列。說白了就是系統(tǒng)延時調(diào)度的一個自定義函數(shù)。
1、定義struct work_struct irq_queue;
2、初始化INIT_WORK(&irq_queue,do_irq_queuework);
3、調(diào)用方法:schedule_work(&rq_queue);
注,調(diào)用完畢后系統(tǒng)會釋放此函數(shù),所以如果想再次執(zhí)行的話,就再次調(diào)用schedule_work()即可。
另外,內(nèi)核必須掛載文件系統(tǒng)才可以使用工作隊列。我的理解是:工作隊列也屬于調(diào)度,如果內(nèi)核掛了,他就不調(diào)度了,當(dāng)然就不能用工作隊列了。
工作隊列接口 工作隊列接口是在2.5的開發(fā)過程中引入的,用于取代任務(wù)隊列接口(用于調(diào)度內(nèi)核任務(wù))。每個工作隊列有一個專門的線程, 所有來自運行隊列的任務(wù)在進(jìn)程的上下文中運行(這樣它們可以休眠)。驅(qū)動程序可以創(chuàng)建并使用它們自己的工作隊列,或者使用內(nèi)核的一個工作隊列。 工作隊列用以下方式創(chuàng)建: struct workqueue_struct *create_workqueue(const char *name); 在這里 name 是工作隊列的名字。
工作隊列任務(wù)可以在編譯時或者運行時創(chuàng)建。任務(wù)需要封裝為一個叫做 work_struct 的結(jié)構(gòu)體。在編譯期初始化一個工作隊列任務(wù)時要用到: DECLARE_WORK(name, void (*function)(void *), void *data); 在這里 name 是 work_struct 的名字,function 是當(dāng)任務(wù)被調(diào)度時調(diào)用的函數(shù),data 是指向那個函數(shù)的指針。
在運行期初始化一個工作隊列時要用到: INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
用下面的函數(shù)調(diào)用來把一個作業(yè)(一個類型為work_struct 結(jié)構(gòu)的工作隊列作業(yè)/任務(wù))加入到工作隊列中: int queue_work(struct workqueue_struct *queue, struct work_struct *work); int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay); 在queue_delay_work()中指定delay,是為了保證至少在經(jīng)過一段給定的最小延遲時間以后,工作隊列中的任務(wù)才可以真正執(zhí)行。
工作隊列中的任務(wù)由相關(guān)的工作線程執(zhí)行,可能是在一個無法預(yù)期的時間(取決于負(fù)載,中斷等等),或者是在一段延遲以后。任何一個在工作隊列中等待了無限長的時間也沒有運行的任務(wù)可以用下面的方法取消: int cancel_delayed_work(struct work_struct *work);
如果當(dāng)一個取消操作的調(diào)用返回時,任務(wù)正在執(zhí)行中,那么這個任務(wù)將繼續(xù)執(zhí)行下去,但不會再加入到隊列中。清空工作隊列中的所有任務(wù)使用: void flush_workqueue(struct workqueue_struct *queue);
銷毀工作隊列使用: void destroy_workqueue(struct workqueue_struct *queue); 不是所有的驅(qū)動程序都必須有自己的工作隊列。驅(qū)動程序可以使用內(nèi)核提供的缺省工作隊列。由于這個工作隊列由很多驅(qū)動程序共享, 任務(wù)可能會需要比較長一段時間才能開始執(zhí)行。為了解決這一問題,工作函數(shù)中的延遲應(yīng)該保持最小或者干脆不要。
需要特別注意的是缺省隊列對所有驅(qū)動程序來說都是可用的,但是只有經(jīng)過GP許可的驅(qū)動程序可以用自定義的工作隊列: int schedule_work(struct work_struct *work); -- 向工作隊列中添加一個任務(wù) int schedule_delayed_work(struct work_struct *work, unsigned long delay); -- 向工作隊列中添加一個任務(wù)并延遲執(zhí)行
當(dāng)模塊被缷載時應(yīng)該去調(diào)用一個 flash_scheduled_work() 函數(shù),這個函數(shù)會使等待隊列中所有的任務(wù)都被執(zhí)行。 |
|