一、MISC驱动简介
misc——miscellaneous,译为杂项 。众所周知,Linux 中的驱动是分层设计的,每个设备都有自己的归属类型,比如键盘为输入设备,属于 input 子系统。但是会存在一些设备无法简单被划分到某一类中,此时可以基于 misc 驱动框架实现该设备的驱动。misc 驱动为不知类型的设备提供一个框架,方便对应设备驱动的开发。
MISC 驱动框架特点:
misc 驱动为字符设备 驱动;
主设备号为 10 ;
简单易用。无需主动调用 register_chrdev_region()、cdev_add() 等函数,只需调用 misc_register() 即可完成次设备号的分配和设备文件的创建工作;
使用 platform 总线实现 misc 驱动和设备的绑定。
上一篇讲了字符设备驱动,MISC驱动可以看成字符设备驱动的一个“轻量封装”:
1 2 3 4 5 6 7 8 9 10 11 普通字符设备: 分配设备号 注册 cdev / register_chrdev 创建 class 创建 device 创建 /dev 节点 MISC 设备: 填一个 struct miscdevice 调用 misc_register() 内核帮你创建 /dev 节点
MISC 不是要和字符设备并列的新机制,它本质上仍然是字符设备。只是很多简单字符设备共享同一个主设备号 10,内核帮你管理次设备号和 /dev 节点。
用户态不关心驱动内部用的是 miscdevice 还是 register_chrdev。它只看到设备文件:
正常情况下会看到:
1 crw-rw-rw- 1 root root 10, 119 ... /dev/misc_lab
含义:
部分
含义
c
字符设备
10
misc 设备常用主设备号
119
动态分配的 misc 次设备号
/dev/misc_lab
用户态访问入口
用户态程序只需要:
1 2 3 int fd = open("/dev/misc_lab" , O_RDWR); write(fd, "1" , 1 ); close(fd);
驱动内部再把这几个系统调用映射到:
1 2 3 4 open() -> misc_lab_open() read() -> misc_lab_read() write() -> misc_lab_write() close() -> misc_lab_release()
查看系统里的 misc 设备:
会看到类似:
1 2 3 4 56 fuse 57 loop-control 60 cpu_dma_latency 119 misc_lab
这里:
对应的设备节点就是:
二、struct miscdevice
MISC 驱动的入口是一个结构体:
1 2 3 4 5 6 static struct miscdevice my_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_lab" , .fops = &misc_lab_fops, .mode = 0666 , };
字段含义:
字段
含义
.minor
次设备号。MISC_DYNAMIC_MINOR 表示让内核动态分配
.name
设备名,决定 /dev/<name>
.fops
file_operations,用户态系统调用的函数表
.mode
设备节点权限,例如 0666
注册:
1 2 3 4 5 ret = misc_register(&my_misc);if (ret) { pr_err("failed to register misc device: %d\n" , ret); return ret; }
注销:
1 misc_deregister(&my_misc);
misc_register() 成功后,内核会帮你:
分配一个 misc minor。
把设备注册到字符设备层。
使用主设备号 10。
让 udev/devtmpfs 创建 /dev/misc_lab。
三、MISC 和 register_chrdev 的关系
上一篇 char_lab 使用传统字符设备写法:
1 2 3 4 5 6 ret = register_chrdev(0 , "char_lab" , &char_lab_fops); char_lab_major = ret; char_lab_devno = MKDEV(char_lab_major, 0 ); char_lab_class = class_create(THIS_MODULE, "char_lab" ); device_create(char_lab_class, NULL , char_lab_devno, NULL , "char_lab" );
这一篇 misc_lab 使用 MISC 写法:
1 2 3 4 5 6 misc_lab_device.misc.minor = MISC_DYNAMIC_MINOR; misc_lab_device.misc.name = "misc_lab" ; misc_lab_device.misc.fops = &misc_lab_fops; misc_lab_device.misc.mode = 0666 ; misc_register(&misc_lab_device.misc);
对比:
写法
主设备号
/dev 节点
代码量
适合
register_chrdev
动态分配
需要 class/device 或手动 mknod
中
学习字符设备传统流程
miscdevice
固定主设备号 10
内核自动创建
少
简单单节点字符设备
手写 cdev
灵活
需要 class/device
多
正式复杂驱动
可以把 MISC 理解为:
1 2 3 4 5 miscdevice = 字符设备注册 + 固定 major 10 + 动态 minor + 自动 /dev 节点
它不是全新的设备模型,而是对常见小字符设备场景的封装。
四、MISC 驱动 API
当设置好miscdevice以后就需要使用misc_register函数向系统中注册一个MISC设备 ,此函数原型如下:
1 int misc_register (struct miscdevice * misc)
函数参数和返回值含义如下: - misc:要注册的MISC设备。 - 返回值:负数,失败;0,成功。
以前需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中会使用如下几个函数完成设备创建过程:
1 2 3 4 5 alloc_chrdev_region(); cdev_init(); cdev_add(); class_create(); device_create();
现在可以直接使用 misc_register 一个函数来完成以上代码中的这些步骤。当卸载设备驱动模块的时候需要调用misc_deregister 函数来注销掉 MISC设备,函数原型如下:
1 int misc_deregister (struct miscdevice *misc)
函数参数和返回值含义如下:
misc:要注销的MISC设备。
返回值:负数,失败;0,成功。
以前注销设备驱动的时候,需要调用一堆的函数去删除此前创建的cdev、设备等等内容,如下所示:
1 2 3 4 cdev_del(); unregister_chrdev_region(); device_destroy(); class_destroy();
现在只需要一个 misc_deregister 函数即可完成以上代码中的这些工作。
五、实例
功能:
1 2 3 4 5 write "1" -> 状态置 1 write "0" -> 状态置 0 read -> 返回当前状态 open -> dmesg 打印打开次数 release -> dmesg 打印 closed
misc_lab.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <linux/atomic.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/uaccess.h> #define MISC_LAB_NAME "misc_lab" struct misc_lab { struct miscdevice misc ; int value; atomic_t open_count; };static struct misc_lab misc_lab_device ;static int misc_lab_open (struct inode *inode, struct file *file) { int count = atomic_inc_return(&misc_lab_device.open_count); pr_info(MISC_LAB_NAME ": opened %d times\n" , count); return 0 ; }static int misc_lab_release (struct inode *inode, struct file *file) { pr_info(MISC_LAB_NAME ": closed\n" ); return 0 ; }static ssize_t misc_lab_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char kbuf[8 ] = {0 ,}; if (count == 0 || count >= sizeof (kbuf)) { return -EINVAL; } if (copy_from_user(kbuf, buf, count)) { return -EFAULT; } if (kbuf[0 ] == '1' ) { misc_lab_device.value = 1 ; } else if (kbuf[0 ] == '0' ) { misc_lab_device.value = 0 ; } else { return -EINVAL; } return count; }static ssize_t misc_lab_read (struct file *file, char __user *buf, size_t count, loff_t *ppos) { char kbuf[4 ]; int len; if (*ppos > 0 ) { return 0 ; } len = scnprintf(kbuf, sizeof (kbuf), "%d\n" , misc_lab_device.value); if (count < len) { return -EINVAL; } if (copy_to_user(buf, kbuf, len)) { return -EFAULT; } *ppos += len; return len; }static const struct file_operations misc_lab_fops = { .owner = THIS_MODULE, .open = misc_lab_open, .release = misc_lab_release, .read = misc_lab_read, .write = misc_lab_write, .llseek = no_llseek, };static int __init misc_lab_init (void ) { int ret; misc_lab_device.value = 0 ; atomic_set (&misc_lab_device.open_count, 0 ); misc_lab_device.misc.minor = MISC_DYNAMIC_MINOR; misc_lab_device.misc.name = MISC_LAB_NAME; misc_lab_device.misc.fops = &misc_lab_fops; misc_lab_device.misc.mode = 0666 ; ret = misc_register(&misc_lab_device.misc); if (ret) { pr_err(MISC_LAB_NAME ": failed to register misc device: %d\n" , ret); return ret; } pr_info(MISC_LAB_NAME ": loaded, /dev/%s ready\n" , MISC_LAB_NAME); return 0 ; }static void __exit misc_lab_exit (void ) { misc_deregister(&misc_lab_device.misc); pr_info(MISC_LAB_NAME ": unloaded\n" ); } module_init(misc_lab_init); module_exit(misc_lab_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("orange-pi-edge" ); MODULE_DESCRIPTION("Minimal miscdevice driver for Linux driver learning" ); MODULE_VERSION("1.0" );
Makefile:
1 2 3 4 5 6 7 8 9 10 11 obj-m += misc_lab.o KDIR ?= /lib/modules/$(shell uname -r) /buildall: $(MAKE) -C $(KDIR) M=$(PWD) modulesclean: $(MAKE) -C $(KDIR) M=$(PWD) clean.PHONY : all clean