## 16.1.?注冊
塊驅動, 象字符驅動, 必須使用一套注冊接口來使內核可使用它們的設備. 概念是類似的, 但是塊設備注冊的細節是都不同的. 你有一整套新的數據結構和設備操作要學習.
### 16.1.1.?塊驅動注冊
大部分塊驅動采取的第一步是注冊它們自己到內核. 這個任務的函數是 register_blkdev(在 <linux/fs.h> 中定義):
~~~
int register_blkdev(unsigned int major, const char *name);
~~~
參數是你的設備要使用的主編號和關聯的名子(內核將顯示它在 /proc/devices). 如果 major 傳遞為0, 內核分配一個新的主編號并且返回它給調用者. 如常, 自 register_blkdev 的一個負的返回值指示已發生了一個錯誤.
取消注冊的對應函數是:
~~~
int unregister_blkdev(unsigned int major, const char *name);
~~~
這里, 參數必須匹配傳遞給 register_blkdev 的那些, 否則這個函數返回 -EINVAL 并且什么都不注銷.
在2.6內核, 對 register_blkdev 的調用完全是可選的. 由 register_blkdev 所進行的功能已隨時間正在減少; 這個調用唯一的任務是 (1) 如果需要, 分配一個動態主編號, 并且 (2) 在 /proc/devices 創建一個入口. 在將來的內核, register_blkdev 可能被一起去掉. 同時, 但是, 大部分驅動仍然調用它; 它是慣例.
### 16.1.2.?磁盤注冊
雖然 register_blkdev 可用來獲得一個主編號, 它不使任何磁盤驅動器對系統可用. 有一個分開的注冊接口你必須使用來管理單獨的驅動器. 使用這個接口要求熟悉一對新結構, 這就是我們的起點.
#### 16.1.2.1.?塊設備操作
字符設備通過 file_ 操作結構使它們的操作對系統可用. 一個類似的結構用在塊設備上; 它是 struct block_device_operations, 定義在 <linux/fs.h>. 下面是一個對這個結構中的成員的簡短的概覽; 當我們進入 sbull 驅動的細節時詳細重新訪問它們.
int (*open)(struct inode *inode, struct file *filp);int (*release)(struct inode *inode, struct file *filp);
就像它們的字符驅動對等體一樣工作的函數; 無論何時設備被打開和關閉都調用它們. 一個字符驅動可能通過啟動設備或者鎖住門(為可移出的介質)來響應一個 open 調用. 如果你將介質鎖入設備, 你當然應當在 release 方法中解鎖.
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
實現 ioctl 系統調用的方法. 但是, 塊層首先解釋大量的標準請求; 因此大部分的塊驅動 ioctl 方法相當短.
int (*media_changed) (struct gendisk *gd);
被內核調用來檢查是否用戶已經改變了驅動器中的介質的方法, 如果是這樣返回一個非零值. 顯然, 這個方法僅適用于支持可移出的介質的驅動器(并且最好給驅動一個"介質被改變"標志); 在其他情況下可被忽略.
struct gendisk 參數是內核任何表示單個磁盤; 我們將在下一節查看這個結構.
int (*revalidate_disk) (struct gendisk *gd);
revalidate_disk 方法被調用來響應一個介質改變; 它給驅動一個機會來進行需要的任何工作使新介質準備好使用. 這個函數返回一個 int 值, 但是值被內核忽略.
struct module *owner;
一個指向擁有這個結構的模塊的指針; 它應當常常被初始化為 THIS_MODULE.
專心的讀者可能已注意到這個列表一個有趣的省略: 沒有實際讀或寫數據的函數. 在塊 I/O 子系統, 這些操作由請求函數處理, 它們應當有它們自己的一節并且在本章后面討論. 在我們談論服務請求之前, 我們必須完成對磁盤注冊的討論.
#### 16.1.2.2.?gendisk 結構
struct gendisk (定義于 <linux/genhd.h>) 是單獨一個磁盤驅動器的內核表示. 事實上, 內核還使用 gendisk 來表示分區, 但是驅動作者不必知道這點. struct gedisk 中有幾個成員, 必須被一個塊驅動初始化:
int major;int first_minor;int minors;
描述被磁盤使用的設備號的成員. 至少, 一個驅動器必須使用最少一個次編號. 如果你的驅動會是可分區的, 但是(并且大部分應當是), 你要分配一個次編號給每個可能的分區. 次編號的一個普通的值是 16, 它允許"全磁盤"設備盒 15 個分區. 一些磁盤驅動使用 64 個次編號給每個設備.
char disk_name[32];
應當被設置為磁盤驅動器名子的成員. 它出現在 /proc/partitions 和 sysfs.
struct block_device_operations *fops;
來自前一節的設備操作集合.
struct request_queue *queue;
被內核用來管理這個設備的 I/O 請求的結構; 我們在"請求處理"一節中檢查它.
int flags;
一套標志(很少使用), 描述驅動器的狀態. 如果你的設備有可移出的介質, 你應當設置 GENHD_FL_REMOVABLE. CD-ROM 驅動器可設置 GENHD_FL_CD. 如果, 由于某些原因, 你不需要分區信息出現在 /proc/partitions, 設置 GENHD_FL_SUPPRESS_PARTITIONS_INFO.
sector_t capacity;
這個驅動器的容量, 以512-字節扇區來計. sector_t 類型可以是 64 位寬. 驅動不應當直接設置這個成員; 相反, 傳遞扇區數目給 set_capacity.
void *private_data;
塊驅動可使用這個成員作為一個指向它們自己內部數據的指針.
內核提供了一小部分函數來使用 gendisk 結構. 我們在這里介紹它們, 接著看 sbull 如何使用它們來使系統可使用它的磁盤驅動器.
struct gendisk 是一個動態分配的結構, 它需要特別的內核操作來初始化; 驅動不能自己分配這個結構. 相反, 你必須調用:
~~~
struct gendisk *alloc_disk(int minors);
~~~
minors 參數應當是這個磁盤使用的次編號數目; 注意你不能在之后改變 minors 成員并且期望事情可以正確工作. 當不再需要一個磁盤時, 它應當被釋放, 使用:
~~~
void del_gendisk(struct gendisk *gd);
~~~
一個 gendisk 是一個被引用計數的結構(它含有一個 kobject). 有 get_disk 和 put_disk 函數用來操作引用計數, 但是驅動應當從不需要做這個. 正常地, 對 del_gendisk 的調用去掉了最一個 gendisk 的最終的引用, 但是不保證這樣. 因此, 這個結構可能繼續存在(并且你的方法可能被調用)在調用 del_gendisk 之后. 但是, 如果你刪除這個結構當沒有用戶時(即, 在最后的釋放之后, 或者在你的模塊清理函數), 你可確信你不會再收到它的信息.
分配一個 gendisk 結構不能使系統可使用這個磁盤. 要做到這點, 你必須初始化這個結構并且調用 add_disk:
~~~
void add_disk(struct gendisk *gd);
~~~
這里記住一件重要的事情:一旦你調用add_disk, 這個磁盤是"活的"并且它的方法可被在任何時間被調用. 實際上, 這樣的第一個調用將可能發生, 即便在 add_disk 返回之前; 內核將讀前幾個字節以試圖找到一個分區表. 因此你不應當調用 add_disk 直到你的驅動被完全初始化并且準備好響應對那個磁盤的請求.
### 16.1.3.?在 sbull 中的初始化
是時間進入一些例子了. sbull 驅動(從 O'Reilly 的 FTP 網站, 以及其他例子源碼)實現一套內存中的虛擬磁盤驅動器. 對每個驅動器, sbull 分配(使用 vmalloc, 為了簡單)一個內存數組; 它接著使這個數組可通過塊操作來使用. 這個 sbull 驅動可通過分區這個驅動器, 在上面建立文件系統, 以及加載到系統層級中來測試.
象我們其他的例子驅動一樣, sbull 允許一個主編號在編譯或者模塊加載時被指定. 如果沒有指定, 動態分配一個. 因為對 register_blkdev 的調用被用來動態分配, sbull 應當這樣做:
~~~
sbull_major = register_blkdev(sbull_major, "sbull");
if (sbull_major <= 0)
{
printk(KERN_WARNING "sbull: unable to get major number\n");
return -EBUSY;
}
~~~
同樣, 象我們在本書已展現的其他虛擬設備, sbull 設備由一個內部結構描述:
~~~
struct sbull_dev {
int size; /* Device size in sectors */
u8 *data; /* The data array */
short users; /* How many users */
short media_change; /* Flag a media change? */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
struct timer_list timer; /* For simulated media changes */
};
~~~
需要幾個步驟來初始化這個結構, 并且使系統可用關聯的設備. 我們從基本的初始化開始, 并且分配底層的內存:
~~~
memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors*hardsect_size;
dev->data = vmalloc(dev->size);
if (dev->data == NULL)
{
printk (KERN_NOTICE "vmalloc failure.\n");
return;
}
spin_lock_init(&dev->lock);
~~~
重要的是在下一步之前分配和初始化一個自旋鎖, 下一步是分配請求隊列. 我們在進入請求處理時詳細看這個過程; 現在, 只需說必要的調用是:
~~~
dev->queue = blk_init_queue(sbull_request, &dev->lock);
~~~
這里, sbull_request 是我們的請求函數 -- 實際進行塊讀和寫請求的函數. 當我們分配一個請求隊列時, 我們必須提供一個自旋鎖來控制對那個隊列的存取. 這個鎖由驅動提供而不是內核通常的部分, 因為, 常常, 請求隊列和其他的驅動數據結構在相同的臨界區; 它們可能被同時存取. 如同任何分配內存的函數, blk_init_queue 可能失敗, 因此你必須在繼續之前檢查返回值.
一旦我們有我們的設備內存和請求隊列, 我們可分配, 初始化, 并且安裝對應的 gendisk 結構. 做這個工作的代碼是:
~~~
dev->gd = alloc_disk(SBULL_MINORS);
if (! dev->gd)
{
printk (KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);
~~~
這里, SBULL_MINORS 是每個 sbull 設備所支持的次編號的數目. 當我們設置第一個次編號給每個設備, 我們必須考慮被之前的設備所用的全部編號. 磁盤的名子被設置, 這樣第一個是 sbulla, 第二個是 sbullb, 等等. 用戶空間可接著添加分區號以便它們在第 2 個設備上的分區可能是 /dev/sbull3.
一旦所有的都被設置, 我們以對 add_disk 的調用來結束. 我們的幾個方法將在 add_disk 返回時被調用, 因此我們負責做這個調用, 這是初始化我們的設備的最后一步.
### 16.1.4.?注意扇區大小
如同我們之前提到的, 內核對待每個磁盤如同一個 512-字節扇區的數組. 不是所有的硬件都使用那個扇區大小, 但是. 使一個有不同扇區大小的設備工作不是一件很難的事; 只要小心處理幾個細節. sbull 設備輸出一個 hardsect_size 參數, 可被用來改變設備的"硬件"扇區大小. 通過看它的實現, 你可見到如何添加這個支持到你自己的驅動.
這些細節中的第一個是通知內核你的設備支持的扇區大小. 硬件扇區大小是一個在請求隊列的參數, 而不是在 gendisk 結構. 這個大小通過調用 blk_queue_hardsect_size 設置的, 在分配隊列后馬上進行:
~~~
blk_queue_hardsect_size(dev->queue, hardsect_size);
~~~
一旦完成那個, 內核堅持你的設備的硬件扇區大小. 所有的 I/O 請求被正確對齊到一個硬件扇區的起始, 并且每個請求的長度是一個整數的扇區數. 你必須記住, 但是, 內核一直以 512-字節扇區表述自己; 因此, 有必要相應地轉換所有的扇區號. 因此, 例如, 當 sbull 在它的 gendisk 結構中設置設備的容量時, 這個調用看來象:
~~~
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
~~~
KERNEL_SECTOR_SIZE 是一個本地定義的常量, 我們用來調整內核的 512-字節和任何我們已被告知要使用的大小. 在我們查看 sbull 請求處理邏輯中會不時看到這類計算出來.
- 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. 快速參考