Linux中I/O設備分為兩類:塊設備和字符設備。兩種設備本身沒有嚴格限制,但是,基於不同的功能進行了分類。
(1) 字符設備:提供連續的數據流,應用程序可以順序讀取,通常不支持隨機存取。相反,此類設備支持按字節/字符來讀寫數據。舉例來說,調制解調器是典型的字符設備。
(2) 塊設備:應用程序可以隨機訪問設備數據,程序可自行確定讀取數據的位置。硬盤是典型的塊設備,應用程序可以尋址磁盤上的任何位置,並由此讀取數據。此外,數據的讀寫只能以塊(通常是512B)的倍數進行。與字符設備不同,塊設備並不支持基於字符的尋址。
兩種設備本身並沒用嚴格的區分,主要是字符設備和塊設備驅動程序提供的訪問接口(file I/O API)是不一樣的。本文主要就數據接口、訪問接口和設備注冊方法對兩種設備進行比較。
1、數據結構
1.1字符設備數據結構
struct file;
struct inode;
file定義於 <linux/fs.h>, 是設備驅動中第二個最重要的數據結構. 文件結構代表一個打開的文件. 它由內核在 open 時創建, 並傳遞給在文件上操作的任何函數, 直到最后的關閉. 在文件的所有實例都關閉后, 內核釋放這個數據結構。
inode 結構由內核在內部用來表示文件.inode 結構包含大量關於文件的信息其中dev_t i_rdev成員包含實際的設備編號.struct cdev *i_cdev中struct cdev 是內核的內部結構, 代表字符設備。
1.2塊設備數據結構
struct gendisk (定義於 <linux/genhd.h>) 是單獨一個磁盤驅動器的內核表示. 事實上, 內核還使用 gendisk 來表示分區。
2、設備訪問接口
2.1字符設備訪問接口
struct file_operations 其中file_operation 結構中的每個成員必須指向驅動中的函數, 這些函數實現一個特別的操作, 或者對於不支持的操作留置為 NULL. 當指定為 NULL 指針時內核的確切的行為是每個函數不同的,該結構中主要函數如下:
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
filp 是文件指針, count 是請求的傳輸數據大小. buff 參數指向持有被寫入數據的緩存, 或者放入新數據的空緩存. 最后, offp 是一個指針指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type"。
2.2塊設備訪問接口
字符設備通過 file_ 操作結構使它們的操作對系統可用. 一個類似的結構用在塊設備上; 它是 struct block_device_operations, 定義在 <linux/fs.h>,其主要操作方法如下:
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 方法相當短。
3、設備注冊
3.1字符設備注冊
int register_chrdev_region(dev_t first, unsigned int count, char *name)
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
void unregister_chrdev_region(dev_t first, unsigned int count);
允許驅動分配和釋放設備編號的范圍的函數. register_chrdev_region 應當用在事先知道需要的主編號時; 對於動態分配, 使用 alloc_chrdev_region 代替.
3.2塊設備注冊
int register_blkdev(unsigned int major, const char *name);
int unregister_blkdev(unsigned int major, const char *name);
register_blkdev 注冊一個塊驅動到內核, 並且, 可選地, 獲得一個主編號. 一個驅動可被注銷, 使用 unregister_blkdev。