Android Input 子系统初探
Android系统基于Linux内核实现,内核作为整个操作系统的核心,对下,它负责整个硬件的驱动、实现对硬件器件的控制管理;对上,它提供各种系统所需的核心功能。Android系统支持的输入设备较多,如按键、触摸屏、手柄等,面对种类繁杂的输入设备,内核通过抽象化的方式来使得各输入设备的的核心处理流程统一化,细节处理流程差异化(通过不同类型的回调实现),这就是Input子系统所要完成的内容,总结来说,它在内核中主要作用为:
-
规范化Input Device的定义方式及其数据的上报格式;
-
规范化Input Handler的定义方式及其需要实现的回调;
-
为Input Device和Input Handler提供核心服务;
-
提供标准化用户空间接口;
图1 Input事件处理整体框图
整体的Input事件处理框图如图1所示,本文主要围绕这张图来详述Input子系统的各个方面,如定义、初始化、注册匹配、事件传递、与用户空间的交互等。介于本人理解有限,如有叙述不当的地方,还请谅解指出。
一.Input子系统相关定义
在前序内容中,我们提前用到了Input Device、Input Handler等名词,但还没有进行相关的解释说明。本节的内容旨在了解Input子系统中几个重要的结构体定义,以便于Input子系统的后续介绍。
1. Input_dev
struct input_dev用来抽象所有的输入设备,由于不同的输入设备上报的事件或形式存在差异,抽象的input_dev必然需要包含差异的内容,形成一种x+(y1、y2、y3..)的方式(其中,x为所有输入设备共有的成员,y1/y2/y3为输入设备差异化成员),所以在实际的特定输入设备驱动开发工作中,只需要填充部分成员即可完成input_dev的定义。详细的成员定义说明如下:
2. Input_handler
struct input_handler用于抽象事件处理,不同的输入设备对应的事件处理方式会存在差异,linux内核抽象该结构体保证input事件的处理流程一致,具体的实现部分通过input_handler的函数指针回调完成,主要包括匹配、建立连接、事件传递/过滤等,具体的成员说明如下:
3. Input_handle
在抽象input_dev和input_handler之后,我们知道一个input_dev上报的事件可以被多个input_handler接收处理,一个input_handler也可以处理多个input_dev上报的事件,这样多个input_dev和多个input_handler之间可能会形成交织的网状(如下图2)。在这种情况下,需要一个桥梁来搭建两者之间的联系,两边的函数调用都可以通过这个“中介”进行,input_handle就是这个桥梁。
图2 device与handler示意图
Input_handle的定义比较简单,各成员说明如下:
二.Input子系统相关流程
主要流程包括input core初始化、input设备注册、input_handler注册、input设备与input_handler匹配、input事件传递。
1. input core初始化
Input core通过sybsys_initcall注册设定启动等级,保证其初始化会早于input设备和input_handler的注册(module_init方式注册),在初始化过程中主要完成:
1)input类注册;
2)Proc文件创建,主要用于input_handler和devices信息查看;
3)注册字符设备,主设备号为13;
2. input_dev注册
一般而言,Input设备驱动需要完成设备的控制和响应上报,其中响应上报是通过注册的input_dev来完成,所以input_dev的注册需要在input设备驱动的初始化过程中调用input_register_device完成,input_register_device执行的过程说明如下:
3. input_handler注册
Input handler的注册相对于input设备的注册更为简单,在填充struct input_handler后,直接调用input_register_handler完成handler的注册,input_register_handler的处理流程如下:
4. input_handler与input_dev匹配
Input_handler与input_dev的注册最终都会调用input_attach_handler完成自己与“相亲对象”的配对,配对完成后input_dev、input_handler、input_handle之间的关系如图3所示,设备驱动和事件处理层驱动都可以通过自身访问到input_handle,然后通过input_handle访问到自己的“对象”,具体的匹配代码说明如下:
图3 input_handle与owner关系图
当handler中match回调没有实现时只用根据input_dev中的id与input_handler中id_table包含的id进行匹配,如evdev_handler匹配所有Input设备,故所有的input设备都可以通过dev/input/event*路径获取原始上报数据。
在匹配成功后会调用connect回调,举例evdev_handler中的connect内容如下:
5. Input事件传递
不同的input设备上报的input事件的格式不同,比如触摸屏上报input事件时一般需要上报手指的id、x坐标、y坐标等信息(如图4为B协议报点格式, A协议报点无需上报id,会在inputReader中重新分配)。
图4 触摸屏报点事件格式
每一个事件上报都是通过input_event接口来完成,在判定事件类型是否支持后,主要是调用input_handle_event来完成:
该接口中,首先根据type、code判定该事件的disposition,当disposition为INPUT_PASS_TO_DEVICE时,将该事件传递给input_dev设备自身的event函数处理;当disposition为INPUT_PASS_TO_HANDLERS时,即将该事件传递给事件处理层处理,此处一般是将所有的事件存储在dev的vals数组中(此处,在disposition为INPUT_SLOT时表明上次处理的点与本次不同,故多添加一个ABS_MT_SLOT事件);当disposition为INPUT_FLUSH时或者传递的事件达到数组的极限时才将事件传递给事件处理层处理( input_sync时,disposition才能取得INPUT_FLUSH这个值)。
input_get_disposition函数是将根据type和code判定事件的disposition,此处只关心EV_SYN、EV_KEY、EV_ABS事件。EV_KEY事件中当设置了按键自动重发时的value值为2,!!test_bit(code, dev->key) != !!value语句中都进行了两次取反操作是为了避免出现0、1之外的数据,如果本次上报的按键事件与上次不同才会进行上报给事件处理层(dev->key保存了最近按键事件的所有状态),否则不予处理。
在收到sync事件或者event buffer size接近最大值时开始同步事件,此时传递的为一包事件,input_pass_values接口主要是寻找input_dev设备对应的handle处理存储的数据,另外设置的输入设备支持EV_REP事件,则会在此处设置定时器自动重发按键事件(按键值为2)。
所有的input设备都会和evdev_handler匹配,此处假设匹配的handler为evdev_handler,则events指向的函数为evdev_events:
evdev_pass_values只是将传过来的所有事件存储在client->buffer中;kill_fasync函数用于发送通知事件,告诉上层client->buffer中有数据可以读了。
6. Input事件传递给用户空间
当应用层或框架层调用read函数读取/dev/input/event*文件时,会调用evdev_read返回数据,其中event_fetch_next_event是判断client->buffer这个循环缓冲区中的头尾指针是否相等(相等时buffer中没有数据),不相等时取出一个input_event类型的事件放入到event中;input_event_to_user函数是将此事件copy到应用层,input_event_size函数是用来获取一个input_event事件的大小,循环复制client->buffer中的事件到应用层的buffer中。
上层谁会来打开文件读这些事件?一种是getevent工具,另外一种是android框架层的inputflinger服务,其主要会创建InputReader和InputDispatcher两个线程。InputReader负责与底层的事件打交道,其先通过eventHub读取所有的事件, 然后通过设备属性或事件特征找到对应的mapper处理将底层事件转换为android设计的事件类型;InputDispatcher负责与窗口打交道,将收到的事件派发给对应注册的窗口。
参考文献
1. https://www.cnblogs.com/lifexy/p/7542989.html
2. https://blog.csdn.net/qq_39937242/article/details/82631165
“内核工匠”微信公众号
Linux 内核黑科技 | 技术文章 | 精选教程