3. Linux输入子系统基础
Android 的触控链路并不是从 MotionEvent 开始的,它的最底层源头在 Linux 内核的 input 子系统。理解 input 子系统的框架、设备节点、事件结构体和上报流程,是后续分析 /dev/input/eventX、getevent、InputReader 乃至整条触控链路的前提。本节从内核视角出发,把这条“从硬件中断到用户空间可读事件”的路径讲清楚。
一、input 子系统框架:三层结构
Linux input 子系统采用典型的分层设计,自上而下可分为三层,各层职责清晰、通过统一的数据结构解耦:
- 输入设备驱动层(device driver):直接与硬件打交道,负责初始化设备、申请中断、读取寄存器,并把原始数据转换为标准输入事件。例如触摸屏驱动、按键驱动、鼠标驱动。
- input 核心层(input core):位于
drivers/input/input.c,是子系统的大脑。它维护所有输入设备的链表,提供input_register_device()、input_event()等核心接口,负责把驱动上报的事件分发到对应的 handler。 - 事件处理层(event handler):把核心层分发来的事件按类型处理,并暴露给用户空间。最常见的是
evdev,它对应/dev/input/eventX节点;此外还有joydev、mousedev等。
三层之间通过 input_dev 和 input_handle 关联:驱动注册 input_dev,核心层为每个匹配的 handler 建立 input_handle,事件便沿着 handle 找到 evdev,最终写入环形缓冲区供用户空间读取。
二、event 设备节点:用户空间的入口
当 evdev handler 与某个 input_dev 匹配成功后,内核会在 /dev/input/ 下创建字符设备节点,命名形如 event0、event1……每个节点对应一个输入设备。用户空间通过标准的 open()、read()、ioctl() 操作它。
在 Android 设备上,可以通过以下命令查看节点与设备的对应关系:
adb shell getevent -pl
# 输出示例(节选):
add device 1: /dev/input/event2
name: "sec_touchscreen"
events:
ABS (0003): ABS_MT_POSITION_X ...
KEY (0001): BTN_TOUCH ...
add device 2: /dev/input/event0
name: "gpio-keys"
events:
KEY (0001): KEY_VOLUMEUP ...
节点名称、支持的事件类型(evbit)、绝对坐标范围(absbit)等信息,都可以通过 EVIOCGNAME、EVIOCGBIT 等 ioctl 获取。getevent -pl 正是对这些 ioctl 的封装。理解节点与物理设备的映射,是排查“触摸没反应”“按键错乱”等问题的第一步。
三、input_event 结构体:事件的统一载体
所有输入事件都以 struct input_event 的形式在用户空间呈现,定义在 linux/input.h 中:
struct input_event {
struct timeval time; // 事件时间戳(秒 + 微秒)
__u16 type; // 事件类型,如 EV_ABS / EV_KEY / EV_SYN
__u16 code; // 事件代码,如 ABS_MT_POSITION_X / BTN_TOUCH
__s32 value; // 事件值,如坐标值、按下/抬起
};
三个字段的组合决定了事件语义:type 说明“哪一类事件”,code 说明“具体是哪一个”,value 说明“值是多少”。触摸屏上报一个点,通常需要多个 input_event 配合,最后以 EV_SYN / SYN_REPORT 收尾,表示这一帧数据完整。
| type 常量 | 含义 | 典型 code | 典型 value |
|---|---|---|---|
| EV_SYN | 同步事件 | SYN_REPORT | 0 |
| EV_KEY | 按键/触摸状态 | BTN_TOUCH | 1 按下 / 0 抬起 |
| EV_ABS | 绝对坐标 | ABS_MT_POSITION_X | 坐标值 |
| EV_REL | 相对位移 | REL_X | 增量值 |
四、设备注册与上报流程
一个输入设备从驱动加载到用户空间可读,经历“注册—匹配—上报—读取”四个阶段:
- 分配与初始化:驱动调用
input_allocate_device()分配input_dev,设置name、id,并通过set_bit()声明支持的事件类型与代码。 - 注册设备:调用
input_register_device(),核心层把设备加入全局链表,并为每个已注册的 handler(如 evdev)建立input_handle,同时创建/dev/input/eventX节点。 - 事件上报:硬件中断触发后,驱动读取数据,调用
input_report_abs()、input_report_key()等接口,最终进入input_event(),核心层沿 handle 把事件分发给 evdev。 - 用户空间读取:evdev 把事件写入环形缓冲区,用户空间
read()得到input_event数组,Android InputReader 再将其解析为RawEvent。
关键点:注册决定“设备是否存在”,上报决定“事件是否产生”,两者缺一不可。设备注册成功但不上报,节点存在却读不到数据;上报接口调用错误(如 code 写错),则事件语义完全错乱。
五、案例与常见误区
案例:某触摸屏驱动注册后,getevent 能看到 event2 节点,但滑动时无坐标输出。排查发现驱动只调用了 input_report_key(BTN_TOUCH),却漏掉了 input_report_abs(ABS_MT_POSITION_X/Y),且未调用 input_sync()。补全上报并加上 input_sync() 后,事件正常。
常见误区:
- 忘记调用
input_sync(),导致事件被缓冲但用户空间读不到完整帧。 - 把
EV_ABS与EV_REL混用,触摸屏应使用绝对坐标。 - 注册时未用
set_bit()声明能力,导致 evdev 不识别对应事件。 - 误以为
/dev/input/eventX编号固定,实际编号随设备注册顺序变化,应通过name匹配。
调试建议:先用 getevent -pl 确认节点与能力,再用 getevent -lt /dev/input/eventX 实时观察事件流,最后对照 input_event 三字段逐条解读。掌握这套方法,后续分析 Android InputReader 的 RawEvent 会顺畅很多。