一、杂项设备驱动介绍
1.1 系统介绍
本文是基于linux-2.6.32内核进行分析的,如果使用的是其他版本的内核,其内核调用的函数可能有所不同,但是其实现原理是相通的。
1.2 杂项设备驱动的引入
在前面一小节里面,我们详细介绍了字符设备驱动程序,知道字符设备指那些必须以串行顺序依次进行访问,且没有经过系统快速缓冲的设备,了解了Linux内核中驱动的框架和组成,以及编写的步骤等。但是,当我们写的驱动程序多了之后,就会发现:部分硬件并不符合预先定义的字符设备的范畴,而且普通字符设备的主设备号不管是静态分配还是动态分配,都会消耗一个主设备号(目前一个系统最多只能有255个字符设备),比较浪费主设备号资源。因此,而引入了杂项设备驱动。
在Linux里面,把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体描述)。杂项设备是一个典型的字符设备(与接下来要介绍的输入子系统一样,呵呵),其主设备号固定为10。其内部实现就是用主设备号10来调用register_chrdev()实现的;并且在内部还调用了class_create()和device_create ()为每个杂项设备创建设备节点,从而避免了我们通过mknod命令或自行调用该两个函数来创建设备节点的麻烦。
从以上这点来说,杂项设备就是将我们平常编写字符设备的驱动进行了再次封装,降低了我们编写字符设备驱动的难度,同时节约了主设备号资源。
1.3 杂项设备与字符设备实现比较
在进行字符设备驱动程序开发的过程中,我们的实现步骤如下:
- 申请一个字符设备号:可以自己指定,也可系统自动分配;
- 构造一个file_operations结构体,其包含对硬件的所有操作;
- 实现file_operations结构体中的成员函数;
- 将字符设备注册进系统中:register_chrdev();
- 创建设备类和设备节点:class_create()、device_create();
- 告诉内核入口与出口函数:module_init()、module_exit();
杂项设备驱动也是字符设备驱动,那么其注册的过程与字符设备驱动一样,也必须经过上面的这些步骤,只是杂项设备驱动中的对申请字符设备号、注册字符设备到系统、创建设备类和设备节点进行了封装,我们只需要完成如下几步开发即可:
- 构造一个file_operations结构体,其中包含对硬件的所有操作;
- 实现file_operations结构体中的成员函数;
- 构造一个杂项设备驱动(struct miscdevice)实体,并赋值前面定义的file_operations实体;
- 在入口函数处调用misc_register()向系统注册杂项设备;
- 在出口函数处调用misc_deregister()从系统注销杂项设备;
- 告诉内核入口与出口函数:module_init()、module_exit();
从中也可以得出一个结论:无论Linux内核对驱动框架设计的如何好,内核实现了多少的代码,与硬件相关部分的代码还是需要我们去实现。
本文的重点内容就是讲解如上几点内容:
- 杂项子系统如何向内核注册;
- 杂项子系统如何实现字符设备注册;
- 杂项子系统如何创建设备类和设备文件;
- 如何编写一个杂项设备驱动程序;
1.4 杂项设备和字符设备与应用层数据交互比较
编写过字符设备驱动的人都知道,应用程序与驱动之间实现数据交互就是通过应用API的read()、write()调用,从而产生一个SWI软件中断,然后通过主设备号找到对应的struct cdev结构体实体,从而找到具体硬件设备的struct file_operations结构体,然后具体调用底层的drv_read()、drv_write(),我们就是在具体的drv_read()和drv_write()中实现对硬件的操作的,其过程如下:
read()—>swi_read()—>drv_read()—>硬件操作
那么对于杂项设备驱动呢?前面已经说了,杂项设备也是字符设备,那么它也必须经历上面的这些步骤,只是中间穿插了几个通过次设备号找到对应struct file_operation结构体的过程而已而已。那么是如何穿插的呢:
首先在mist.c(杂项子系统的核心)文件的打开函数中获取此设备号,然后次设备号找到对应的fops(也即struct file_operations结构体)填充struct file中的f_op成员,那么之后应用调用read()、write()函数就是调用具体次设备号对应的struct file_operations结构体中的成员了;最后再调用fops中的open()函数打开具体的函数:
open()—>
swi_open()—>
misc_open()
{
int minor = iminor(inode); //获取次设备号
new_fops = fops_get(c->fops);
old_fops = file->f_op;
file->f_op = new_fops;
file->f_op = fops_get(old_fops);
}
read()/write()—>
swi_read()/swi_write()—>
new_fops->drv_read()/drv_write()
这里说明一下:misc.c是杂项子系统的核心,内核已经实现,我们只需要实现底层的硬件操作函数集合,然后调用misc_register()告诉杂项子系统就好了。
通过前面的介绍,不太理解也没有关系。你只需要记住,其实杂项子系统就是一个典型的字符设备系统,它也逃不过字符设备的框架,其应用与驱动交互的流程也和字符设备驱动一样,没有什么不同就是了(要从心里小瞧它)。
要想搞明白杂项子系统的框架,只需要弄明白应用程序是如何与具体的硬件设备驱动进行交互的就行了。本文的重点就是对杂项子系统的注册、杂项子系统中驱动与应用数据交互过程,以及如何编写杂项子系统进行分析的。
二、杂项子系统实现
2.1 杂项子系统
杂项设备是主设备号为10的驱动设备,比较适用于功能简单的设备。它有自己的设备结构体(/include/linux/miscdevice.h):
struct miscdevice {
int minor; //次设备号
const char *name; //设备名字
const struct file_operations *fops; //文件操作集合
struct list_head list; //连接到misc_list的链表
struct device *parent; //父设备
struct device *this_device; //当前设备,是device_create的返回值
const char *nodename;
mode_t mode;
};
miscdevice在本质上仍然属于字符设备,只是被增加了一层封装而已,因此其驱动的主体工作还是file_operations的成员函数。
其中minor是次设备号,杂项子系统主要是依赖minor来对不同的杂项设备驱动进行管理,如果该项设置为MISC_DYNAMIC_MINOR,表示由系统自行分配次设备号。name是调用misc_register()注册杂项设备时创建节点文件的名字;list是双向链表,用于将要注册的杂项设备注册进misc_list链表中。如下图所示:
接下来的内容,将详细介绍misc.c杂项子系统的实现过程,主要包括:
- 杂项子系统的注册;
- 杂项子系统之设备注册;
- 杂项子系统之设备注销;
- 杂项子系统之设备使用;