misc子系統是Linux核心中一個簡單而靈活的設備驅動框架,它可以用來實現一些不屬於其他子系統的設備驅動,如字元設備,虛擬設備,混合設備等。 misc子系統的優點是簡單易用,不需要寫大量的程式碼,只需要實作一些基本的操作函數。但是misc子系統也有一些缺點,例如無法支援多個設備實例,無法支援設備樹,無法支援熱插拔等。為了解決這些問題,本文將介紹一個新的裝置驅動框架:3 2 1裝置辨識驅動框架,它是基於misc子系統和platform子系統的結合,可以實現更多的功能和特性。
#Linux 中有三大類設備:字符,網絡,塊設備,每一種設備又細分為很多類,比如字符設備就被預先分為很多種類,並在文件中標記了這些種類都使用了哪個主設備號,但即便如此,硬體千千萬,總還是有漏網之魚,對於這些難以劃分類別的字符設備,Linux中使用”混雜”,設備來統一描述,並分配給他們一個共同的主設備號10,只用此設備號進行區分設備,,這些設備主要包括隨機數產生器,LCD,時鐘產生器等。此外,和許多同樣是對cdev進行再次封裝的子系統一樣,misc也會自動建立裝置檔案,免得每次寫cdev介面都要使用class_create()和device_create()等。
核心中提供的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 };
我們只要像字元裝置一樣實作fops介面再給一個minor即可,如果minor使用巨集MISC_DYNAMIC_MINOR(其實就是255),核心會自動分配一個次設備號,其他的核心已經實現約定好的次設備號可以參考**”include/linux/miscdevice.h”**。萬事具備之後只需使用下面的API註冊/註銷到核心
178 int misc_register(struct miscdevice * misc) 238 int misc_deregister(struct miscdevice *misc)
#misc的使用是不是很簡單?但麻雀雖小五臟俱全,正是因為misc精簡的結構,我們可以很容易的抓到其中體現的分層思想,misc的設計方法體現在很多使用cdev作為接口的子系統,而其中的清晰的分層思想更是Linux驅動的兩大支柱之一(另外一個是分離)。 我們可以藉鏡其中的設計思路,提升我們的驅動程式的品質。下面,我們簡單的分析一下misc的內部機制。
#作為Linux的子系統,misc子系統在Linux啟動過程中就會完成準備工作,主要包括初始化資料結構,建立對應的class,建立、初始化並註冊cdev物件到核心等。有了這些基礎,我們就可以使用misc的眾多好處來進行程式設計。
//drivers/char/misc.c 56 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_FS 273 proc_create("misc", 0, NULL, &misc_proc_fops); 274 #endif 275 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–>系統啟動的過程中就會初始化misc子系統
–273–>依系統配置,可能需要提供/proc介面
–275–>在/sysfs中建立一個類,名為misc
–281–>使用靜態主設備號(10)、封裝好的方法集misc_fops,register_chrdev()內部會創建一個cdev物件並使用這兩個參數將其初始化並註冊到內核,這個cdev物件將負責所有的混雜設備的設備號。關於cdev物件和裝置號的關係請參考cdev_map。
–158–>misc的cdev物件所使用的fops,顯然,至此和普通字元裝置的呼叫過程一樣,chrdev_open()->misc_open()。”
接下来,老规矩,我们从”XXX_register”开始分析,在Linux内核中,这些”XXX_register”往往就是一个设备对象注册到内核的接口,是研究当相应对象注册进去之后内核动作的最佳入口。
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 = 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是动态分配,那么进入相关语句块。
–188–> 使dev用位图遍历API-find_first_zero_bit找到最小未用的设备号。
–193–> 得到分配好的次设备号。
–208–> (根据设备号)创建设备文件,使用的是misc_init中创建的misc_class,至此就可以实现misc设备文件的自动创建。就相当与我们在纯粹的cdev驱动中使用class_create()+device_create()创建设备文件。一个设备文件和一个设备号相联系,而misc的所有的设备号都和misc_init创建的cdev对象相联系,所以打开的任何一个misc设备文件首先回调的就是(chrdev_open()->)misc_open()。
–222–> 关键,将这个新分配的misc加入到misc链表中,用于管理所有的misc设备,便于misc_open()提取具体设备的fops。”
构建的misc子系统,将设备添加到了该子系统中,接下来我们来看一下应用层程序是如何打开一个misc设备的。由于misc也是一种字符设备,所以其提供的接口也是位于/dev中。但是正如misc的定义,其中的设备五花八门却共用同一个主设备号,这就意味着最终被chrdev_open回调的misc_open一定要具备根据被打开的不同文件为file结构准备不同的操作方法这一能力,即在驱动中实现对子设备的识别,或者称之为”多态”。
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设备链表,根据被打开的设备的次设备号找到设备对象。
–123–>存储这个设备对象的操作方法集unique_fops。
–144–>将misc设备具体的操作方法集unique_fops替换到filp中的f_op中,这个位置原来是misc的cdev对象的fops,filp带着这个unique_fops从open()返回,就实现了不同的设备对应不同的操作方法,即面向对象的”多态””
通过上述对misc机制的分析,我们不难总结出一个支持设备识别的3+2+1驱动模型(3个函数+2个数据结构+1个封装):
+
+
至此,我们就可以写一写这个3+2+1驱动模型的模板。
struct multidevice{ struct list_head head; int minor; struct file_operations* fops; void *priv; //私有数据,供read/write等接口识别的信息,以及其他数据都放这里 };
struct list_head multi_dev_list; unsigned int minors_map; //根据设备号数目的不同选数据类型or数组
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); } /*---------------下面是给待加驱动用的----------------------*/ 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", misc->name); list_add(dev->head, &multi_dev_list); } EXPORT_SYMBOL(xxx_register)
通过本文,我们了解了misc子系统和3+2+1设备识别驱动框架的原理和方法,它们可以用来实现一些特殊的设备驱动,如识别设备,虚拟设备,混合设备等。我们应该根据实际需求选择合适的框架,并遵循一些基本原则,如使用正确的注册和注销函数,使用正确的文件操作结构体,使用正确的设备树节点等。misc子系统和3+2+1设备识别驱动框架是Linux内核中两个有用而灵活的设备驱动框架,它们可以提升设备驱动的兼容性和可扩展性,也可以提升开发者的效率和质量。希望本文能够对你有所帮助和启发。
以上是Linux核心中的兩種特殊裝置驅動框架:misc子系統與3+2+1裝置辨識驅動框架的詳細內容。更多資訊請關注PHP中文網其他相關文章!