## 7.6.?工作隊列
工作隊列是, 表面上看, 類似于 taskets; 它們允許內核代碼來請求在將來某個時間調用一個函數. 但是, 有幾個顯著的不同在這 2 個之間, 包括:
-
tasklet 在軟件中斷上下文中運行的結果是所有的 tasklet 代碼必須是原子的. 相反, 工作隊列函數在一個特殊內核進程上下文運行; 結果, 它們有更多的靈活性. 特別地, 工作隊列函數能夠睡眠.
-
tasklet 常常在它們最初被提交的處理器上運行. 工作隊列以相同地方式工作, 缺省地.
-
內核代碼可以請求工作隊列函數被延后一個明確的時間間隔.
兩者之間關鍵的不同是 tasklet 執行的很快, 短時期, 并且在原子態, 而工作隊列函數可能有高周期但是不需要是原子的. 每個機制有它適合的情形.
工作隊列有一個 struct workqueue_struct 類型, 在 <linux/workqueue.h> 中定義. 一個工作隊列必須明確的在使用前創建, 使用一個下列的 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 返回可能需要多長時間.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考