INIT_WORK–Linux 裝置驅動 Edition 3–7.6. 工作隊列

來源:互聯網
上載者:User
7.6. 工作隊列
工作隊列是, 表面上看, 類似於 taskets; 它們允許核心代碼來請求在將來某個時間調用一個函數. 但是, 有幾個顯著的不同在這 2 個之間, 包括:

tasklet 在軟體中斷上下文中啟動並執行結果是所有的 tasklet 代碼必須是原子的. 相反, 工作隊列函數在一個特殊核心進程上下文運行; 結果, 它們有更多的靈活性. 特別地, 工作隊列函數能夠睡眠.

tasklet 常常在它們最初被提交的處理器上運行. 工作隊列以相同地方式工作, 預設地.

核心代碼可以請求工作隊列函數被延後一個明確的時間間隔.

兩者之間關鍵的不同是 tasklet 執行的很快, 短時期, 並且在原子態, 而工作隊列函數可能有高周期但是不需要是原子的. 每個機制有它適合的情形.

工作隊列有一個 struct workqueue_struct 類型, 在 中定義. 一個工作隊列必須明確的在使用前建立, 使用一個下列的 2 個函數:

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

每個工作隊列有一個或多個專用的進程("核心線程"), 它運行提交給這個隊列的函數. 如果你使用 create_workqueue, 你得到一個工作隊列它有一個專用的線程在系統的每個處理器上. 在很多情況下, 所有這些線程是簡單的過度行為; 如果一個單個工作者線程就足夠, 使用 create_singlethread_workqueue 來代替建立工作隊列

提交一個任務給一個工作隊列, 你需要填充一個 work_struct 結構. 這可以在編譯時間完成, 如下:

DECLARE_WORK(name, void (*function)(void *), void *data);

這裡 name 是聲明的結構名稱, function 是從工作隊列被調用的函數, 以及 data 是一個傳遞給這個函數的值. 如果你需要建立 work_struct 結構在運行時, 使用下面 2 個宏定義:

INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);

INIT_WORK 做更加全面的初始化結構的工作; 你應當在第一次建立結構時使用它. PREPARE_WORK 做幾乎同樣的工作, 但是它不初始化用來串連 work_struct 結構到工作隊列的指標. 如果有任何的可能性這個結構當前被提交給一個工作隊列, 並且你需要改變這個隊列, 使用 PREPARE_WORK 而不是 INIT_WORK.

有 2 個函數來提交工作給一個工作隊列:

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 jiffies 已過去. 從這些函數的傳回值是 0 如果工作被成功加入到隊列; 一個非零結果意味著這個 work_struct 結構已經在隊列中等待, 並且第 2 次沒有加入.

在將來的某個時間, 這個工作函數將被使用給定的 data 值來調用. 這個函數將在工作者線程的上下文運行, 因此它可以睡眠如果需要 -- 儘管你應當知道這個睡眠可能怎樣影響提交給同一個工作隊列的其他任務. 這個函數不能做的是, 但是, 是存取使用者空間. 因為它在一個核心線程中運行, 完全沒有使用者空間來存取.

如果你需要取消一個掛起的工作隊列入口, 你可以調用:

int cancel_delayed_work(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 返回後, 沒有在這個調用前提交的函數在系統中任何地方運行.

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

void destroy_workqueue(struct workqueue_struct *queue);

7.6.1. 共用隊列
一個裝置驅動, 在許多情況下, 不需要它自己的工作隊列. 如果你只偶爾提交任務給隊列, 簡單地使用核心提供的共用的, 預設的隊列可能更有效. 如果你使用這個隊列, 但是, 你必須明白你將和別的在共用它. 從另一個方面說, 這意味著你不應當長時間獨佔隊列(無長睡眠), 並且可能要更長時間它們輪到處理器.

jiq ("just in queue") 模組輸出 2 個檔案來示範共用隊列的使用. 它們使用一個單個 work_struct structure, 這個結構這樣建立:

static struct work_struct jiq_work;
/* this line is in jiq_init() */
INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data);

當一個進程讀 /proc/jiqwq, 這個模組不帶延遲地初始化一系列通過共用的工作隊列的路線.

int schedule_work(struct work_struct *work);

注意, 當使用共用隊列時使用了一個不同的函數; 它只要求 work_struct 結構作為一個參數. 在 jiq 中的實際代碼看來如此:

prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);
schedule_work(&jiq_work);
schedule();
finish_wait(&jiq_wait, &wait);

這個實際的工作函數列印出一行就象 jit 模組所作的, 接著, 如果需要, 重新提交這個 work_structcture 到工作隊列中. 在這是 jiq_print_wq 全部:

static void jiq_print_wq(void *ptr)
{
struct clientdata *data = (struct clientdata *) ptr;

if (! jiq_print (ptr))
return;

if (data->delay)
schedule_delayed_work(&jiq_work, data->delay);
else
schedule_work(&jiq_work);
}

如果使用者在讀被延後的裝置 (/proc/jiqwqdelay), 這個工作函數重新提交它自己在延後的模式, 使用 schedule_delayed_work:

int schedule_delayed_work(struct work_struct *work, unsigned long delay);

如果你看從這 2 個裝置的輸出, 它看來如:

% cat /proc/jiqwq
time delta preempt pid cpu command
1113043 0 0 7 1 events/1
1113043 0 0 7 1 events/1
1113043 0 0 7 1 events/1
1113043 0 0 7 1 events/1
1113043 0 0 7 1 events/1
% cat /proc/jiqwqdelay
time delta preempt pid cpu command
1122066 1 0 6 0 events/0

1122067 1 0 6 0 events/0
1122068 1 0 6 0 events/0
1122069 1 0 6 0 events/0
1122070 1 0 6 0 events/0

當 /proc/jiqwq 被讀, 在每行的列印之間沒有明顯的延遲. 相反, 當 /proc/jiqwqdealy 被讀時, 在每行之間有恰好一個 jiffy 的延時. 在每一種情況, 我們看到同樣的進程名子被列印; 它是實現共用隊列的核心線程的名子. CPU 號被列印在斜線後面; 我們從不知道當讀 /proc 檔案時哪個 CPU 會在運行, 但是這個工作函數之後將一直運行在同一個處理器.

如果你需要取消一個已提交給工作隊列的工作入口, 你可以使用 cancel_delayed_work, 如上面所述. 重新整理共用隊列需要一個不同的函數, 但是:

void flush_scheduled_work(void);

因為你不知道別人誰可能使用這個隊列, 你從不真正知道 flush_schduled_work 返回可能需要多長時間.

--------------------------------------------------------------------------------

相關文章

Beyond APAC's No.1 Cloud

19.6% IaaS Market Share in Asia Pacific - Gartner IT Service report, 2018

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。