Linux驅動:使用workqueue、tasklet處理中斷
背景
中斷服務程序一般都是在中斷請求關閉的條件下執行的,以避免嵌套而使中斷控制復雜化。但是,中斷是一個隨機事件,它隨時會到來,如果關中斷的時間太長,CPU就不能及時響應其他的中斷請求,從而造成中斷的丟失。
因此,Linux內核的目標就是盡可能快的處理完中斷請求,盡其所能把更多的處理向后推遲。
例如,假設一個數據塊已經達到了網線,當中斷控制器接受到這個中斷請求信號時,Linux內核只是簡單地標志數據到來了,然后讓處理器恢復到它以前運行的狀態,其余的處理稍后再進行(如把數據移入一個緩沖區,接受數據的進程就可以在緩沖區找到數據)。
因此,內核把中斷處理分為兩部分:上半部(tophalf)和下半部(bottomhalf),上半部(就是中斷服務程序)內核立即執行,而下半部(就是一些內核函數)留着稍后處理。
首先,一個快速的“上半部”來處理硬件發出的請求,它必須在一個新的中斷產生之前終止。通常,除了在設備和一些內存緩沖區(如果你的設備用到了DMA,就不止這些)之間移動或傳送數據,確定硬件是否處於健全的狀態之外,這一部分做的工作很少。
下半部運行時是允許中斷請求的,而上半部運行時是關中斷的,這是二者之間的主要區別。
但是,內核到底什時候執行下半部,以何種方式組織下半部?這就是我們要討論的下半部實現機制,這種機制在內核的演變過程中不斷得到改進,在以前的內核中,這個機制叫做bottomhalf(簡稱bh),在2.4以后的版本中有了新的發展和改進,改進的目標使下半部可以在多處理機上並行執行,並有助於驅動程序的開發者進行驅動程序的開發。
對於在上半部和下半部之間划分工作,盡管不存在某種嚴格的規則,但還是有一些提示可供借鑒:
- 如果一個任務對時問非常敏感,將其放在中斷處理程序中執行。
- 如果一個任務和硬件相關,將其放在中斷處理程序中執行。
- 如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程執行。
- 其他所有任務,考慮放置在下半部執行。
下面主要介紹常用的小任務(Tasklet
)機制及2.6內核中的工作隊列(workqueue
)機制。
在2.6版本的內核中,內核提供了三種不同形式的下半部實現機制:軟中斷、tasklets和workqueue。
參考:
- https://blog.csdn.net/qq_31505483/article/details/78534609
- http://seen.blog.chinaunix.net/uid-25695950-id-4471821.html
- http://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq
workqueue與tasklet的區別
從表面上看,workqueue
類似於tasklet
;允許內核在將來的某個時間調用一個函數。但是,兩者還是存在着顯著的差異,兩個機制有各自適合的情形:
1、運行的環境不同:
tasklet
運行在軟中斷上下文中,所以其代碼必須是原子操作。相反,workqueue運行在內核進程上下文中;結果就是workqueue具有更大的靈活性。特別是,workqueue可以休眠。tasklet
始終運行在最初提交它們的處理器上。默認情況下,workqueue以相同方式工作。
2、調度策略不同:可以將workqueue中的函數延遲一段時間后再執行(適用於長周期且不需要是原子的處理)
所以,workqueue
和tasklet
最大的不同就是tasklet
執行的更快,因為其是原子的;但因為workqueue不必是原子的,所以workqueue具有更高的延遲。
前面比較了workqueue與其他基於中斷上下文的延遲機制之間的優勢,但workqueue並非沒有缺點。首先是公共的共享workqueue不能提供更多的好處,因為如果其中的任一工作項阻塞,則其他工作項將不能被執行,因此在實際的使用中,使用者多會自己創建workqueue,而這又導致下面的一些問題:
-
MT的workqueue導致了內核的線程數增加得非常的快,這樣帶來一些問題:一個是占用了 pid 數目,這對於服務器可不是一個好消息,因為 pid實際上是一種全局資源;而大量的工作線程對於資源的競爭也導致了無效的調度,而這些調度其實是不需要的,對調度器也帶來了壓力。
-
現有的workqueue機制某些情況下有導致死鎖的傾向,特別是在兩個工作項之間存在依賴時。如果你曾經調試過這種偶爾出現的死鎖,會知道這種問題讓人非常的沮喪。
關於MT的部分詳見
並發可管理workqueue
。
什么情況下使用workqueue
,什么情況下使用tasklet
?
- 如果推后執行的任務需要睡眠(調用引起阻塞的函數),那么就選擇
workqueue
。 - 如果推后執行的任務不需要睡眠(調用不涉及引起阻塞的函數),那么就選擇
tasklet
。 - 另外,如果需要用一個可以重新調度的實體來執行你的下半部處理,也應該使用
workqueue
。
tasklet
在內核中的中斷機制中,為了防止解決中斷嵌套(防止一個中斷打斷另一個中斷)的問題,引進小任務(tasklet)機制,在中斷處理中tasklet機制被廣泛應用,對於中斷處理的實時性響應特別有幫助。
思想:硬件中斷必須盡快處理, 但大部分的數據管理可以延后到以后安全的時間執行
tasklet用於減少硬中斷處理的時間,將本來是在硬中斷服務程序中完成的任務轉化成軟中斷完成,即是:將一些非緊急的任務留到tasklet中完成,而緊急的任務則在硬中斷服務程序中完成。
使用步驟:
- 1、定義一個tasklet的執行任務;
- 2、初始化taskelet,將處理任務的函數和takslet任務通過
DECLARE_TASKLET
捆綁; - 3、調度tasklet :
tasklet_schedule(&tasklet);
特點:
- tasklet不能引起休眠,同一個tasklet不能在兩個CPU上同時運行。
- 但是不同tasklet可能在不同CPU上同時運行,則需要注意共享數據的保護。
聲明、初始化
Tasklet的使用比較簡單,關鍵在於定義tasklet及其處理函數並將兩者關聯。
#include <linux/interrupt.h>
#if 0 // 一個宏完成 關聯
DECLARE_TASKLET(tasklet_name, tasklet_func, func_data);
#else //相當於:
struct t asklet_struct tasklet_name;
tasklet_init(&tasklet_name, tasklet_func, func_data);
#endif
// 結構體原型
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
描述:將函數和一個tasklet綁定在一起。
參數解析:
- tasklet_name : tasklet的名字
- tasklet_func : 執行的tasklet的函數名
- func_data : 執行tasklet所附帶的參數,
unsigned long
,可以像ioctl一樣傳遞指針值進去。
執行
#include <linux/interrupt.h>
// 執行tasklet
tasklet_schedule(struct tasklet_struct* t);
描述:調用后,讓系統在適當的時候進行調度之前綁定好的tasklet。
參數解析:t: 某一個 tasklet 。
接口
tasklet 以一個數據結構形式存在,使用前必須被初始化。初始化能夠通過調用一個特定函數或者通過使用某些宏定義聲明結構:
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*/
例子
// 有關函數聲明
void xxx_do_tasklet(unsigned long);
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs);
// 聲明一個小任務,並綁定某個函數
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
// 構造這個模塊
int _init xxx_init(void)
{
// …… ;
// 注冊中斷
result = request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);
// …… ;
}
// 析構這個模塊
void _exit xxx_exit(void)
{
// …… ;
// 釋放中斷
free_irq(xxx_irq,xxx_irq_interrupt);
// 取消某個小任務
tasklet_kill(&xxx_tasklet);
// …… ;
}
// 寫好中斷處理函數
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
// …… ;
// 在某個中斷中調度小任務
tasklet_schedule(&xxx_tasklet);
// …… ;
}
// 執行tasklet
void xxx_do_tasklet(unsigned long)
{
// …… ;
}
workqueue
工作隊列(work queue)是另外一種將工作推后執行的形式,它和前面討論的tasklet有所不同。
workqueue其實相當於內核線程(因此可以睡眠,可以等待),創建一個workqueue后,如果有新的event發生(常常是中斷),可以將你的work提交到workqueue里后,內核會在合適的時間點去調用workqueue
來執行提交的work。
一個CPU上的所有worker線程共同構成了一個worker pool(此概念由內核v3.8引入)。
我們可能比較熟悉memory pool,當需要內存時,就從空余的memory pool中去獲取。
同樣地,當workqueue上有work item待處理時,我們就從worker pool里挑選一個空閑的worker線程來服務這個work item。
也就是說,這個下半部分可以在進程上下文中執行。這樣,通過workqueue執行的代碼能占盡進程上下文的所有優勢。最重要的就是workqueue允許被重新調度甚至是睡眠。
它是唯一能在進程上下文運行的下半部實現的機制,也只有它才可以睡眠。這意味着在需要獲得大量的內存時、在需要獲取信號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。如果不需要用一個內核線程來推后執行工作,那么就考慮使用tasklet。
如前所述,我們把推后執行的任務叫做工作(work),描述它的數據結構為work_struct
,這些工作以隊列結構組織成workqueue(workqueue),其數據結構為workqueue_struct
,而工作線程就是負責執行workqueue中的工作。
在討論之前,先定義幾個內核中使用workqueue時用到的術語方便后面描述。
- work queues:所有工作項被 ( 需要被執行的工作 ) 排列於該隊列,因此稱作workqueue (workqueues) 。
- worker thread:工作者線程 (worker thread) 是一個用於執行workqueue中各個工作項的內核線程,當workqueue中沒有工作項時,該線程將變為 idle 狀態。系統默認的工作者線程為events,也可以創建自己的工作者線程(每一條
workqueue
對應一條worker thread
)。
workqueue之所以成為使用最多的延遲執行機制,得益於它的實現中的一些有意思的地方:
- 使用的接口簡單明了
- 執行在進程上下文中,這樣使得它可以睡眠,被調度及被搶占
對於使用者,基本上只需要做 3 件事情,依次為:
- 創建workqueue(workqueue) ( 如果使用內核默認的workqueue,連這一步都可以省略掉 )
- 創建工作項(work item)
- 向workqueue中提交工作項
由於workqueue的實現中,已有默認的共享workqueue,因此在選擇接口時,就出現了2種選擇:要么使用內核已經提供的共享workqueue;要么自己創建workqueue。
使用步驟:
0、創建workqueue(可省略):create_workqueue
1、定義一個workqueue:struct work_struct my_wq;
2、聲明、實現處理任務:void my_wq_func(void* data);
3、通過INIT_WORK初始化這個workqueue並將workqueue與處理函數綁定:INIT_WORK(&my_wq, my_wq_func)
4、提交工作、適時調度workqueue:schedule_work(&my_wq)
使用workqueue
/* 原型*/
struct work_struct {
atomic_long_t data; /*工作處理函數func的參數*/
struct list_head entry; /*連接工作的鏈表結點*/
work_func_t func; /*工作處理函數*/
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
/*
"data"表示的意義就比較豐富了。這種在一個C語言變量里塞入不同的類型的數據的方法在Linux的代碼實現中還是不難見到的:
- 最后的4個bits是作為"flags"標志位使用的;
- 中間的4個bits是用於flush功能的"color",flush功能簡單地說就是:等待workqueue隊列上的任務都處理完,並清空workqueue隊列
- 剩下的bits在不同的場景下有不同的含義(相當於C語言里的"union"),它可以指向work item所在的workqueue隊列的地址,由於低8位被挪作他用,因此要求workqueue隊列的地址是按照256字節對齊的。它還可以表示處理work item的worker線程所在的pool的ID(關於pool將在本文的后半部分介紹)。
*/
};
創建workqueue(可選)
如果因為某些原因,如需要執行的是個阻塞性質的任務而不願或不能使用內核提供的共享workqueue,這時需要自己創建workqueue。
創建workqueue時,有 2 個選擇,可選擇系統范圍內的 ST,也可選擇每 CPU 一個內核線程的 MT。
- single threaded(ST)::工作者線程的表現形式之一,在系統范圍內,只有一個工作者線程為workqueue服務
- multi threaded(MT):工作者線程的表現形式之一,在多 CPU 系統上每個 CPU 上都有一個工作者線程為workqueue服務
#include <linux/workqueue.h>
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name; /*workqueue name*/
int singlethread; /*是不是單線程 - 單線程我們首選第一個CPU -0表示采用默認的工作者線程event*/
int freezeable; /* Freeze threads during suspend */
int rt;
};
struct workqueue_struct *create_workqueue(const char *name);
// MT形式,用於創建一個workqueue隊列,為系統中的每個CPU都創建一個內核線程。
struct workqueue_struct *create_singlethread_workqueue(const char *name);
// 用於創建workqueue,只創建一個內核線程。
//--------------
void destroy_workqueue(struct workqueue_struct *queue);
// 釋放workqueue隊列。
描述:創建workqueue線程,可用來指定work執行時的內核線程。
相對於create_singlethread_workqueue,create_workqueue 同樣會分配一個 wq的workqueue。
不同之處在於,對於多 CPU 系統而言,
create_workqueue
對每一個 active 的 CPU,都會為之創建一個 per-CPU 的 cwq結構,對應每一個 cwq,都會生成一個新的 worker_thread。
返回值:workqueue線程對象。
聲明、初始化一個工作項目
#if 0
/* 靜態創建工作項 */
typedef void (*work_func_t)(struct work_struct *work);
DECLARE_WORK(work, func);
DECLARE_DELAYED_WORK(work, func);
#else
/* 動態創建工作項 */
struct work_struct work;
INIT_WORK(struct work_struct *work, work_func_t func);
PREPARE_WORK(struct work_struct *work, work_func_t func);
INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);
PREPARE_DELAYED_WORK(struct delayed_work *work, work_func_t func);
#endif
該系列宏最終都會創建一個以 work
命名的工作項,並設置了回調函數 func
,例如:
void workqueue_func(void * arg);
#if 0
DECLARE_WORK(work, workqueue_func);
#else
// 相當於
struct work_struct work;
INIT_WORK(&work, workqueue_func);
#endif
描述:聲明一個工作,綁定對應的執行函數。
調度work
// 使用內核默認的 workqueue
int schedule_work(struct work_struct *work);
// 使用指定的 workqueue
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
描述:將工作項添加到某條wq,內核會適時執行。
參數解析:
- wq:workqueue
- work:需要執行的任務。
取消work
如果需要取消一個掛起的workqueue中的工作項 , 調用:
int cancel_delayed_work(struct work_struct *work);
描述:取消workqueue中掛起的工作項。
返回值:
- 如果這個工作項在它開始執行前被取消,返回值是非零。內核保證給定工作項的執行不會在調用 cancel_delay_work 成功后被執行。
- 如果 cancel_delay_work 返回0,則這個工作項可能已經運行在一個不同的處理器,並且仍然可能在調用 cancel_delayed_work之后被執行。要絕對確保工作函數沒有在 cancel_delayed_work 返回 0 后在任何地方運行,你必須跟隨這個調用之后接着調用flush_workqueue。在 flush_workqueue 返回后。任何在改調用之前提交的工作函數都不會在系統任何地方運行。
例子
這個例子展示了如何創建workqueue並在其中執行work。
struct my_work_struct{
int test;
struct work_struct save;
};
struct my_work_struct test_work;
struct workqueue_struct *test_workqueue;
void do_save(struct work_struct *p_work)
{
struct my_work_struct *p_test_work = container_of(p_work, struct my_work_struct, save);
printk("%d\n",p_test_work->test);
}
void test_init()
{
INIT_WORK(&(test_work.save), do_save);
test_work.test = 1;
#if 0
test_workqueue = create_workqueue("test_workqueue");
if (!test_workqueue)
panic("Failed to create test_workqueue\n");
queue_work(test_workqueue, &(test_work.save));
#else
schedule_work(&(test_work.save));
#endif
}
void test_destory(void)
{
if(test_workqueue)
destroy_workqueue(test_workqueue);
}
這個是常規中使用默認wq的例子。
delaywork
在內核中,除了work_struct外還有一個結構體delayed_work
該工作隊列里擁有一個timer定時器結構體,從而實現延時工作。
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
使用delaywork
定義
struct delayed_work my_delaywork;
初始化
綁定delaywork對應的函數
INIT_DELAYED_WORK(&my_delaywork , my_delaywork_func);
實現對應的回調函數:
static void my_delaywork_func(struct work_struct *work)
{
printk("delaywork running\n");
}
調度delaywork
// 使用內核默認的 workqueue
static inline bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay);
// 提交到指定的 wq 上執行。
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);
使用delayed workqueue最主要的是調用queue_delayed_work。
描述:提交work到某條wq,工作項隨后在某個合適時機將被執行。
參數解析:
- wq:workqueue
- work:需要執行的任務。
- dalay:延遲時間,以ms為單位。(內部通過注冊定時器實現延遲)最少延遲 delay jiffies 之后該工作才會被執行
例子:
schedule_delayed_work(&my_delaywork , msecs_to_jiffies(200));
取消delaywork
cancel_delayed_work_sync(&my_delaywork);
例子
#include <linux/workqueue.h>
// ...
struct my_work_struct{
int test;
struct delayed_work my_delay_work;
};
static struct my_work_struct test_work;
static struct workqueue_struct *test_workqueue;
void do_my_delay_work(struct work_struct *p_work)
{
// 注意,這里的container_of要求 `.work`。
struct my_work_struct *p_test_work = container_of(p_work, struct my_work_struct, my_delay_work.work);
printk("%d\n",p_test_work->test);
// 再次運行
schedule_delayed_work(&(test_work.my_delay_work), msecs_to_jiffies(200));
}
void test_init()
{
unsigned long delay_ms;
INIT_DELAYED_WORK(&(test_work.my_delay_work), do_my_delay_work);
test_work.test = 1;
// 延遲 200ms
delay_ms = msecs_to_jiffies(200);
#if 1
// 以 獨立的 wq 運行work
test_workqueue = create_workqueue("test_workqueue");
if (!test_workqueue)
panic("Failed to create test_workqueue\n");
queue_delayed_work(test_workqueue, &(test_work.my_delay_work), delay_ms);
#else
// 以內核中默認的wq運行work
schedule_delayed_work(&(test_work.my_delay_work), delay_ms);
#endif
}
void test_destory(void)
{
if(test_workqueue)
destroy_workqueue(test_workqueue);
}
並發可管理workqueue
介紹
並發可管理工作隊列:Concurrency-managed workqueues,CMWQ。
在2.6.36 之前的workqueue,其核心是每個workqueue都有專有的內核線程為其服務——系統范圍內的 ST 或每個 CPU 都有一個內核線程的MT。
新的 cmwq 在實現上摒棄了這一點:不再有專有的線程與每個workqueue關聯。事實上,現在變成了 Online CPU number + 1個線程池來為workqueue服務,這樣將線程的管理權實際上從workqueue的使用者交還給了內核。
當一個工作項被創建以及排隊,將在合適的時機被傳遞給其中一個線程,而 cmwq 最有意思的改變是:被提交到相同workqueue,相同 CPU 的工作項可能並發執行,這也是命名為並發可管理workqueue的原因。
兼容性
cmwq 的實現遵循了以下幾個原則:
- 與原有的workqueue接口保持兼容,cmwq 只是更改了創建workqueue的接口,很容易移植到新的接口。
- workqueue共享 per-CPU 的線程池,提供靈活的並發級別而不再浪費大量的資源。
- 自動平衡工作者線程池和並發級別,這樣workqueue的用戶不再需要關注如此多的細節。
在workqueue的用戶眼中,cmwq 與之前的workqueue相比,創建workqueue的接口實現的后端有所改變,現在的新接口為:alloc_workqueue
。其他用法與workqueue相同。
對於 MT 的情況,當用 queue_work 向cwq 上提交工作項節點時, 是哪個 active CPU 正在調用該函數,那么便向該 CPU 對應的 cwq 上的 worklist上增加工作項節點。
alloc_workqueue
struct workqueue_struct *alloc_workqueue(char *name, unsigned int flags, int max_active);
描述:創建cmwq。
參數解析:
name:為workqueue的名字,而不像 2.6.36 之前實際是為workqueue服務的內核線程的名字。
flag 指明workqueue的屬性,可以設定的標記如下:
- WQ_NON_REENTRANT:默認情況下,workqueue只是確保在同一 CPU 上不可重入,即工作項不能在同一 CPU上被多個工作者線程並發執行,但容許在多個 CPU 上並發執行。但該標志標明在多個 CPU上也是不可重入的,工作項將在一個不可重入workqueue中排隊,並確保至多在一個系統范圍內的工作者線程被執行。
- WQ_UNBOUND:工作項被放入一個由特定 gcwq 服務的未限定workqueue,該客戶工作者線程沒有被限定到特定的 CPU,這樣,未限定工作者隊列就像簡單的執行上下文一般,沒有並發管理。未限定的 gcwq 試圖盡可能快的執行工作項。
- WQ_FREEZEABLE:可凍結 wq 參與系統的暫停操作。該workqueue的工作項將被暫停,除非被喚醒,否者沒有新的工作項被執行。
- WQ_MEM_RECLAIM:所有的workqueue可能在內存回收路徑上被使用。使用該標志則保證至少有一個執行上下文而不管在任何內存壓力之下。
- WQ_HIGHPRI:高優先級的工作項將被排練在隊列頭上,並且執行時不考慮並發級別;換句話說,只要資源可用,高優先級的工作項將盡可能快的執行。高優先工作項之間依據提交的順序被執行。
- WQ_CPU_INTENSIVE:CPU 密集的工作項對並發級別並無貢獻,換句話說,可運行的 CPU 密集型工作項將不阻止其它工作項。這對於限定得工作項非常有用,因為它期望更多的 CPU 時鍾周期,所以將它們的執行調度交給系統調度器。
返回值:成功時返回一個workqueue_struct
實例。