misc子系統(tǒng)在Linux中是一個非常簡單的子系統(tǒng),但是其清晰的框架結(jié)構(gòu)非常適合用來研究設(shè)備識別模型。本文從misc子系統(tǒng)的使用出發(fā),通過了解其機(jī)制來總結(jié)一套的設(shè)備識別的驅(qū)動框架,即使用使用同一個驅(qū)動,向上提供多個設(shè)備文件接口,向下控制多個(相應(yīng)的)設(shè)備,這就需要該驅(qū)動可以根據(jù)不同的設(shè)備文件來控制與之相應(yīng)的設(shè)備。misc本身并不是一個針對某種具體設(shè)備的驅(qū)動框架, 而是一個管理設(shè)備的模型, 幫助我們設(shè)計出更通用的驅(qū)動
misc的使用
Linux 中有三大類設(shè)備:字符,網(wǎng)絡(luò),塊設(shè)備,每一種設(shè)備又細(xì)分為很多類,比如字符設(shè)備就被預(yù)先分為很多種類,并在文件中標(biāo)記了這些種類都使用了哪個主設(shè)備號,但即便如此,硬件千千萬,總還是有漏網(wǎng)之魚,對于這些難以劃分類別的字符設(shè)備,Linux中使用"混雜",設(shè)備來統(tǒng)一描述,并分配給他們一個共同的主設(shè)備號10,只用此設(shè)備號進(jìn)行區(qū)分設(shè)備,,這些設(shè)備主要包括隨機(jī)數(shù)發(fā)生器,LCD,時鐘發(fā)生器等。此外,和很多同樣是對cdev進(jìn)行再次封裝的子系統(tǒng)一樣,misc也會自動創(chuàng)建設(shè)備文件,免得每次寫cdev接口都要使用class_create()和device_create()等。
內(nèi)核中提供的misc對象:
//include/linux/miscdevice.h 55 struct miscdevice { 56 int minor; 57 const char *name; 58 const struct file_operations *fops; 59 struct list_head list; 60 struct device *parent; 61 struct device *this_device; 62 const char *nodename; 63 umode_t mode; 64 };
我們只要像字符設(shè)備一樣實現(xiàn)fops接口再給一個minor即可,如果minor使用宏MISC_DYNAMIC_MINOR(其實就是255),內(nèi)核會自動分配一個次設(shè)備號,其他的內(nèi)核已經(jīng)實現(xiàn)約定好的次設(shè)備號可以參考"include/linux/miscdevice.h"。萬事具備之后只需使用下面的API注冊/注銷到內(nèi)核
178 int misc_register(struct miscdevice * misc)238 int misc_deregister(struct miscdevice *misc)
misc的分析
misc的使用是不是很簡單?但麻雀雖小五臟俱全,正是因為misc精簡的結(jié)構(gòu),我們可以很容易的抓到其中體現(xiàn)的分層思想,misc的設(shè)計方法體現(xiàn)在很多使用cdev作為接口的子系統(tǒng),而其中的清晰的分層思想更是Linux驅(qū)動的兩大支柱之一(另外一個是分離)。我們可以借鑒其中的設(shè)計思路,提升我們的驅(qū)動程序的質(zhì)量。下面,我們簡單的分析一下misc的內(nèi)部機(jī)制。
misc_init
作為Linux的一個子系統(tǒng),misc子系統(tǒng)在Linux啟動過程中就會完成準(zhǔn)備工作,主要包括初始化數(shù)據(jù)結(jié)構(gòu),創(chuàng)建相應(yīng)的class,創(chuàng)建、初始化并注冊cdev對象到內(nèi)核等。有了這些基礎(chǔ),我們就可以使用misc的眾多好處進(jìn)行編程。
//drivers/char/misc.c56 static const struct file_operations misc_fops = {157 .owner = THIS_MODULE,158 .open = misc_open,159 .llseek = noop_llseek,160 };268 static int __init misc_init(void)269 {272 #ifdef CONFIG_PROC_FS273 proc_create("misc", 0, NULL, &misc_proc_fops);274 #endif275 misc_class = class_create(THIS_MODULE, "misc");281 if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))282 goto fail_printk;283 misc_class->devnode = misc_devnode;284 return 0;292 }293 subsys_initcall(misc_init);
misc_init()
--293-->系統(tǒng)啟動的過程中就會初始化misc子系統(tǒng)
--273-->根據(jù)系統(tǒng)配置,可能需要提供/proc接口
--275-->在/sysfs中創(chuàng)建一個類,名為misc
--281-->使用靜態(tài)主設(shè)備號(10)、封裝好的方法集misc_fops,register_chrdev()內(nèi)部會創(chuàng)建一個cdev對象并使用這兩個參數(shù)將其初始化并注冊到內(nèi)核,這個cdev對象將負(fù)責(zé)所有的混雜設(shè)備的設(shè)備號。關(guān)于cdev對象和設(shè)備號之間的關(guān)系參見cdev_map。
--158-->misc的cdev對象使用的fops,顯然,至此和普通字符設(shè)備的調(diào)用過程一樣,chrdev_open()->misc_open()。
misc_register
接下來,老規(guī)矩,我們從"XXX_register"開始分析,在Linux內(nèi)核中,這些"XXX_register"往往就是一個設(shè)備對象注冊到內(nèi)核的接口,是研究當(dāng)相應(yīng)對象注冊進(jìn)去之后內(nèi)核動作的最佳入口。
178 int misc_register(struct miscdevice * misc)179 { 180 dev_t dev;187 if (misc->minor == MISC_DYNAMIC_MINOR) {188 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);193 misc->minor = DYNAMIC_MINORS - i - 1;194 set_bit(i, misc_minors);195 } 206 dev = MKDEV(MISC_MAJOR, misc->minor);208 misc->this_device = device_create(misc_class, misc->parent, dev,209 misc, "%s", misc->name);210 if (IS_ERR(misc->this_device)) {211 int i = DYNAMIC_MINORS - misc->minor - 1;212 if (i < DYNAMIC_MINORS && i >= 0)213 clear_bit(i, misc_minors);214 err = PTR_ERR(misc->this_device);216 }222 list_add(&misc->list, &misc_list);226 }
misc_register()
--187--> 如果指定的minor是動態(tài)分配,那么進(jìn)入相關(guān)語句塊。
--188--> 使dev用位圖遍歷API-find_first_zero_bit找到最小未用的設(shè)備號。
--193--> 得到分配好的次設(shè)備號。
--208--> (根據(jù)設(shè)備號)創(chuàng)建設(shè)備文件,使用的是misc_init中創(chuàng)建的misc_class,至此就可以實現(xiàn)misc設(shè)備文件的自動創(chuàng)建。就相當(dāng)與我們在純粹的cdev驅(qū)動中使用class_create()+device_create()創(chuàng)建設(shè)備文件。一個設(shè)備文件和一個設(shè)備號相聯(lián)系,而misc的所有的設(shè)備號都和misc_init創(chuàng)建的cdev對象相聯(lián)系,所以打開的任何一個misc設(shè)備文件首先回調(diào)的就是(chrdev_open()->)misc_open()。
--222--> 關(guān)鍵,將這個新分配的misc加入到misc鏈表中,用于管理所有的misc設(shè)備,便于misc_open()提取具體設(shè)備的fops。
misc_open
構(gòu)建的misc子系統(tǒng),將設(shè)備添加到了該子系統(tǒng)中,接下來我們來看一下應(yīng)用層程序是如何打開一個misc設(shè)備的。由于misc也是一種字符設(shè)備,所以其提供的接口也是位于/dev中。但是正如misc的定義,其中的設(shè)備五花八門卻共用同一個主設(shè)備號,這就意味著最終被chrdev_open回調(diào)的misc_open一定要具備根據(jù)被打開的不同文件為file結(jié)構(gòu)準(zhǔn)備不同的操作方法這一能力,即在驅(qū)動中實現(xiàn)對子設(shè)備的識別,或者稱之為"多態(tài)"。
112 static int misc_open(struct inode * inode, struct file * file)113 {114 int minor = iminor(inode);115 struct miscdevice *c;116 int err = -ENODEV;117 const struct file_operations *new_fops = NULL;121 list_for_each_entry(c, &misc_list, list) {122 if (c->minor == minor) {123 new_fops = fops_get(c->fops); 124 break;125 }126 }144 replace_fops(file, new_fops);145 if (file->f_op->open) {146 file->private_data = c;147 err = file->f_op->open(inode,file);148 }152 }
misc_open()
--121-->遍歷misc設(shè)備鏈表,根據(jù)被打開的設(shè)備的次設(shè)備號找到設(shè)備對象。
--123-->存儲這個設(shè)備對象的操作方法集unique_fops。
--144-->將misc設(shè)備具體的操作方法集unique_fops替換到filp中的f_op中,這個位置原來是misc的cdev對象的fops,filp帶著這個unique_fops從open()返回,就實現(xiàn)了不同的設(shè)備對應(yīng)不同的操作方法,即面向?qū)ο蟮?多態(tài)"
3+2+1多設(shè)備識別驅(qū)動模型
通過上述對misc機(jī)制的分析,我們不難總結(jié)出一個支持設(shè)備識別的3+2+1驅(qū)動模型(3個函數(shù)+2個數(shù)據(jù)結(jié)構(gòu)+1個封裝):
初始化整個驅(qū)動組的xxx_init(),通常用模塊加載函數(shù)或總線的probe函數(shù)實現(xiàn);
用于注冊一個子驅(qū)動的xxx_register(),需要EXPORT到符號表;
能夠根據(jù)傳入的inode識別具體的設(shè)備并將其操作方法集放到filp中的xxx_open()。
+
用于存儲每一個驅(qū)動對象的通用鏈表或數(shù)組+priv_data
用于存儲子設(shè)備號的位圖。
+
將所有的不同的設(shè)備用一個統(tǒng)一的結(jié)構(gòu)進(jìn)行封裝
至此,我們就可以寫一寫這個3+2+1驅(qū)動模型的模板。
1個封裝
struct multidevice{ struct list_head head; char *name[128]; int minor; struct file_operations* fops; void *priv; //私有數(shù)據(jù),供read/write等接口識別的信息,以及其他數(shù)據(jù)都放這里};
2個數(shù)據(jù)結(jié)構(gòu)
struct list_head multi_dev_list;unsigned int minors_map; //根據(jù)設(shè)備號數(shù)目的不同選數(shù)據(jù)類型or數(shù)組
3個函數(shù)
int major,baseminor = 0,max_dev = sizeof(minors_map)*8;#define DEV_NAME "multi_device"struct class *cls;xxx_open(struct inode *inode,struct file *file){ int minor = iminor(inode); struct multidevice *dp; const struct file_operations *new_fops = NULL; list_for_each_entry(dp, &multi_dev_list, head) { if (dp->minor == minor) { new_fops = fops_get(dp->fops); break; } } replace_fops(file, new_fops); if (file->f_op->open) { file->private_data = dp file->f_op->open(inode,file); }}xxx_init(void){ dev_t devno, INIT_LIST_HEAD(&multi_dev_list); init_map(&minors_map); struct cdev *multi_cdev = cdev_alloc(); cdev_init(multi_cdev, multi_fops); alloc_chrdev_region(&devno, baseminor, count,DEV_NAME); major = MAJOR(devno); cdev_add(multi_cdev , devno, count); cls = class_create(THIS_MODULE, DEV_NAME);}/*---------------下面是給待加驅(qū)動用的----------------------*/xxx_register(struct *multidevice dev){ dev_t dev; if (dev->minor == MISC_DYNAMIC_MINOR) { int i = find_first_zero_bit(minors_map, DYNAMIC_MINORS); dev->minor = DYNAMIC_MINORS - i - 1; set_bit(i, minors_map); } dev_t pri_devno = MKDEV(major, dev->minor); device_create(multi_class, NULL, pri_devno, "%s%d", dev->name,dev->minor); list_add(dev->head, &multi_dev_list);}EXPORT_SYMBOL(xxx_register)
?
評論
查看更多