Linux驅動程序開發(fā)(2) - 字符設備驅動
Linux下的大部分驅動程序都是字符設備驅動程序,通過下面的學習我們將會了解到字符設備是如何注冊到系統(tǒng)中的,應用程序是如何訪問驅動程序的數(shù)據(jù)的,及字符驅動程序是如何工作的。
設備號
通過前面的學習我們知道應用程序是通過設備節(jié)點來訪問驅動程序及設備的,其根本是通過設備節(jié)點的設備號(主設備號及從設備號)來關聯(lián)驅動程序及設備的,字符設備也不例外(其實字符設備只能這樣訪問)。這里我們詳細討論Linux內部如何管理設備號的。
· 設備號類型
Linux內核里用“dev_t”來表示設備號,它是一個32位的無符號數(shù),其高12位用來表示主設備號,低20位用來表示從設備號。它被定義在<linux/types.h>頭文件里。內核里提供了操作“dev_t”的函數(shù),驅動程序中通過這些函數(shù)(其實是宏,定義在<linux/kdev_t.h>文件中)來操作設備號。
#define MINORBITS 20 |
MAJOR(dev)用于獲取主設備號,MINOR(dev)用于獲取從設備號,而MKDEV(ma,mi)用于通過主設備號和從設備號構造"dev_t"數(shù)據(jù)。
另一點需要說明的是,dev_t數(shù)據(jù)類型支持2^12個主設備號,每個主設備號(通常是一個設備驅動)可以支持2^20個設備,目前來說這已經(jīng)足夠大了,但誰又能說將來還能滿足要求呢?一個良好的編程習慣是不要依賴dev_t這個數(shù)據(jù)類型,切記必須使用內核提供的操作設備號的函數(shù)。
· 字符設備號注冊
內核提供了字符設備號管理的函數(shù)接口,作為一個良好的編程習慣,字符設備驅動程序應該通過這些函數(shù)向系統(tǒng)注冊或注銷字符設備號。
int register_chrdev_region(dev_t from, unsigned count, const char *name) |
register_chrdev_region用于向內核注冊已知可用的設備號(次設備號通常是0)范圍。由于歷史的原因一些設備的設備號是固定的,你可以在內核源代碼樹的Documentation/devices.txt文件中找到這些靜態(tài)分配的設備號。
alloc_chrdev_region用于動態(tài)分配的設備號并注冊到內核中,分配的設備號通過dev參數(shù)返回。作為一個良好的內核開發(fā)習慣,我們推薦你使用動態(tài)分配的方式來生成設備號。
unregister_chrdev_region用于注銷一個不用的設備號區(qū)域,通常這個函數(shù)在驅動程序卸載時被調用。
字符設備
Linux2.6內核使用“struct cdev”來記錄字符設備的信息,內核也提供了相關的函數(shù)來操作“struct cdev”對象,他們定義在<linux/cdev.h>頭文件中??梢娮址O備及其操作函數(shù)接口定義的很簡單。
struct cdev { |
對于Linux 2.6內核來說,struct cdev是內核字符設備的基礎結構,用來表示一個字符設備,包含了字符設備需要的全部信息。
· kobj:struct kobject對象數(shù)據(jù),用來描述設備的引用計數(shù),是Linux設備模型的基礎結構。我們在后面的“Linux設備模型”在做詳細的介紹。
· owner:struct module對象數(shù)據(jù),描述了模塊的屬主,指向擁有這個結構的模塊的指針,顯然它只有對編譯為模塊方式的驅動才由意義。一般賦值位“THIS_MODULE”。
· ops:struct file_operations對象數(shù)據(jù),描述了字符設備的操作函數(shù)指針。對于設備驅動來說,這是一個很重要的數(shù)據(jù)成員,幾乎所有的驅動都要用到這個對象,我們會在下面做詳細介紹。
· dev:dev_t對象數(shù)據(jù),描述了字符設備的設備號。
內核提供了操作字符設備對象“struct cdev”的函數(shù),我們只能通過這些函數(shù)來操作字符設備,例如:初始化、注冊、添加、移除字符設備。
· cdev_alloc:用于動態(tài)分配一個新的字符設備 cdev 對象,并對其進行初始化。采用cdev_alloc分配的cdev對象需要顯示的初始化owner和ops對象。
// 參考drivers/scsi/st.c:st_probe 函數(shù) |
· cdev_init:用于初始化一個靜態(tài)分配的cdev對象,一般這個對象會嵌入到其他的對象中。cdev_init會自動初始化ops數(shù)據(jù),因此應用程序只需要顯示的給owner對象賦值。cdev_init的功能與cdev_alloc基本相同,唯一的區(qū)別是cdev_init初始化一個已經(jīng)存在的cdev對象,并且這個初始化會影響到字符設備刪除函數(shù)(cdev_del)的行為,請參考cdev_del函數(shù)。
· cdev_add:向內核系統(tǒng)中添加一個新的字符設備cdev,并且使它立即可用。
· cdev_del:從內核系統(tǒng)中移除cdev字符設備。如果字符設備是由cdev_alloc動態(tài)分配的,則會釋放分配的內存。
· cdev_put:減少模塊的引用計數(shù),一般很少會有驅動程序直接調用這個函數(shù)。
文件操作對象
Linux中的所有設備都是文件,內核中用“struct file”結構來表示一個文件。盡管我們的驅動不會直接使用這個結構中的大部分對象,其中的一些數(shù)據(jù)成員還是很重要的,我們有必要在這里做一些介紹,具體的內容請參考內核源代碼樹<linux/fs.h>頭文件。
// struct file 中的一些重要數(shù)據(jù)成員 |
這里我們不對struct file做過多的介紹,另一篇structfile將做詳細介紹。這個結構中的f_ops成員是我們的驅動所關心的,它是一個struct file_operations結構。Linux里的struct file_operations結構描述了一個文件操作需要的所有函數(shù),它定義在<linux/fs.h>頭文件中。
struct file_operations { |
這是一個很大的結構,包含了所有的設備操作函數(shù)指針。當然,對于一個驅動,不是所有的接口都需要來實現(xiàn)的。對于一個字符設備來說,一般實現(xiàn)open、release、read、write、mmap、ioctl這幾個函數(shù)就足夠了。
這里需要指出的是,open和release函數(shù)的第一個參數(shù)是一個struct inode對象。這是一個內核文件系統(tǒng)索引節(jié)點對象,它包含了內核在操作文件或目錄是需要的全部信息。對于字符設備驅動來說,我們關心的是從structinode對象中獲取設備號(inode的i_rdev成員)內核提供了兩個函數(shù)來做這件事。
static inline unsigned iminor(const struct inode *inode) |
盡管我們可以直接從inode->i_rdev獲取設備號,但是盡量不要這樣做。我們推薦你調用內核提供的函數(shù)來獲取設備號,這樣即使將來inode->i_rdev有所變化,我們的程序也會工作的很好。
字符設備驅動可以參考Linux 設備驅動程序第三版和linux設備驅動開發(fā)詳解,其中linux設備驅動程序第三版中講的:
主次編號
一些重要數(shù)據(jù)結構
字符設備注冊
Open和release
讀和寫
一些頭文件和結構體;