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,最终写入环形缓冲区供用户空间读取。

Linux input 子系统三层框架与事件流向 硬件层 触摸屏 / 按键 / 鼠标 → 产生中断或寄存器数据 IRQ / GPIO / I2C / SPI 输入设备驱动层 申请中断、读取原始数据、调用 input_report_* 上报 input_dev 结构体 name / id / evbit / absbit input 核心层(input core) input_register_device() / input_event() / 事件分发 input_handle 关联 dev ↔ handler 匹配 事件处理层(event handler) evdev / joydev / mousedev → 暴露字符设备节点 /dev/input/eventX 用户空间 read() 读取 事件上报 input_event() 用户空间:Android InputReader / getevent / 应用层 MotionEvent

二、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_REPORT0
EV_KEY按键/触摸状态BTN_TOUCH1 按下 / 0 抬起
EV_ABS绝对坐标ABS_MT_POSITION_X坐标值
EV_REL相对位移REL_X增量值

四、设备注册与上报流程

一个输入设备从驱动加载到用户空间可读,经历“注册—匹配—上报—读取”四个阶段:

  1. 分配与初始化:驱动调用 input_allocate_device() 分配 input_dev,设置 name、id,并通过 set_bit() 声明支持的事件类型与代码。
  2. 注册设备:调用 input_register_device(),核心层把设备加入全局链表,并为每个已注册的 handler(如 evdev)建立 input_handle,同时创建 /dev/input/eventX 节点。
  3. 事件上报:硬件中断触发后,驱动读取数据,调用 input_report_abs()、input_report_key() 等接口,最终进入 input_event(),核心层沿 handle 把事件分发给 evdev。
  4. 用户空间读取: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 会顺畅很多。