塊設備是與字符設備並列的概念,這兩類設備在Linux中驅動的結構有較大差異,總體而言,塊設備驅動比字符設備驅動要復雜得多,在I/O操作上表現出極大的不同,緩沖、I/O調度、請求隊列等都是與塊設備驅動相關的概念。本章將向您展示Linux塊設備驅動的編程方法。
13.1節分析塊設備I/O操作的特點,對比字符設備與塊設備在I/O操作上的差異。
13.2節從整體上描述Linux塊設備驅動的結構,分析主要的數據結構、函數及其關系。
13.3~13.5節分別闡述塊設備驅動模塊加載與卸載、打開與釋放和ioctl()函數。
13.6節非常重要,它講述了塊設備I/O操作所依賴的請求隊列的概念及用法。
13.2節與13.3~13.6節是整體與部分的關系,13.2~13.6節與13.7節是迭代遞進關系。
13.7節在13.1~13.6節講解內容的基礎上,總結Linux下塊設備的讀寫流程。而13.7節則給出了塊設備驅動的具體實例,即RAMDISK的驅動。
13.1塊設備的I/O操作特點
字符設備與塊設備I/O操作的不同在於:
① 塊設備只能以塊為單位接受輸入和返回輸出,而字符設備則以字節為單位。大多數設備是字符設備,因為它們不需要緩沖而且不以固定塊大小進行操作。
② 塊設備對於I/O請求有對應的緩沖區,因此它們可以選擇以什麼順序進行響應,字符設備無需緩沖且被直接讀寫。對於存儲設備而言調整讀寫的順序作用巨大,因為在讀寫連續的扇區比分離的扇區更快。
③ 字符設備只能被順序讀寫,而塊設備可以隨機訪問。雖然塊設備可隨機訪問,但是對於磁盤這類機械設備而言,順序地組織塊設備的訪問可以提高性能。如圖13.1,對磁盤1、10、3、2的請求被調整為對1、2、3、10的請求可以提高讀寫性能。注意,對SD卡、RAMDISK等塊設備而言,不存在機械上的原因,進行這樣的調整沒有必要。
圖13.1 調整塊設備I/O操作的順序
13.2 Linux塊設備驅動結構
13.2.1 block_device_operations結構體
在塊設備驅動中,有1個類似於字符設備驅動中file_operations結構體的block_device_operations結構體,它是對塊設備操作的集合,定義如代碼清單13.1。
代碼清單13.1 block_device_operations結構體
1 struct block_device_operations
2 {
3 int(*open)(struct inode *, struct file*); //打開
4 int(*release)(struct inode *, struct file*); //釋放
5 int(*ioctl)(struct inode *, struct file *, unsigned, unsigned long); //ioctl
6 long(*unlocked_ioctl)(struct file *, unsigned, unsigned long);
7 long(*compat_ioctl)(struct file *, unsigned, unsigned long);
8 int(*direct_access)(struct block_device *, sector_t, unsigned long*);
9 int(*media_changed)(struct gendisk*); //介質被改變?
10 int(*revalidate_disk)(struct gendisk*); //使介質有效
11 int(*getgeo)(struct block_device *, struct hd_geometry*);//填充驅動器信息
12 struct module *owner; //模塊擁有者
13 };
下面對其主要的成員函數進行分析:
? 打開和釋放
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
與字符設備驅動類似,當設備被打開和關閉時將調用它們。
? IO控制
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd,
unsigned long arg);
上述函數是ioctl() 系統調用的實現,塊設備包含大量的標准請求,這些標准請求由Linux塊設備層處理,因此大部分塊設備驅動的ioctl()函數相當短。
? 介質改變
int (*media_changed) (struct gendisk *gd);
被內核調用來檢查是否驅動器中的介質已經改變,如果是,則返回一個非零值,否則返回0。這個函數僅適用於支持可移動介質的驅動器(非可移動設備的驅動不需要實現這個方法),通常需要在驅動中增加1個表示介質狀態是否改變的標志變量。
? 使介質有效
int (*revalidate_disk) (struct gendisk *gd);
revalidate_disk()函數被調用來響應一個介質改變,它給驅動一個機會來進行必要的工作以使新介質准備好。
? 獲得驅動器信息
int (*getgeo)(struct block_device *, struct hd_geometry *);
該函數根據驅動器的幾何信息填充一個hd_geometry結構體,hd_geometry結構體包含磁頭、扇區、柱面等信息。
? 模塊指針
struct module *owner;
一個指向擁有這個結構體的模塊的指針,它通常被初始化為THIS_MODULE。
13.2.2 gendisk結構體
在Linux內核中,使用gendisk(通用磁盤)結構體來表示1個獨立的磁盤設備(或分區),這個結構體的定義如代碼清單13.2。
代碼清單13.2 gendisk結構體
1 struct gendisk
2 {
3 int major; /* 主設備號 */
4 int first_minor; /*第1個次設備號*/
5 int minors; /* 最大的次設備數,如果不能分區,則為1*/
6 char disk_name[32]; /* 設備名稱 */
7 struct hd_struct **part; /* 磁盤上的分區信息 */
8 struct block_device_operations *fops; /*塊設備操作結構體*/
9 struct request_queue *queue; /*請求隊列*/
10 void *private_data; /*私有數據*/
11 sector_t capacity; /*扇區數,512字節為1個扇區*/
12
13 int flags;
14 char devfs_name[64];
15 int number;
16 struct device *driverfs_dev;
17 struct kobject kobj;
18
19 struct timer_rand_state *random;
20 int policy;
21
22 atomic_t sync_io; /* RAID */
23 unsigned long stamp;
24 int in_flight;
25 #ifdef CONFIG_SMP
26 struct disk_stats *dkstats;
27 #else
28 struct disk_stats dkstats;
29 #endif
30 };
major、first_minor和minors共同表征了磁盤的主、次設備號,同一個磁盤的各個分區共享1個主設備號,而次設備號則不同。fops為block_device_operations,即上節描述的塊設備操作集合。queue是內核用來管理這個設備的 I/O請求隊列的指針。capacity表明設備的容量,以512個字節為單位。private_data可用於指向磁盤的任何私有數據,用法與字符設備驅動file結構體的private_data類似。