Linux输入子系统

原创
2017/04/09 10:56
阅读数 725

一开始学习输入子系统觉得这东西很高大上,学习了之后发现这东西真的是挺高大上的。

下面就我学习的过程对输入子系统的理解来跟大家分享下,由于本人知识有限,有讲不对的地方欢迎批评纠正。

可以将输入子系统看做由三大部分组成,分别为核心层、事件处理层、设备驱动层,体现了一种分离分层思想。

核心层:这部分主要由input.c来实现,它为事件处理层和设备驱动层提供统一接口,这里我们先列出几个重要的函数

static int __init input_init(void)

int input_register_device(struct input_dev *dev)

int input_register_handler(struct input_handler *handler)

int input_register_handle(struct input_handle *handle)

这几个函数几乎就实现了整个输入子系统的运作过程,在后面我会采用函数跟踪的方式来一一解析这几个函数。

一般我分析程序会从入口函数开始分析,

subsys_initcall(input_init);是整个输入子系统的入口函数,它是被编译进内核的,也就是说一开机就会被执行的,

这里限于篇幅和能力就不讲函数怎样实现了,我们更关心的是input_init做了什么。

从函数名来看猜测是初始化子系统,不小心说了一句废话。接下来进入该函数

--->input_init

如果大家看了之前的字符设备驱动程序的话,相信对 register_chrdev(INPUT_MAJOR, "input", &input_fops);这个函数并不陌生,它就是注册一个设备名为input且主设备号为INPUT_MAJOR(13)的字符设备,大家可以通过命令 cat /proc/devices 查看。

接下来把重点放在input_fops,就我们学习字符设备时知道,应用程序打开,读写等操作最后就是调用input_fops里面相应的函数,但这里和字符设备不一样的是该结构体只有一个input_open_file函数,难道是应用程序只能打开该字符设备吗,答案当然是否定的,那你完全有理由怀疑是不是在input_open_file函数里实现了其它应用函数,接下来我们就进入该函数

--->input_open_file

struct input_handler *handler = input_table[iminor(inode) >> 5];这里就出现了一个重要的结构体input_handler ,我们先放着,

首先根据次设备号从input_table取出handler

new_fops = fops_get(handler->fops),file->f_op = new_fops;

然后从handler结构中获取一个fops结构并将替换原来的fops。

至此我们验证了之前的怀疑,即执行input_open_file这个函数最终无非是将老的fops替换为新的fops,也就是说应用程序以后执行读写等操作是调用新的fops里面相应的函数的,那我们又会怀疑了,到底fops是怎样来的,里面真的有相应的操作函数吗,答案同样是肯定的,该函数一开始就从input_table获得一项handler,接下来我们会一步步追踪它的由来。

搜索下,找到tatic struct input_handler *input_table[8]发现它是一个静态变量,由本文件定义使用

一步步找下来最终发现它在int input_register_handler(struct input_handler *handler)函数中定义,但这个又是由谁调用呢,

同样搜索下,找到了几个c文件调用了它,分别有evdev.c、joydev.c、mousedev.c等等。

分析到这里,就已经体现了一开始说的分离分层思想的分离思想,evdev.c、游戏手柄joydev.c、鼠标mousedev.c这些就是事件处理层,这部分的代码稳定,一般我们不用修改。

接着上面我们说的input_table在input_register_handler中定义的,我们现在要分析的就是input_register_handler函数做了哪些具体的事情。

我们找evdev.c这个事件处理层的input_register_handler来分析。

--->input_register_handler

这个函数主要做了三件事情
1、input_table[handler->minor >> 5]    将handler放进input_table表中,而不同的处理层该input_handler的结构不同
2、list_add_tail(&handler->node, &input_handler_list);    将handler放进input_handler_list链表中
3、list_for_each_entry(dev, &input_dev_list, node)    从input_dev_list链表中找出每个input_dev并执行input_attach_handler
        input_attach_handler(dev, handler);
从这里看的话,这个函数也就干了这主要的三件事情,当然我们如果跟踪下去的话,可能会理解的更深刻。

--->input_attach_handler
handler->blacklist && input_match_device(handler->blacklist, dev)
id = input_match_device(handler->id_table, dev);
若dev里含有handler的不支持项则匹配不成功

--->input_match_device
对两者的id进行匹配,同样若有不同则匹配不成功。
当两者匹配成功后执行handler->connect(handler, dev, id)

接下来跟踪进该connect函数
--->evdev_connect

evdev->handle.dev = dev;
evdev->handle.handler = handler;
handle连接了dev、handle

class_device_create(&input_class, &dev->cdev, devt,
                   dev->cdev.dev, evdev->name);创建一个设备节点
input_register_handle(&evdev->handle);注册一个handle,注意不是handler

--->input_register_handle
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
这样就可以将dev和handler建立起连接了
通过dev里的handle找到handler或者通过handler里的handle找到dev

至此我们对事件处理层的分析基本完成了
我们接下来再分析设备驱动层
一般设备驱动层都是有用户根据设备的硬件情况自己编写代码的
最终要调用input_register_device将设备注册进内核

--->input_register_device
1、list_add_tail(&dev->node, &input_dev_list);
2、list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);
主要的事情还是这两件,这和上面讲的事件处理层很对称。
主要将自己挂接在input_dev_list链表上,然后从input_handler_list链表中遍历每一个handler并执行input_attach_handler
之后匹配后的代码和事件处理层的一模一样,这里就不再重复了。

至此设备处理层也将完了。

接下来讲讲从应用程序的角度如何使用输入子系统。
其中一个重要的函数为input_event

例如有按键中断了,我们在中断服务函数里通过input_event告知内核有哪个事件发生了

--->input_event
list_for_each_entry(handle, &dev->h_list, d_node)
handle->handler->event(handle, type, code, value);
这个通知函数最终会调用handler来调用event处理函数
并唤醒等待事件。

以上就是整个输入子系统大概的运行过程。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部