## 7.5.?Tasklets 機制
另一個有關于定時問題的內核設施是 tasklet 機制. 它大部分用在中斷管理(我們將在第 10 章再次見到).
tasklet 類似內核定時器在某些方面. 它們一直在中斷時間運行, 它們一直運行在調度它們的同一個 CPU 上, 并且它們接收一個 unsigned long 參數. 不象內核定時器, 但是, 你無法請求在一個指定的時間執行函數. 通過調度一個 tasklet, 你簡單地請求它在以后的一個由內核選擇的時間執行. 這個行為對于中斷處理特別有用, 那里硬件中斷必須被盡快處理, 但是大部分的時間管理可以安全地延后到以后的時間. 實際上, 一個 tasket, 就象一個內核定時器, 在一個"軟中斷"的上下文中執行(以原子模式), 在使能硬件中斷時執行異步任務的一個內核機制.
一個 tasklet 存在為一個時間結構, 它必須在使用前被初始化. 初始化能夠通過調用一個特定函數或者通過使用某些宏定義聲明結構:
~~~
#include <linux/interrupt.h>
struct tasklet_struct {
/* ... */
void (*func)(unsigned long);
unsigned long data;
};
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);
~~~
tasklet 提供了許多有趣的特色:
-
一個 tasklet 能夠被禁止并且之后被重新使能; 它不會執行直到它被使能與被禁止相同的的次數.
-
如同定時器, 一個 tasklet 可以注冊它自己.
-
一個 tasklet 能被調度來執行以正常的優先級或者高優先級. 后一組一直是首先執行.
-
taslet 可能立刻運行, 如果系統不在重載下, 但是從不會晚于下一個時鐘嘀噠.
-
一個 tasklet 可能和其他 tasklet 并發, 但是對它自己是嚴格地串行的 -- 同樣的 tasklet 從不同時運行在超過一個處理器上. 同樣, 如已經提到的, 一個 tasklet 常常在調度它的同一個 CPU 上運行.
jit 模塊包括 2 個文件, /proc/jitasklet 和 /proc/jitasklethi, 它返回和在"內核定時器"一節中介紹過的 /proc/jitimer 同樣的數據. 當你讀其中一個文件時, 你取回一個 header 和 sixdata 行. 第一個數據行描述了調用進程的上下文, 并且其他的行描述了一個 tasklet 過程連續運行的上下文. 這是一個在編譯一個內核時的運行例子:
~~~
phon% cat /proc/jitasklet
time delta inirq pid cpu command
6076139 0 0 4370 0 cat
6076140 1 1 4368 0 cc1
6076141 1 1 4368 0 cc1
6076141 0 1 2 0 ksoftirqd/0
6076141 0 1 2 0 ksoftirqd/0
6076141 0 1 2 0 ksoftirqd/0
~~~
如同由上面數據所確定的, tasklet 在下一個時間嘀噠內運行只要 CPU 在忙于運行一個進程, 但是它立刻被運行當 CPU 處于空閑. 內核提供了一套 ksoftirqd 內核線程, 每個 CPU 一個, 只是來運行 "軟件中斷" 處理, 就像 tasklet_action 函數. 因此, tasklet 的最后 3 個運行在關聯到 CPU 0 的 ksoftirqd 內核線程的上下文中發生. jitasklethi 的實現使用了一個高優先級 tasklet, 在馬上要來的函數列表中解釋.
jit 中實現 /proc/jitasklet 和 /proc/jittasklethi 的實際代碼與 /proc/jitimer 的實現代碼幾乎是一致的, 但是它使用 tasklet 調用代替那些定時器. 下面的列表詳細展開了 tasklet 結構已被初始化后的內核對 tasklet 的接口:
void tasklet_disable(struct tasklet_struct *t);
這個函數禁止給定的 tasklet. tasklet 可能仍然被 tasklet_schedule 調度, 但是它的執行被延后直到這個 tasklet 被再次使能. 如果這個 tasklet 當前在運行, 這個函數忙等待直到這個tasklet退出; 因此, 在調用 tasklet_disable 后, 你可以確保這個 tasklet 在系統任何地方都不在運行.
void tasklet_disable_nosync(struct tasklet_struct *t);
禁止這個 tasklet, 但是沒有等待任何當前運行的函數退出. 當它返回, 這個 tasklt 被禁止并且不會在以后被調度直到重新使能, 但是它可能仍然運行在另一個 CPU 當這個函數返回時.
void tasklet_enable(struct tasklet_struct *t);
使能一個之前被禁止的 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 在更高優先級執行. 當軟中斷處理運行時, 它處理高優先級 tasklet 在其他軟中斷之前, 包括"正常的" tasklet. 理想地, 只有具有低響應周期要求( 例如填充音頻緩沖 )應當使用這個函數, 為避免其他軟件中斷處理引入的附加周期. 實際上, /proc/jitasklethi 沒有顯示可見的與 /proc/jitasklet 的區別.
void tasklet_kill(struct tasklet_struct *t);
這個函數確保了這個 tasklet 沒被再次調度來運行; 它常常被調用當一個設備正被關閉或者模塊卸載時. 如果這個 tasklet 被調度來運行, 這個函數等待直到它已執行. 如果這個 tasklet 重新調度它自己, 你必須阻止在調用 tasklet_kill 前它重新調度它自己, 如同使用 del_timer_sync.
tasklet 在 kernel/softirq.c 中實現. 2 個 tasklet 鏈表( 正常和高優先級 )被聲明為每-CPU數據結構, 使用和內核定時器相同的 CPU-親和 機制. 在 tasklet 管理中的數據結構是簡單的鏈表, 因為 tasklet 沒有內核定時器的分類請求.
- 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. 快速參考