LV005-输入设备简介
一、输入设备简介
1. 什么是输入设备?
先来了解什么是输入设备(也称为 input 设备),常见的输入设备有鼠标、键盘、触摸屏、 遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
2. 输入系统框架
2.1 input 子系统
由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么 Linux 系统如何管理呢? Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是 input 子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序。
input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件), 设备节点名称通常为 eventX(X 表示一个数字编号 0、 1、 2、 3 等),例如 /dev/input/event0、 /dev/input/event1、/dev/input/event2 等, 通过读取这些设备节点可以获取输入设备上报的数据。
2.2 框架概述
输入系统框架如图所示:

3. 读取数据的流程
如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
(1)应用程序打开/dev/input/event0 设备文件;
(2)应用程序发起读操作(例如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
(3)用户操作设备,硬件上产生中断;
(4)输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报。所谓输入事件就是一个“ struct input_event”结构体。
(5)核心层可以决定把输入事件转发给上面哪个 handler 来处理:从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等。最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核 buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。(当应用程序正在等待数据时, evdev_handler 会把它唤醒,这样应用程序就可以返回数据。)
(6)当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
(4)应用程序对读取到的数据进行解析。
当无数据可读时,程序会进入休眠状态(也就是阻塞),例如应用程序读触摸屏数据, 如果当前并没有去触碰触摸屏, 自然是无数据可读; 当我们用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据、应用程序就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式 I/O 方式下), 当有数据可读时才会被唤醒。
二、应用程序如何解析数据?
1. 内核中怎么表示一个输入设备?
我们先来看一下内核中怎么表示一个输入设备?使用 input_dev 结构体来表示输入设备,它的内容如图所示:

2. 应用程序得到什么样的数据?
我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢? 其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据, 该结构体定义在 < linux/input.h > 头文件中,它的定义如下:

结构体中的 time 成员变量是一个 struct timeval 类型的变量,timeval 表示的是“自系统启动以来过了多少时间”,它是一个结构体,含有“ tv_sec、 tv_usec”两项(即秒、微秒), 内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它 3 个成员变量 type(哪类事件)、 code(哪个事件)、value(事件值) 更为重要。
2.1 type
type 用于描述发生了哪一种类型的事件(对事件的分类) , Linux 系统所支持的输入事件类型如下所示(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin):
/*
* Event types
*/
#define EV_SYN 0x00 // 同步类事件,用于同步事件
#define EV_KEY 0x01 // 按键类事件
#define EV_REL 0x02 // 相对位移类事件(如鼠标)
#define EV_ABS 0x03 // 绝对位移类事件(如触摸屏)
#define EV_MSC 0x04 // 其它杂类事件
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)以上这些宏定义也是在 < linux/input.h > 头文件中,所以在应用程序中需要包含该头文件; 一种输入设备 通常可以产生多种不同类型的事件,例如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键 类事件,移动鼠标时则会上报相对位移类事件。
在 type 成员中,我们看到有一个同步事件类型 EV_SYN, 同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据,例如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、 Y 坐标以及其它信息, 对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来, 这样才能得到触摸点的完整信息。
那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的, 内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、 可以进行同步了。同步类事件中也包含了多种不同的事件,如下所示:
/*
* Synchronization events.
*/
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)所以的输入设备都需要上报同步事件, 上报的同步事件通常是 SYN_REPORT, 而 value 值通常为 0。
2.2 code
code 表示该类事件中的哪一个具体事件(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin), 以上列举的每一种事件类型中都包含了一系列具体事件, 如一个键盘上通常有很多按键, 例如字母 A、B、 C、 D 或者数字 1、 2、 3、 4 等, 而 code 变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件。
2.2.1 按键类事件
#define KEY_RESERVED 0
#define KEY_ESC 1 //ESC 键
#define KEY_1 2 //数字 1 键
#define KEY_2 3 //数字 2 键
#define KEY_TAB 15 //TAB 键
#define KEY_Q 16 //字母 Q 键
#define KEY_W 17 //字母 W 键
#define KEY_E 18 //字母 E 键
#define KEY_R 19 //字母 R 键
// ......2.2.2 相对位移事件
#define REL_X 0x00 //X 轴
#define REL_Y 0x01 //Y 轴
#define REL_Z 0x02 //Z 轴
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)2.2.3 绝对位移事件
触摸屏设备是一种绝对位移设备,它能够产生绝对位移事件; 例如对于触摸屏来说,一个触摸点所包含的信息可能有多种,例如触摸点的 X 轴坐标、 Y 轴坐标、 Z 轴坐标、按压力大小以及接触面积等, 所以 code 变量告知应用程序当前上报的是触摸点的哪一种信息(X 坐标还是 Y 坐标、亦或者其它)。绝对位移事件如下:
#define ABS_X 0x00 //X 轴
#define ABS_Y 0x01 //Y 轴
#define ABS_Z 0x02 //Z 轴
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
// ......除了以上列举出来的之外,还有很多,我们可以自己浏览< linux/input.h> 头文件(这些宏其实是定义在 input-event-codes.h 头文件中,该头文件被< linux/input.h > 所包含了) , 关于这些具体的事件,后面遇到了再学习。
2.3 value
表示事件值 ,内核每次上报事件都会向应用层发送一个数据 value, 对 value 值的解释随着 code 的变化而变化。对于按键事件(type = 1) 来说, 如果 code = 2(键盘上的数字键 1,也就是 KEY_1), 那么如果 value 等于 1,则表示 KEY_1 键按下; value 等于 0 表示 KEY_1 键松开,如果 value 等于 2, 则表示 KEY_1 键长按。再比如, 在绝对位移事件中(type = 3),如果 code = 0 (触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值; 同理, 如果 code = 1(触摸点 Y 坐标 ABS_Y),此时 value 值便等于触摸点的 Y 轴坐标值; 所以对 value 值的解释需要根据不同的 code 值而定。
3. 事件之间的界线
应用程序读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、 Y 位置信息,也可能会上报压力值。那它怎么知道已经读到了完整的数据?
驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。同步事件也是一个 input_event 结构体,它的 type、 code、 value 三项都是 0。
4. 输入子系统支持完整的 API 操作
支持这些机制:阻塞、非阻塞、 POLL/SELECT、异步通知。