linux工作佇列機制
工作佇列(work queue)是Linux kernel中將工作推後執行的一種機制。這種機制和BH或Tasklets不同之處在於工作佇列是把推後的工作交由一個核心執行緒去執行,因此工作佇列的優勢就在於它允許重新排程甚至睡眠。
工作佇列是2.6核心開始引入的機制,在2.6.20之後,工作佇列的資料結構發生了一些變化,因此本文分成兩個部分對2.6.20之前和之後的版本分別做介紹。
I、2.6.0~2.6.19
資料結構: ?
1 2 3 4 5 6 7 8 |
struct
work_struct {
unsigned
long
pending;
struct
list_head entry;
void
(*func)(
void
*);
void
*data;
void
*wq_data;
struct
timer_list timer;
};
|
pending是用來記錄工作是否已經掛在佇列上;
entry是迴圈連結串列結構;
func作為函式指標,由使用者實現;
data用來儲存使用者的私人資料,此資料即是func的引數;
wq_data一般用來指向工作者執行緒(工作者執行緒參考下文);
timer是推後執行的定時器。
work_struct的這些變數裡,func和data是使用者使用的,其他是內部變數,我們可以不用太過關心。
API:
?
1 2 3 4 5 |
INIT_WORK(_work, _func, _data);
int
schedule_work(
struct
work_struct *work);
int
schedule_delayed_work(
struct
work_struct *work, unsigned
long
delay);
void
flush_scheduled_work(
void
);
int
cancel_delayed_work(
struct
work_struct *work);
|
1、初始化指定工作,目的是把使用者指定的函式_func及_func需要的引數_data賦給work_struct的func及data變數。
2、對工作進行排程,即把給定工作的處理函式提交給預設的工作佇列和工作者執行緒。工作者執行緒本質上是一個普通的核心執行緒,在預設情況下,每個CPU均有一個型別為“events”的工作者執行緒,當呼叫schedule_work時,這個工作者執行緒會被喚醒去執行工作連結串列上的所有工作。
3、延遲執行工作,與schedule_work類似。
4、重新整理預設工作佇列。此函式會一直等待,直到佇列中的所有工作都被執行。
5、flush_scheduled_work並不取消任何延遲執行的工作,因此,如果要取消延遲工作,應該呼叫cancel_delayed_work。
以上均是採用預設工作者執行緒來實現工作佇列,其優點是簡單易用,缺點是如果預設工作佇列負載太重,執行效率會很低,這就需要我們建立自己的工作者執行緒和工作佇列。
API:
?
1 2 3 4 5 |
struct
workqueue_struct *create_workqueue(
const
char
*name);
int
queue_work(
struct
workqueue_struct *wq,
struct
work_struct *work);
int
queue_delayed_work(
struct
workqueue_struct *wq,
struct
work_struct *work, unsigned
long
delay);
void
flush_workqueue(
struct
workqueue_struct *wq);
void
destroy_workqueue(
struct
workqueue_struct *wq);
|
1、建立新的工作佇列和相應的工作者執行緒,name用於該核心執行緒的命名。
2、類似於schedule_work,區別在於queue_work把給定工作提交給建立的工作佇列wq而不是預設佇列。
3、延遲執行工作。
4、重新整理指定工作佇列。
5、釋放建立的工作佇列。
下面一段程式碼可以看作一個簡單的實作:
?1 2 3 4 5 6 7 8 9 10 11 12 13 |
void
my_func(
void
*data)
{
char
*name = (
char
*)data;
printk(KERN_INFO “Hello world, my name is %s!\n”, name);
}
struct
workqueue_struct *my_wq = create_workqueue(“my wq”);
struct
work_struct my_work;
INIT_WORK(&my_work, my_func, “Jack”);
queue_work(my_wq, &my_work);
destroy_workqueue(my_wq);
|
II、2.6.20~2.6.??
自 2.6.20 起,工作佇列的資料結構發生了一些變化,使用時不能沿用舊的方法。
資料結構:
?
1 2 3 4 5 6 7 |
typedef
void
(*work_func_t)(
struct
work_struct *work);
struct
work_struct {
atomic_long_t data;
struct
list_head entry;
work_func_t func;
};
|
與2.6.19之前的版本相比,work_struct瘦身不少。粗粗一看,entry和之前的版本相同,func和data發生了變化,另外並無其他的變數。
entry我們不去過問,這個和以前的版本完全相同。data的型別是atomic_long_t,這個型別從字面上看可以知道是一個原子型別。第一次看到這個變數時,很容易誤認為和以前的data是同樣的用法,只不過型別變了而已,其實不然,這裡的data是之前版本的pending和wq_data的複合體,起到了以前的pending和wq_data的作用。
func的引數是一個work_struct指標,指向的資料就是定義func的work_struct。
看到這裡,會有兩個疑問,第一,如何把使用者的資料作為引數傳遞給func呢?以前有void *data來作為引數,現在好像完全沒有辦法做到;第二,如何實現延遲工作?目前版本的work_struct並沒有定義timer。
解決第一個問題,需要換一種思路。2.6.20版本之後使用工作佇列需要把work_struct定義在使用者的資料結構中,然後通過container_of來得到使用者資料。具體用法可以參考稍後的實作。
對於第二個問題,新的工作佇列把timer拿掉的用意是使得work_struct更加單純。首先回憶一下之前版本,只有在需要延遲執行工作時才會用到timer,普通情況下timer是沒有意義的,所以之前的做法在一定程度上有些浪費資源。所以新版本中,將timer從work_struct中拿掉,然後又定義了一個新的結構delayed_work用於處理延遲執行:
?
1 2 3 4 |
struct
delayed_work {
struct
work_struct work;
struct
timer_list timer;
};
|
下面把API羅列一下,每個函式的解釋可參考之前版本的介紹或者之後的實作:
?
1 2 3 4 5 6 7 8 9 10 11 |
INIT_WORK(
struct
work_struct *work, work_func_t func);
INIT_DELAYED_WORK(
struct
delayed_work *work, work_func_t func);
int
schedule_work(
struct
work_struct *work);
int
schedule_delayed_work(
struct
delayed_work *work, unsigned
long
delay);
struct
workqueue_struct *create_workqueue(
const
char
*name);
int
queue_work(
struct
workqueue_struct *wq,
struct
work_struct *work);
int
queue_delayed_work(
struct
workqueue_struct *wq,
struct
delayed_work *work, unsigned
long
delay);
void
flush_scheduled_work(
void
);
void
flush_workqueue(
struct
workqueue_struct *wq);
int
cancel_delayed_work(
struct
delayed_work *work);
void
destroy_workqueue(
struct
workqueue_struct *wq);
|
其中,1、2、4、7和以前略有區別,其他用法完全一樣。
實作:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct
my_struct_t {
char
*name;
struct
work_struct my_work;
};
void
my_func(
struct
work_struct *work)
{
struct
my_struct_t *my_name = container_of(work,
struct
my_struct_t, my_work);
printk(KERN_INFO “Hello world, my name is %s!\n”, my_name->name);
}
struct
workqueue_struct *my_wq = create_workqueue(“my wq”);
struct
my_struct_t my_name;
my_name.name = “Jack”;
INIT_WORK(&(my_name.my_work), my_func);
queue_work(my_wq, &(my_name.my_work));
destroy_workqueue(my_wq);
|