## 11.5.?鏈表
操作系統內核, 如同其他程序, 常常需要維護數據結構的列表. 有時, Linux 內核已經同時有幾個列表實現. 為減少復制代碼的數量, 內核開發者已經創建了一個標準環形的, 雙鏈表; 鼓勵需要操作列表的人使用這個設施.
當使用鏈表接口時, 你應當一直記住列表函數不做加鎖. 如果你的驅動可能試圖對同一個列表并發操作, 你有責任實現一個加鎖方案. 可選項( 破壞的列表結構, 數據丟失, 內核崩潰) 肯定是難以診斷的.
為使用列表機制, 你的驅動必須包含文件 <linux/list.h>. 這個文件定義了一個簡單的類型 list_head 結構:
~~~
struct list_head { struct list_head *next, *prev; };
~~~
真實代碼中使用的鏈表幾乎是不變地由幾個結構類型組成, 每一個描述一個鏈表中的入口項. 為在你的代碼中使用 Linux 列表, 你只需要嵌入一個 list_head 在構成這個鏈表的結構里面. 假設, 如果你的驅動維護一個列表, 它的聲明可能看起來象這樣:
~~~
struct todo_struct
{
struct list_head list;
int priority; /* driver specific */
/* ... add other driver-specific fields */
};
~~~
列表的頭常常是一個獨立的 list_head 結構. 圖[鏈表頭數據結構](# "圖?11.1.?鏈表頭數據結構")顯示了這個簡單的 struct list_head 是如何用來維護一個數據結構的列表的.
**圖?11.1.?鏈表頭數據結構**

鏈表頭必須在使用前用 INIT_LIST_HEAD 宏來初始化. 一個"要做的事情"的鏈表頭可能聲明并且初始化用:
~~~
struct list_head todo_list;
INIT_LIST_HEAD(&todo_list);
<para>可選地, 鏈表可在編譯時初始化:</para>
LIST_HEAD(todo_list);
~~~
幾個使用鏈表的函數定義在 <linux/list.h>:
list_add(struct list_head *new, struct list_head *head);
在緊接著鏈表 head 后面增加新入口項 -- 正常地在鏈表的開頭. 因此, 它可用來構建堆棧. 但是, 注意, head 不需要是鏈表名義上的頭; 如果你傳遞一個 list_head 結構, 它在鏈表某處的中間, 新的項緊靠在它后面. 因為 Linux 鏈表是環形的, 鏈表的頭通常和任何其他的項沒有區別.
list_add_tail(struct list_head *new, struct list_head *head);
剛好在給定鏈表頭前面增加一個新入口項 -- 在鏈表的尾部, 換句話說. list_add_tail 能夠, 因此, 用來構建先入先出隊列.
list_del(struct list_head *entry);list_del_init(struct list_head *entry);
給定的項從隊列中去除. 如果入口項可能注冊在另外的鏈表中, 你應當使用 list_del_init, 它重新初始化這個鏈表指針.
list_move(struct list_head *entry, struct list_head *head);list_move_tail(struct list_head *entry, struct list_head *head);
給定的入口項從它當前的鏈表里去除并且增加到 head 的開始. 為安放入口項在新鏈表的末尾, 使用 list_move_tail 代替.
list_empty(struct list_head *head);
如果給定鏈表是空, 返回一個非零值.
list_splice(struct list_head *list, struct list_head *head);
將 list 緊接在 head 之后來連接 2 個鏈表.
list_head 結構對于實現一個相似結構的鏈表是好的, 但是調用程序常常感興趣更大的結構, 它組成鏈表作為一個整體. 一個宏定義, list_entry, 映射一個 list_head 結構指針到一個指向包含它的結構的指針. 它如下被調用:
~~~
list_entry(struct list_head *ptr, type_of_struct, field_name);
~~~
這里 ptr 是一個指向使用的 struct list_head 的指針, type_of_struct 是包含 ptr 的結構的類型, field_name 是結構中列表成員的名子. 在我們之前的 todo_struct 結構中, 鏈表成員稱為簡單列表. 因此, 我們應當轉變一個列表入口項為它的包含結構, 使用這樣一行:
~~~
struct todo_struct *todo_ptr = list_entry(listptr, struct todo_struct, list);
~~~
list_entry 宏定義使用了一些習慣的東西但是不難用.
鏈表的遍歷是容易的: 只要跟隨 prev 和 next 指針. 作為一個例子, 假設我們想保持 todo_struct 項的列表已降序的優先級順序排列. 一個函數來添加新項應當看來如此:
~~~
void todo_add_entry(struct todo_struct *new)
{
struct list_head *ptr;
struct todo_struct *entry;
for (ptr = todo_list.next; ptr != &todo_list; ptr = ptr->next)
{
entry = list_entry(ptr, struct todo_struct, list);
if (entry->priority < new->priority) {
list_add_tail(&new->list, ptr);
return;
}
}
list_add_tail(&new->list, &todo_struct)
}
~~~
但是, 作為一個通用的規則, 最好使用一個預定義的宏來創建循環, 它遍歷鏈表. 前一個循環, 例如, 可這樣編碼:
~~~
void todo_add_entry(struct todo_struct *new)
{
struct list_head *ptr;
struct todo_struct *entry;
list_for_each(ptr, &todo_list)
{
entry = list_entry(ptr, struct todo_struct, list);
if (entry->priority < new->priority) {
list_add_tail(&new->list, ptr);
return;
}
}
list_add_tail(&new->list, &todo_struct)
}
~~~
使用提供的宏幫助避免簡單的編程錯誤; 宏的開發者也已做了些努力來保證它們進行正常. 存在幾個變體:
list_for_each(struct list_head *cursor, struct list_head *list)
這個宏創建一個 for 循環, 執行一次, cursor 指向鏈表中的每個連續的入口項. 小心改變列表在遍歷它時.
list_for_each_prev(struct list_head *cursor, struct list_head *list)
這個版本后向遍歷鏈表.
list_for_each_safe(struct list_head *cursor, struct list_head *next, struct list_head *list)
如果你的循環可能刪除列表中的項, 使用這個版本. 它簡單的存儲列表 next 中下一個項, 在循環的開始, 因此如果 cursor 指向的入口項被刪除, 它不會被搞亂.
list_for_each_entry(type *cursor, struct list_head *list, member)list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member)
這些宏定義減輕了對一個包含給定結構類型的列表的處理. 這里, cursor 是一個指向包含數據類型的指針, member 是包含結構中 list_head 結構的名子. 有了這些宏, 沒有必要安放 list_entry 調用在循環里了.
如果你查看 <linux/list.h> 里面, 你看到有一些附加的聲明. hlist 類型是一個有一個單獨的, 單指針列表頭類型的雙向鏈表; 它常用作創建哈希表和類型結構. 還有宏用來遍歷 2 種列表類型, 打算作使用 讀取-拷貝-更新 機制(在第 5 章的"讀取-拷貝-更新"一節中描述 ). 這些原語在設備驅動中不可能有用; 看頭文件如果你愿意知道更多信息關于它們是如何工作的.
- 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. 快速參考