字符设备驱动之输入子系统分析(二)
作者:Bright-Ho
联系方式:836665637@qq.com
input输入子系统框架分析(纯软件方面):
上一节,我们简单的描述的什么是输入子系统;什么是字符设备;以及其作用;重点是我们讲到分析输入子系统必须结合硬件设备来分析;那么这一节,我们主要讲解输入子系统的软件框架;接下来,我们就进入主题;
那么在进入主题之前,我们先来了解一个简单的字符设备驱动程序的框架;
(1)构造一个file_operations结构体;里面实现了read,write,open等函数,用于应用层调用;也就是给应用层提供接口;
(2)通过 register_chrdev()函数来注册驱动程序;所谓注册,就是以主设备号为下标,把file_operations结构体放入一个内核数组;
(3)通过udev机制,自动创建设备节点,通过下面两个函数来实现;
class_create(THIS_MODULE,"firstdrv");先创建一个类firstdrv,会在sys/class下生成;
class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");在类下面创建一个xyz设备;
(4)通过void first_drv_exit(void)函数,对所有资源进行卸载;
(5)修饰驱动程序的入口函数;
-
module_init(first_drv_init); /*加载驱动(insmod)的时候,会执行该宏*/
-
module_exit(first_drv_exit); /*卸载驱动(rmmod)的时候,会执行该宏*/
注意,内核版本不一样, class_create() class_device_create()这两函数也不一样,但实际效果是一样的;
这上面5个步骤就是一个简单的字符设备框架,不涉及字符设备的高级技巧;也不对应任何具体设备;就单单是一个简单字符设备的框架;
上面这5部需要理解两个问题:
-
从应用层的角度来看,应用层调用read,write,open等函数,是如何调用到驱动程序里面的open,read,write等函数的?
-
就是所谓udev机制,需要知道怎么使用内核提供的函数来自动创建设备节点;
上面这种字符设备驱动框架有一个很大缺点:它提供给应用程序的接口,也就是设备节点,只有自己清楚,或者只有自己公司的人知道它所提供的接口,别人不知到!所以它不是一个通用的设备驱动程序;这是引入udev机制的原因;
接下来,我们就要真正的讲解输入子系统了;
我一直认为内核就是最好的老师,所以学习输入子系统,就得去分析内核源代码;我采用的内核版本为2.6.22.6的版本,该版本有点老,有些东西和新版本内核不一样,但是对于学习来说没什么太大影响;如果对新老版本都了解的话,那就更好了;那么,在学习输入子系统之前,需要牢记一个问题,加入输入子系统的字符设备驱动的步骤和上面没有加入输入子系统的字符设备框架的5个步骤有什么区别?这个问题,只有分析了内核自带的输入子系统源码,你才能知道其中的区别;弄清楚了这个问题之后,你应该清楚,哪些事情是由内核帮我们实现的,哪些事情是需要我们自己完成的;
input输入子系统的内核源码位于:linux-2.6.22.6/drivers/input下;
首先明确一点,输入子系统分为三层,核心层,事件处理层,设备硬件层;
核心层(input.c):分析主要代码!!!
(1)入口函数input_init()
static int __init input_init(void)/*入口*/
{
int err;
err = class_register(&input_class); /*sys/class/input 创建设备类*/
err = input_proc_init();/* proc文件系统相关的初始化*/
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); /*注册字符设备,以majoy 13为索引,存放于把内核数组*/
...
}
input_fops结构体如下:
1279 static const struct file_operations input_fops = {
1280 .owner = THIS_MODULE,
1281 .open = input_open_file,
1282 };
<解析> 在入口函数中:
1. 在sys/class目录下,创建设备类,用于生成设备节点;
class_register(&input_class);
2. 注册字符设备驱动,以主设备号为下标,把input_fops 放入内核数组中,方便应用层通过主设备号索引;
register_chrdev(INPUT_MAJOR, "input", &input_fops);
注意:该函数执行后会在proc/devices里面生成设备信息;
问题: 我们知道应用程序调用open,read,write函数来操作设备,最终会调用到驱动程序里面所提供的open_drv,read_drv,write_drv函数,但是input_fops结构里面只提供了open函数,那么这是怎么回事呢?所以接下来,继续分析input_open_file函数;
(2)input_open_file函数分析:
static int input_open_file(struct inode *inode, struct file *file)
1245 {
1246 /*(1)从input_table数组中以次设备号为下标,取出一项给handler*/
1247 struct input_handler *handler = input_table[iminor(inode) >> 5];
1248 const struct file_operations *old_fops, *new_fops = NULL;
1252 /*(2)把handler里面的fops赋给new_fops*/
1253 if (!handler || !(new_fops = fops_get(handler->fops)))
1264 /*(3)把new_fops赋给file_f_op,实际上也就是handler里面的fops;*/
1265 old_fops = file->f_op;
1266 file->f_op = new_fops;
1267
1268 /*(4)调用new_fops里面的open函数*/
1269 err = new_fops->open(inode, file);
...
}
<解析>在分析input_open_file函数之前,有一个非常重要的结构体需要了解!!!
struct input_handler *handler
1065 struct input_handler {
1066
1067 void *private;
1069 void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
1070 int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
1071 void (*disconnect)(struct input_handle *handle);
1072 void (*start)(struct input_handle *handle);
1074 const struct file_operations *fops;
1075 int minor;
1076 const char *name;
1078 const struct input_device_id *id_table;
1079 const struct input_device_id *blacklist;
1081 struct list_head h_list;
1082 struct list_head node;
1083 };
该input_handler结构体在核心层引用,那么谁来初始化该结构体呢?后面会分析到;
-
把input_table数组中,通过次设备号找到一项,赋给input_handler 指针;
struct input_handler *handler = input_table[iminor(inode) >> 5];
-
把handler->fops赋给file_operations 指针;
new_fops = fops_get(handler->fops)
-
调用new_fops里面的open函数
new_fops->open(inode, file)
-
如果read的话,最终会调用到 file->f_op中的read函数;
实际上,最终调用的open函数,是input_handler里面的open函数;input_handler是从input_table数组中得到的;那么归根结底的问题就是input_table数组是由谁构造的?
(3)input_table数组是由谁构造的?
搜索“input_table”字段,可知:
static struct input_handler *input_table[8];
它是一个静态结构体指针数组,说明只能在本文件使用;
最终在input_register_handler(struct input_handler *handler) 函数中找到该“input_table”数组,在这里被赋值的;
所以接下来得分析input_register_handler()函数:
源码如下:
1182 int input_register_handler(struct input_handler *handler)
1183 {
1184 struct input_dev *dev;
1185
1186 INIT_LIST_HEAD(&handler->h_list);
1187
1188 if (handler->fops != NULL) {
1189 if (input_table[handler->minor >> 5])
1190 return -EBUSY;
1191 /*(1)把传进来的handler保存在input_table数组中*/
1192 input_table[handler->minor >> 5] = handler;
1193 }
1194
1195 /*(2)把handler放入handler链表*/
1196 list_add_tail(&handler->node, &input_handler_list);
1197 /*(3)把设备链表的每项,与handler比较看是否匹配*/
1198 list_for_each_entry(dev, &input_dev_list, node)
1199 input_attach_handler(dev, handler);
1200
1201 input_wakeup_procfs_readers();
1202 return 0;
1203 }
input_table[]数组是通过input_register_handler()函数的参数,来赋值的;也就是说谁调用该函数,就是谁传的这个input_handler结构体;
-
input_register_handler()函数把这个结构体,存放于input_table数组;
-
并且把这个input_handler结构体放入handler链表;
-
把设备链表的每项取出来,与handler比较看是否匹配;
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_attach_handler()函数源码如下:
static int input_attach_handler(struct inpuft_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
...
return error;
}
匹配设备和handler的id,匹配成功则调用handler->connect(handler, dev, id);
分析到这里,关键是看谁调用input_register_handler()函数;
遍历所有内核源代码:
input_register_handler()函数;被evdev.c(事件设备),tsdev.c(触摸屏设备),keyborad.c(键盘设备),以及mousedev.c(鼠标设备)等;都调用了该注册函数;
实际上我们这里就进入了所谓的“事件处理层”了!!!
所以说目前对于事件处理层来说,核心层做了如下几件事情:
-
提供了用于事件处理层的注册接口,也就是input_register_handler()函数;
-
提供了 input_match_device()匹配函数,通过id号来匹配设备,一旦匹配成功,则调用input_handler->connect函数;
注意:这个connect函数是由“事件处理层”实现的;
-
把事件处理层所提供的input_handler->fops通过register_chrdev()函数,放入内核数组,为应用层提供设备接口;
上面我们一直讲到input_handler这个结构体,它是在核心层里面声明,定义的,至于在哪里初始化的?就是“事件处理层”的事情了,“事件处理层”中会初始化这个结构体,并提交给核心层;
所以接下来,下一节我们具体的分析一个事件设备;