文章系原创,如需转载请注明:转载自”Blog of UnicornX” (http://unicornx.github.io/)
最新更新于:2016-02-14
主要参考
Linux设备驱动开发详解(第2版)12.5章节,宋宝华
linux系统中misc子系统 - 这位博主写了不少好文章,值得参考
linux内核代码v2.6.32.6
linux子系统-miscdevice
杂项设备(miscdevice)是嵌入式系统中用得比较多的一种设备类型。在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。Linux内核所提供的miscdevice有很强的包容性,各种无法归结为标准字符设备的类型都可以定义为miscdevice,譬如NVRAM,看门狗,实时时钟,字符LCD等,就象一组大杂烩。
在linux内核里把所有的misc设备组织在一起,构成了一个子系统(subsys),统一进行管理。在这个子系统里的所有miscdevice类型的设备共享一个主设备号MISC_MAJOR(即10),但次设备号不同。
在内核中用struct miscdevice表示miscdevice设备,具体的定义在内核头文件include/linux/miscdevice.h。
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
miscdevice的API的实现在drivers/char/misc.c中,misc子系统的的初始化,以及misc设备的注册,注销等接口都实现在这个文件中。通过阅读这个文件,miscdevice类型的设备实际上就是对字符设备的简单封装,最直接的证据可以看miscdevice子系统的初始化函数misc_init(),在这个函数里,撇开其他的代码不看,其中有如下这两行代码:
...
misc_class = class_create(THIS_MODULE, "misc");
...
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
第一行,创建了一个名字叫misc的类,具体表现是在/sys/class/目录下创建一个名为misc的目录。以后每注册一个自己的miscdevice都会在该目录下新建一项。
第二行,调用register_chrdev为给定的主设备号MISC_MAJOR(10)注册0~255共256个次设备号,并为每个设备建立一个对应的默认cdev结构,该函数是2.6内核之前的老函数,现在已经不建议使用了。由此可见misc设备其实也就是主设备号是MISC_MAJOR(10)的字符设备。从面向对象的角度来看,字符设备类是misc设备类的父类。同时我们也要注意到采用这个函数注册后实际上系统最多可以支持有255个驱动自定义的杂项设备,因为杂项设备子系统模块自己占用了一个次设备号0。查看源文件可知,目前内核里已经被预先使用的子设备号定义在include/linux/miscdevice.h的开头。
在这个子系统中所有的miscdevice设备形成了一个链表,他们共享一个主设备号10,但它们有不同的次设备号。对设备访问时内核根据次设备号查找对应的miscdevice设备。这一点我们可以通过阅读misc子系统提供的注册接口函数misc_register()和注销接口函数misc_deregister()来理解。具体可以参考linux系统中misc子系统。
在这个子系统中所有的miscdevice设备不仅共享了主设备号,还共享了一个misc_open()的文件操作方法。
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
该方法结构在注册设备时通过register_chrdev(MISC_MAJOR,"misc",&misc_fops)传递给内核。但不要以为所有的miscdevice都使用相同的文件open方法,仔细阅读misc_open()我们发现该函数内部会检查驱动模块自己定义的miscdevice结构体对象的fops成员,如果不为空会将其替换掉缺省的,参考函数中的new_fops = fops_get(c->fops);以及file->f_op = new_fops;语句。如此这般,以后内核再调用设备文件的操作时就会调用该misc设备自己定义的文件操作函数了。这种实现方式是不是很类似于面向对象语言中的函数重载的概念。
采用miscdevice开发设备驱动的方法
大致的步骤如下:
第一步,定义自己misc设备的文件操作函数以及file_operations结构体对象,如下:
static const struct file_operations my_misc_drv_fops = {
.owner = THIS_MODULE,
.open = my_misc_drv_open,
.release = my_misc_drv_release,
//根据实际情况扩充 ...
};
第二步,定义自己的misc设备对象,如下:
static struct miscdevice my_misc_drv_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = KBUILD_MODNAME,
.fops = &my_misc_drv_fops,
};.minor如果填充MISC_DYNAMIC_MINOR,则由内核动态分配次设备号,否则根据根据你自己定义的指定。
.name给定设备的名字,也可以直接利用内核编译系统的环境变量KBUILD_MODNAME。
.fops设置为第一步定i的文件操作结构体对象的地址。
第三步,注册和注销
驱动模块一般在模块初始化函数中调用misc_register()注册自己的misc设备。实例代码如下:
ret = misc_register(&my_misc_drv_dev);
if (ret < 0) {
//失败处理
}
注意在misc_register()函数中有如下语句:
misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name);
这句话配合前面在misc_init()函数中的misc_class = class_create(THIS_MODULE, "misc");
这样,class_create()创建了一个类,而device_create()就创建了该类的一个设备,这些都涉及linux内核的设备模型和sys文件系统额概念,暂不展开,我们只需要知道,如此这般,当该驱动模块被加载(insmod)时,和内核态的设备模型配套运行的用户态有个udev的后台服务会自动在/dev下创建一个驱动模块中注册的misc设备对应的设备文件节点,其名字就是misc->name。这样就省去了自己创建设备文件的麻烦。这样也有助于动态设备的管理。
驱动模块可以在模块卸载函数中调用misc_deregister()注销自己的misc设备。实例代码如下:
misc_deregister(&my_misc_drv_dev);
在这个函数中会自动删除`/dev下的同名设备文件。
总结:
杂项设备作为字符设备的封装,为字符设备提供的简单的编程接口,如果编写新的字符驱动,可以考虑使用杂项设备接口,方便简单,只需要初始化一个miscdevice的结构体设备对象,然后调用misc_register注册就可以了,不用的时候,调用misc_deregister进行卸载。