一、雜項設(shè)備驅(qū)動介紹
1.1 系統(tǒng)介紹
本文是基于linux-2.6.32內(nèi)核進(jìn)行分析的,如果使用的是其他版本的內(nèi)核,其內(nèi)核調(diào)用的函數(shù)可能有所不同,但是其實現(xiàn)原理是相通的。
1.2 雜項設(shè)備驅(qū)動的引入
在前面一小節(jié)里面,我們詳細(xì)介紹了字符設(shè)備驅(qū)動程序,知道字符設(shè)備指那些必須以串行順序依次進(jìn)行訪問,且沒有經(jīng)過系統(tǒng)快速緩沖的設(shè)備,了解了Linux內(nèi)核中驅(qū)動的框架和組成,以及編寫的步驟等。但是,當(dāng)我們寫的驅(qū)動程序多了之后,就會發(fā)現(xiàn):部分硬件并不符合預(yù)先定義的字符設(shè)備的范疇,而且普通字符設(shè)備的主設(shè)備號不管是靜態(tài)分配還是動態(tài)分配,都會消耗一個主設(shè)備號(目前一個系統(tǒng)最多只能有255個字符設(shè)備),比較浪費(fèi)主設(shè)備號資源。因此,而引入了雜項設(shè)備驅(qū)動。
在Linux里面,把無法歸類的五花八門的設(shè)備定義為混雜設(shè)備(用miscdevice結(jié)構(gòu)體描述)。雜項設(shè)備是一個典型的字符設(shè)備(與接下來要介紹的輸入子系統(tǒng)一樣,呵呵),其主設(shè)備號固定為10。其內(nèi)部實現(xiàn)就是用主設(shè)備號10來調(diào)用register_chrdev()實現(xiàn)的;并且在內(nèi)部還調(diào)用了class_create()和device_create ()為每個雜項設(shè)備創(chuàng)建設(shè)備節(jié)點,從而避免了我們通過mknod命令或自行調(diào)用該兩個函數(shù)來創(chuàng)建設(shè)備節(jié)點的麻煩。
從以上這點來說,雜項設(shè)備就是將我們平常編寫字符設(shè)備的驅(qū)動進(jìn)行了再次封裝,降低了我們編寫字符設(shè)備驅(qū)動的難度,同時節(jié)約了主設(shè)備號資源。
1.3 雜項設(shè)備與字符設(shè)備實現(xiàn)比較
在進(jìn)行字符設(shè)備驅(qū)動程序開發(fā)的過程中,我們的實現(xiàn)步驟如下:
- 申請一個字符設(shè)備號:可以自己指定,也可系統(tǒng)自動分配;
- 構(gòu)造一個file_operations結(jié)構(gòu)體,其包含對硬件的所有操作;
- 實現(xiàn)file_operations結(jié)構(gòu)體中的成員函數(shù);
- 將字符設(shè)備注冊進(jìn)系統(tǒng)中:register_chrdev();
- 創(chuàng)建設(shè)備類和設(shè)備節(jié)點:class_create()、device_create();
- 告訴內(nèi)核入口與出口函數(shù):module_init()、module_exit();
雜項設(shè)備驅(qū)動也是字符設(shè)備驅(qū)動,那么其注冊的過程與字符設(shè)備驅(qū)動一樣,也必須經(jīng)過上面的這些步驟,只是雜項設(shè)備驅(qū)動中的對申請字符設(shè)備號、注冊字符設(shè)備到系統(tǒng)、創(chuàng)建設(shè)備類和設(shè)備節(jié)點進(jìn)行了封裝,我們只需要完成如下幾步開發(fā)即可:
- 構(gòu)造一個file_operations結(jié)構(gòu)體,其中包含對硬件的所有操作;
- 實現(xiàn)file_operations結(jié)構(gòu)體中的成員函數(shù);
- 構(gòu)造一個雜項設(shè)備驅(qū)動(struct miscdevice)實體,并賦值前面定義的file_operations實體;
- 在入口函數(shù)處調(diào)用misc_register()向系統(tǒng)注冊雜項設(shè)備;
- 在出口函數(shù)處調(diào)用misc_deregister()從系統(tǒng)注銷雜項設(shè)備;
- 告訴內(nèi)核入口與出口函數(shù):module_init()、module_exit();
從中也可以得出一個結(jié)論:無論Linux內(nèi)核對驅(qū)動框架設(shè)計的如何好,內(nèi)核實現(xiàn)了多少的代碼,與硬件相關(guān)部分的代碼還是需要我們?nèi)崿F(xiàn)。
本文的重點內(nèi)容就是講解如上幾點內(nèi)容:
- 雜項子系統(tǒng)如何向內(nèi)核注冊;
- 雜項子系統(tǒng)如何實現(xiàn)字符設(shè)備注冊;
- 雜項子系統(tǒng)如何創(chuàng)建設(shè)備類和設(shè)備文件;
- 如何編寫一個雜項設(shè)備驅(qū)動程序;
1.4 雜項設(shè)備和字符設(shè)備與應(yīng)用層數(shù)據(jù)交互比較
編寫過字符設(shè)備驅(qū)動的人都知道,應(yīng)用程序與驅(qū)動之間實現(xiàn)數(shù)據(jù)交互就是通過應(yīng)用API的read()、write()調(diào)用,從而產(chǎn)生一個SWI軟件中斷,然后通過主設(shè)備號找到對應(yīng)的struct cdev結(jié)構(gòu)體實體,從而找到具體硬件設(shè)備的struct file_operations結(jié)構(gòu)體,然后具體調(diào)用底層的drv_read()、drv_write(),我們就是在具體的drv_read()和drv_write()中實現(xiàn)對硬件的操作的,其過程如下:
read()—>swi_read()—>drv_read()—>硬件操作
那么對于雜項設(shè)備驅(qū)動呢?前面已經(jīng)說了,雜項設(shè)備也是字符設(shè)備,那么它也必須經(jīng)歷上面的這些步驟,只是中間穿插了幾個通過次設(shè)備號找到對應(yīng)struct file_operation結(jié)構(gòu)體的過程而已而已。那么是如何穿插的呢:
首先在mist.c(雜項子系統(tǒng)的核心)文件的打開函數(shù)中獲取此設(shè)備號,然后次設(shè)備號找到對應(yīng)的fops(也即struct file_operations結(jié)構(gòu)體)填充struct file中的f_op成員,那么之后應(yīng)用調(diào)用read()、write()函數(shù)就是調(diào)用具體次設(shè)備號對應(yīng)的struct file_operations結(jié)構(gòu)體中的成員了;最后再調(diào)用fops中的open()函數(shù)打開具體的函數(shù):
open()—>
swi_open()—>
misc_open()
{
int minor = iminor(inode); //獲取次設(shè)備號
new_fops = fops_get(c->fops);
old_fops = file->f_op;
file->f_op = new_fops;
file->f_op = fops_get(old_fops);
}
read()/write()—>
swi_read()/swi_write()—>
new_fops->drv_read()/drv_write()
這里說明一下:misc.c是雜項子系統(tǒng)的核心,內(nèi)核已經(jīng)實現(xiàn),我們只需要實現(xiàn)底層的硬件操作函數(shù)集合,然后調(diào)用misc_register()告訴雜項子系統(tǒng)就好了。
通過前面的介紹,不太理解也沒有關(guān)系。你只需要記住,其實雜項子系統(tǒng)就是一個典型的字符設(shè)備系統(tǒng),它也逃不過字符設(shè)備的框架,其應(yīng)用與驅(qū)動交互的流程也和字符設(shè)備驅(qū)動一樣,沒有什么不同就是了(要從心里小瞧它)。
要想搞明白雜項子系統(tǒng)的框架,只需要弄明白應(yīng)用程序是如何與具體的硬件設(shè)備驅(qū)動進(jìn)行交互的就行了。本文的重點就是對雜項子系統(tǒng)的注冊、雜項子系統(tǒng)中驅(qū)動與應(yīng)用數(shù)據(jù)交互過程,以及如何編寫雜項子系統(tǒng)進(jìn)行分析的。
二、雜項子系統(tǒng)實現(xiàn)
2.1 雜項子系統(tǒng)
雜項設(shè)備是主設(shè)備號為10的驅(qū)動設(shè)備,比較適用于功能簡單的設(shè)備。它有自己的設(shè)備結(jié)構(gòu)體(/include/linux/miscdevice.h):
struct miscdevice {
int minor; //次設(shè)備號
const char *name; //設(shè)備名字
const struct file_operations *fops; //文件操作集合
struct list_head list; //連接到misc_list的鏈表
struct device *parent; //父設(shè)備
struct device *this_device; //當(dāng)前設(shè)備,是device_create的返回值
const char *nodename;
mode_t mode;
};
miscdevice在本質(zhì)上仍然屬于字符設(shè)備,只是被增加了一層封裝而已,因此其驅(qū)動的主體工作還是file_operations的成員函數(shù)。
其中minor是次設(shè)備號,雜項子系統(tǒng)主要是依賴minor來對不同的雜項設(shè)備驅(qū)動進(jìn)行管理,如果該項設(shè)置為MISC_DYNAMIC_MINOR,表示由系統(tǒng)自行分配次設(shè)備號。name是調(diào)用misc_register()注冊雜項設(shè)備時創(chuàng)建節(jié)點文件的名字;list是雙向鏈表,用于將要注冊的雜項設(shè)備注冊進(jìn)misc_list鏈表中。如下圖所示:
接下來的內(nèi)容,將詳細(xì)介紹misc.c雜項子系統(tǒng)的實現(xiàn)過程,主要包括:
- 雜項子系統(tǒng)的注冊;
- 雜項子系統(tǒng)之設(shè)備注冊;
- 雜項子系統(tǒng)之設(shè)備注銷;
- 雜項子系統(tǒng)之設(shè)備使用;