一、何为misc设备node
(1)misc中文名就是杂项设备\杂散设备,由于如今的硬件设备多种多样,有好些设备很差对他们进行一个单独的分类,因此就将这些设备所有归属于linux
杂散设备,也就是misc设备,例如像adc、buzzer等这些设备通常都归属于misc中。数组
(2)须要注意的是,虽然这些设备归属于杂散设备中,可是其实你也能够不把设备放在这个类中,这都是驱动工程师按照本身的想法作的,你想把他们写在框架
misc类设备中也能够,本身单独创建一个类也是能够的,只不过是否标准化而已,由于人家既然创建了这个类,那你就把这个设备放在这个类下,不是很好吗?ide
你还本身单独搞一个类,虽然这也没错,只不过是说你不按套路出牌。函数
(3)全部的misc类设备都是字符设备,也就是misc类设备实际上是字符设备中分出来的一个小类。源码分析
(4)misc类设备在应用层的操做接口:/dev/xxxx, 设备类对应在 /sys/class/misc this
(5)misc类设备有本身的一套驱动框架,因此咱们写一个misc设备的驱动直接利用的是内核中提供的驱动框架来实现的。misc驱动框架是对内核提供的原始的字符设备spa
注册接口的一个类层次的封装,不少典型的字符设备均可以归于misc设备,均可以利用misc提供的驱动框架来编写驱动代码,经过misc驱动框架来进行管理。设计
二、misc驱动框架源码分析
在内核中,misc驱动框架的源码实如今: driver/char/misc.c 相应的头文件在:include/linux/miscdevice.h
可是若是咱们本身添加的misc类设备,那么驱动源文件最好放在 driver/misc 这个目录下,这个目录是官方推荐的目录
misc驱动框架和以前的led的驱动框架都是实现为一个模块的形式,在内核配置的时候能够进行动态的编译或者是不编译进内核当中。这样作的一个好处就是可以对内核
进行一个最大化的裁剪,将不须要的模块通通拿掉,可以使得内核在知足要求的状况下实现最小化。
(1)一个重要的结构体 struct miscdevice
1 struct miscdevice { 2 int minor; // 次设备号 3 const char *name; // 名字 4 const struct file_operations *fops; // file_operations 结构体指针 5 struct list_head list; // 做为一个链表节点挂接到misc设备维护的一个链表头上去 misc_list 6 struct device *parent; // 次设备的父设备 7 struct device *this_device; // 本设备的device 结构体指针 8 const char *nodename; 9 mode_t mode; 10 };
(2)misc_init函数分析
misc_init函数是misc驱动框架模块注册时的一个初始化函数,只有执行了初始化,咱们才可以利用misc提供的框架来进行编写misc设备驱动程序和管理misc类设备。
misc_init函数是misc驱动框架的入口函数。
1 static int __init misc_init(void) 2 { 3 int err; 4 5 #ifdef CONFIG_PROC_FS /* CONFIG_PROC_FS用来控制咱们的系统中是否须要proc虚拟文件系统 */ 7 proc_create("misc", 0, NULL, &misc_proc_fops); /*在proc文件系统下建立一个名为 misc 的文件*/ 8 #endif 9 misc_class = class_create(THIS_MODULE, "misc"); /*在sys文件系统下建立 misc 设备类*/ 10 err = PTR_ERR(misc_class); 11 if (IS_ERR(misc_class)) 12 goto fail_remove; 13 14 err = -EIO; 15 if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) /*注册misc 字符设备 主设备号10 misc_fops*/ 16 goto fail_printk; 17 misc_class->devnode = misc_devnode; 18 return 0; 19 20 fail_printk: 21 printk("unable to get major %d for misc devices\n", MISC_MAJOR); 22 class_destroy(misc_class); 23 fail_remove: 24 remove_proc_entry("misc", NULL); 25 return err; 26 }
proc文件系统在2.4版本中用的比较流行,如今主要用的就是sys文件系统,由于sys文件系统比proc文件系统作的更好,功能更加齐全,目录层次设计的很好
因此如今proc文件系统成为了一个能够选择添加或者删除的一个选项了,能够经过在内核配置的时候进行相应的配置。
(2)misc_register函数与misc_deregister函数
misc_register函数是misc驱动框架提供给驱动工程师编写misc类设备时的注册函数,一个重要的接口,misc_deregister就是相对应的卸载函数
1 int misc_register(struct miscdevice * misc) 2 { 3 struct miscdevice *c; // 定义一个 miscdevice 结构体指针 4 dev_t dev; // 设备号 5 int err = 0; 6 7 INIT_LIST_HEAD(&misc->list); // 初始化链表 8 9 mutex_lock(&misc_mtx); // 上锁 10 list_for_each_entry(c, &misc_list, list) { // 遍历 misc_list 链表 查找是否存在次设备号与当前注册的设备的次设备号相同的 11 if (c->minor == misc->minor) { 12 mutex_unlock(&misc_mtx); 13 return -EBUSY; // 若是存在直接退出 14 } 15 } 16 17 if (misc->minor == MISC_DYNAMIC_MINOR) { // misc->minor == 255 表示 自动分配次设备号 18 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 在咱们的misc类设备的驱动框架中使用了一种位来表示次设备号是否被占用的状况 19 if (i >= DYNAMIC_MINORS) { // 使用8个字节的一个变量来表示,这个数据的每一位表示一个次设备号, 20 mutex_unlock(&misc_mtx); // 第一位表明次设备号0 第二位表明次设备号1 ....... 若是这个位被置1表示已经被分配出去了,置0表示没有被分配出去 21 return -EBUSY; // 因此这段代码就是在找一个最小的没有被使用被置1的位, 22 } // 由此可知misc类设备最多只有64个 23 misc->minor = DYNAMIC_MINORS - i - 1; // 咱们这里的意思就是咱们是从小到大去寻找,那么分配就是从大到小,例如: i=0 ,minor=63 i =1,minor=62 24 set_bit(i, misc_minors); // 而后将该位置1 25 } 26 27 dev = MKDEV(MISC_MAJOR, misc->minor); // 使用主次设备号合成设备号 28 29 misc->this_device = device_create(misc_class, misc->parent, dev, // 建立设备 /sys/devices/virtual/misc/xxx 30 misc, "%s", misc->name); 31 if (IS_ERR(misc->this_device)) { 32 int i = DYNAMIC_MINORS - misc->minor - 1; 33 if (i < DYNAMIC_MINORS && i >= 0) 34 clear_bit(i, misc_minors); 35 err = PTR_ERR(misc->this_device); 36 goto out; 37 } 38 39 /* 40 * Add it to the front, so that later devices can "override" 41 * earlier defaults 42 */ 43 list_add(&misc->list, &misc_list); // 将 misc->list 做为节点挂接到 misc_list 链表上去 44 out: 45 mutex_unlock(&misc_mtx); 46 return err; 47 }
(2.1)misc_list是misc驱动框架中提供的用来挂载全部的已经注册的misc设备的一个链表,当咱们 cat /proc/misc 时查看系统中注册的全部misc类设备就是经过遍历
这个链表来实现的。与字符设备的用数组管理的方式必定要区分开来,misc设备的主设备号在这个数组中也占有一个位置,不要将他们之间的关系脱离了。
(2.2)对代码中宏的解析
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
原式子:static LIST_HEAD(misc_list);
展开后:static struct list_head misc_list = { &(misc_list), &(misc_list) } // 其实就是定义了一个链表,next指针和prev指针都指向自己
三、一些须要注意的细节部分
(1)misc_init函数中调用的注册字符设备的函数 register_chrdev
register_chrdev(MISC_MAJOR,"misc",&misc_fops),从这里能够看出来 misc_fops 就是传入的一个file_operations结构体,以前说过了这个结构体在注册
字符设备时的一个重要性,这里就再也不重复了,misc_fops 以下:
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
从上面能够看出来结构体中只实现了open函数,而没有实现其余的函数,由于具体的驱动实现的open、read、write函数在他们的file_operations结构体中,并不在这里实现,
咱们须要经过这里的open函数去找到具体的要打开的硬件设备,而后找到他下面的file_operations结构体,调用结构体中实现的open函数,而且将要打开的设备的file_operations结构体替换当前要操做的这个结构体,以后咱们就能够经过这个结构体来调用设备的其余操做函数,例如read、write....等函数。
为何会有这样的一种操做模式呢? 其缘由就是字符设备的管理的问题,调用register_chrdev函数一次就是注册了一个设备组,而这一个设备组共用了一个file_operations,因此打开这个
设备组中的任何一个设备节点最开始是对应到这个共用的file_operations,因此咱们须要经过这个file_operations中的函数找到咱们须要正真打开的设备的对应函数。
先来看看misc_open函数:
1 static int misc_open(struct inode * inode, struct file * file) 2 { 3 int minor = iminor(inode); // 由传进了的inode结构体找到设备的次设备号 inode结构体以前说了它里面有一个元素记录的就是设备号,由上层传下来的,以前已经讲过 4 struct miscdevice *c; // 定义一个miscdevice指针 5 int err = -ENODEV; 6 const struct file_operations *old_fops, *new_fops = NULL; // 定义两个file_operations指针 7 8 mutex_lock(&misc_mtx); // 互斥锁上锁 9 10 list_for_each_entry(c, &misc_list, list) { // 遍历咱们的misc_list链表找到次设备号与当前须要打开的设备的次设备号相同的 11 if (c->minor == minor) { 12 new_fops = fops_get(c->fops); // 而后 获取这个设备的fops结构体 放入new_fops 13 break; 14 } 15 } 16 17 if (!new_fops) { // 这里是错误校验 18 mutex_unlock(&misc_mtx); 19 request_module("char-major-%d-%d", MISC_MAJOR, minor); 20 mutex_lock(&misc_mtx); 21 22 list_for_each_entry(c, &misc_list, list) { 23 if (c->minor == minor) { 24 new_fops = fops_get(c->fops); 25 break; 26 } 27 } 28 if (!new_fops) 29 goto fail; 30 } 31 32 err = 0; 33 old_fops = file->f_op; // 将file中的fops先放在 old_fops , 这是用来以避免后面出错的时候可以恢复的一种手段 34 file->f_op = new_fops; // 将咱们的获取到的fops放入 file->fops中,也就是替换 那么以后操做file时,对应的就是咱们上面获取到的具体的设备的fops 35 if (file->f_op->open) { // 若是咱们的open函数存在 36 file->private_data = c; // 将miscdevice变量指针c做为 file的私有数据 37 err=file->f_op->open(inode,file); // 打开次设备的open函数 38 if (err) { 39 fops_put(file->f_op); 40 file->f_op = fops_get(old_fops); 41 } 42 } 43 fops_put(old_fops); 44 fail: 45 mutex_unlock(&misc_mtx); 46 return err; 47 }
参考:《朱友鹏嵌入式Linux开发\5.Linux驱动开发\5.6.misc类设备与蜂鸣器驱动》