Linux驱动分为字符设备驱动、块设备驱动和网络设备驱动,而字符设备又包括很多种,内核使用主设备号来区分各个字符设备驱动,在include/linux/major.h
文件中已经预先定义好了各类字符设备的主设备号,但是即便如此,仍然存在着大量字符设备无法准确归类,对于这些设备,内核提供了一种Misc(杂项)设备来安放它们的去处。
Misc子系统使用一个统一的主设备号来管理,当需要注册Misc驱动时,内核会为其分配次设备号。而如果采用普通字符设备驱动的方式,无论主设备号是静态分配还是动态分配,都会消耗掉一个主设备号,而且如果系统存在着大量的无法准确归类的字符设备驱动,那会大量浪费主设备号;当需要开发一个功能较简单的字符设备驱动,导出接口让用户空间程序方便地控制硬件,只需要使用Misc框架提供的接口即可快速地实现一个Misc设备驱动。
Misc框架位于driver/char/misc.c
文件中,从misc_init
函数开始分析
static int __init misc_init(void)
{
int err;
#ifdef CONFIG_PROC_FS
proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
misc_class = class_create(THIS_MODULE, "misc"); // 创建misc类
err = PTR_ERR(misc_class);
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) // misc通过实现为字符设备驱动来注册
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
printk("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
remove_proc_entry("misc", NULL);
return err;
}
先是创建了Misc类,随后将Misc子系统实现为字符设备驱动来注册到内核中,并为其绑定了fops。
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
fops只实现了open方法,暂且不分析fops,先分析内核为驱动开发人员导出的注册接口misc_register
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
// 查找设备是否已经注册
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
}
// 动态分配次设备号
if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 找到位图中第一个为0的bit
if (i >= DYNAMIC_MINORS) { // 没有找到
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = DYNAMIC_MINORS - i - 1; // 分配次设备号
set_bit(i, misc_minors); // 将分配的次设备号加入位图
}
// 生成设备号
dev = MKDEV(MISC_MAJOR, misc->minor);
// 注册设备
misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name);
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
// 将已注册的驱动添加到链表上,open时可遍历链表来替换真正的fops
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
从上面可以看到,先查找设备是否已经注册(内核采用一个链表来管理已经注册的Misc设备驱动),然后判断是否需要动态分配次设备号(内核使用位图来管理已经注册的Misc次设备号),然后生成设备号,通过device_create
函数在Misc类下创建设备,这时候/dev
目录下就会根据misc->name
的值生成设备节点,然后将已经注册的驱动添加到链表上。
把关注点放到该接口需要传递的结构体struct miscdevice
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops; // 真正的fops
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
可以看到该结构体内部也定义了一个fops,需要驱动开发者使用该接口时实现一个fops,其实这个才是真正的fops,而在misc_init
函数中调用register_chrdev
来绑定的fops是用来中转数据的,具体情况可以从其open方法可以分析出来。
static int misc_open(struct inode * inode, struct file * file)
{
int minor = iminor(inode);
struct miscdevice *c;
int err = -ENODEV;
const struct file_operations *old_fops, *new_fops = NULL;
mutex_lock(&misc_mtx);
// 遍历链表来查找真正的fops
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops); // 获取真正的fops
break;
}
}
if (!new_fops) {
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops)
goto fail;
}
err = 0;
old_fops = file->f_op;
// 替换真正的fops,之后再调用其他接口(write、ioctl、close)时调用的是真正的fops
file->f_op = new_fops;
// 调用真正的fops中的open方法
if (file->f_op->open) {
file->private_data = c;
err=file->f_op->open(inode,file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
fail:
mutex_unlock(&misc_mtx);
return err;
}
遍历用来管理Misc设备驱动的链表,根据次设备号来找到真正的由驱动开发者用misc_register
接口注册的Misc驱动,然后获取其fops,该fops就是真正的fops。然后替换真正的fops,之后再调用其他接口(write、ioctl、close)时调用的则是真正的fops,所以用来中转数据的那个fops只定义了一个open方法。最后,该open方法并不是真正的open方法,所以需要调用真正的fops中的open方法。
Misc子系统使用同一个驱动来向上提供多个设备文件节点,向下控制多个(相应的)设备。Misc驱动本质上也是字符驱动,只是它借用Misc子系统的框架来更方便的向内核注册而已。驱动开发人员只需要把Misc设备的一些基本信息通过struct miscdevice
来构建,再通过misc_register
接口向内核注册即可。
本文作者: Ifan Tsai (菜菜)
本文链接: https://cloud.tencent.com/developer/article/2164584
版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!