7.6. 工作隊(duì)列

2018-02-24 15:50 更新

7.6.?工作隊(duì)列

工作隊(duì)列是, 表面上看, 類似于 taskets; 它們允許內(nèi)核代碼來請求在將來某個時間調(diào)用一個函數(shù). 但是, 有幾個顯著的不同在這 2 個之間, 包括:

  • tasklet 在軟件中斷上下文中運(yùn)行的結(jié)果是所有的 tasklet 代碼必須是原子的. 相反, 工作隊(duì)列函數(shù)在一個特殊內(nèi)核進(jìn)程上下文運(yùn)行; 結(jié)果, 它們有更多的靈活性. 特別地, 工作隊(duì)列函數(shù)能夠睡眠.

  • tasklet 常常在它們最初被提交的處理器上運(yùn)行. 工作隊(duì)列以相同地方式工作, 缺省地.

  • 內(nèi)核代碼可以請求工作隊(duì)列函數(shù)被延后一個明確的時間間隔.

兩者之間關(guān)鍵的不同是 tasklet 執(zhí)行的很快, 短時期, 并且在原子態(tài), 而工作隊(duì)列函數(shù)可能有高周期但是不需要是原子的. 每個機(jī)制有它適合的情形.

工作隊(duì)列有一個 struct workqueue_struct 類型, 在 <linux/workqueue.h> 中定義. 一個工作隊(duì)列必須明確的在使用前創(chuàng)建, 使用一個下列的 2 個函數(shù):


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

每個工作隊(duì)列有一個或多個專用的進(jìn)程("內(nèi)核線程"), 它運(yùn)行提交給這個隊(duì)列的函數(shù). 如果你使用 create_workqueue, 你得到一個工作隊(duì)列它有一個專用的線程在系統(tǒng)的每個處理器上. 在很多情況下, 所有這些線程是簡單的過度行為; 如果一個單個工作者線程就足夠, 使用 create_singlethread_workqueue 來代替創(chuàng)建工作隊(duì)列

提交一個任務(wù)給一個工作隊(duì)列, 你需要填充一個 work_struct 結(jié)構(gòu). 這可以在編譯時完成, 如下:


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

這里 name 是聲明的結(jié)構(gòu)名稱, function 是從工作隊(duì)列被調(diào)用的函數(shù), 以及 data 是一個傳遞給這個函數(shù)的值. 如果你需要建立 work_struct 結(jié)構(gòu)在運(yùn)行時, 使用下面 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 做更加全面的初始化結(jié)構(gòu)的工作; 你應(yīng)當(dāng)在第一次建立結(jié)構(gòu)時使用它. PREPARE_WORK 做幾乎同樣的工作, 但是它不初始化用來連接 work_struct 結(jié)構(gòu)到工作隊(duì)列的指針. 如果有任何的可能性這個結(jié)構(gòu)當(dāng)前被提交給一個工作隊(duì)列, 并且你需要改變這個隊(duì)列, 使用 PREPARE_WORK 而不是 INIT_WORK.

有 2 個函數(shù)來提交工作給一個工作隊(duì)列:


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);

每個都添加工作到給定的隊(duì)列. 如果使用 queue_delay_work, 但是, 實(shí)際的工作沒有進(jìn)行直到至少 delay jiffies 已過去. 從這些函數(shù)的返回值是 0 如果工作被成功加入到隊(duì)列; 一個非零結(jié)果意味著這個 work_struct 結(jié)構(gòu)已經(jīng)在隊(duì)列中等待, 并且第 2 次沒有加入.

在將來的某個時間, 這個工作函數(shù)將被使用給定的 data 值來調(diào)用. 這個函數(shù)將在工作者線程的上下文運(yùn)行, 因此它可以睡眠如果需要 -- 盡管你應(yīng)當(dāng)知道這個睡眠可能怎樣影響提交給同一個工作隊(duì)列的其他任務(wù). 這個函數(shù)不能做的是, 但是, 是存取用戶空間. 因?yàn)樗谝粋€內(nèi)核線程中運(yùn)行, 完全沒有用戶空間來存取.

如果你需要取消一個掛起的工作隊(duì)列入口, 你可以調(diào)用:


int cancel_delayed_work(struct work_struct *work); 

返回值是非零如果這個入口在它開始執(zhí)行前被取消. 內(nèi)核保證給定入口的執(zhí)行不會在調(diào)用 cancel_delay_work 后被初始化. 如果 cancel_delay_work 返回 0, 但是, 這個入口可能已經(jīng)運(yùn)行在一個不同的處理器, 并且可能仍然在調(diào)用 cancel_delayed_work 后在運(yùn)行. 要絕對確保工作函數(shù)沒有在 cancel_delayed_work 返回 0 后在任何地方運(yùn)行, 你必須跟隨這個調(diào)用來調(diào)用:


void flush_workqueue(struct workqueue_struct *queue); 

在 flush_workqueue 返回后, 沒有在這個調(diào)用前提交的函數(shù)在系統(tǒng)中任何地方運(yùn)行.

當(dāng)你用完一個工作隊(duì)列, 你可以去掉它, 使用:


void destroy_workqueue(struct workqueue_struct *queue); 

7.6.1.?共享隊(duì)列

一個設(shè)備驅(qū)動, 在許多情況下, 不需要它自己的工作隊(duì)列. 如果你只偶爾提交任務(wù)給隊(duì)列, 簡單地使用內(nèi)核提供的共享的, 缺省的隊(duì)列可能更有效. 如果你使用這個隊(duì)列, 但是, 你必須明白你將和別的在共享它. 從另一個方面說, 這意味著你不應(yīng)當(dāng)長時間獨(dú)占隊(duì)列(無長睡眠), 并且可能要更長時間它們輪到處理器.

jiq ("just in queue") 模塊輸出 2 個文件來演示共享隊(duì)列的使用. 它們使用一個單個 work_struct structure, 這個結(jié)構(gòu)這樣建立:


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

當(dāng)一個進(jìn)程讀 /proc/jiqwq, 這個模塊不帶延遲地初始化一系列通過共享的工作隊(duì)列的路線.


int schedule_work(struct work_struct *work); 

注意, 當(dāng)使用共享隊(duì)列時使用了一個不同的函數(shù); 它只要求 work_struct 結(jié)構(gòu)作為一個參數(shù). 在 jiq 中的實(shí)際代碼看來如此:


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

這個實(shí)際的工作函數(shù)打印出一行就象 jit 模塊所作的, 接著, 如果需要, 重新提交這個 work_structcture 到工作隊(duì)列中. 在這是 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);
}

如果用戶在讀被延后的設(shè)備 (/proc/jiqwqdelay), 這個工作函數(shù)重新提交它自己在延后的模式, 使用 schedule_delayed_work:


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

如果你看從這 2 個設(shè)備的輸出, 它看來如:


% 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  

當(dāng) /proc/jiqwq 被讀, 在每行的打印之間沒有明顯的延遲. 相反, 當(dāng) /proc/jiqwqdealy 被讀時, 在每行之間有恰好一個 jiffy 的延時. 在每一種情況, 我們看到同樣的進(jìn)程名子被打印; 它是實(shí)現(xiàn)共享隊(duì)列的內(nèi)核線程的名子. CPU 號被打印在斜線后面; 我們從不知道當(dāng)讀 /proc 文件時哪個 CPU 會在運(yùn)行, 但是這個工作函數(shù)之后將一直運(yùn)行在同一個處理器.

如果你需要取消一個已提交給工作隊(duì)列的工作入口, 你可以使用 cancel_delayed_work, 如上面所述. 刷新共享隊(duì)列需要一個不同的函數(shù), 但是:


void flush_scheduled_work(void); 

因?yàn)槟悴恢绖e人誰可能使用這個隊(duì)列, 你從不真正知道 flush_schduled_work 返回可能需要多長時間.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號