Linux 输入子系统(Input Subsystem)是 Linux 内核中专门负责处理输入设备(按键、触摸屏、鼠标、键盘、游戏手柄等)的框架。
1. 为什么需要输入子系统?
如果没有这个子系统,每个硬件厂家都会发明自己的协议。
- A 厂家的鼠标发出的数据是:
"MOUSE: LEFT CLICK" - B 厂家的鼠标发出的数据是:
0x01 0x00 - 触摸屏发出的数据是:
X=100, Y=200
作为应用开发者(写 APP 的人),你会疯掉,因为你需要适配成千上万种硬件。
Linux 输入子系统的作用: "统一语言"。
无论底层是鼠标、键盘还是触摸屏,到了应用层,Linux 都把它们统一封装成 struct input_event 结构体。应用程序只需要读取这个结构体,就能知道发生了什么。
2. Linux 输入系统的框架
整个框架可以想象成一个 "三层汉堡包":
第一层:输入设备驱动层 (Input Device Drivers)
- 位置:最底层,直接和硬件打交道。
- 职责 :
- 初始化硬件(GPIO、I2C、SPI 等)。
- 读取寄存器或电平状态。
- 关键点:它不知道这些数据给谁用,它只负责把硬件产生的原始数据(比如"GPIO变低了")转化为核心层能听懂的"事件"(比如"按键被按下了"),然后汇报给核心层。
- 开发者工作:这是嵌入式驱动工程师主要工作的地方。
第二层:输入核心层 (Input Core)
- 位置 :
drivers/input/input.c - 职责 :
- 它是中间的"大管家"。
- 向下提供接口给驱动层注册设备 (
input_register_device)。 - 向上提供接口给事件处理层注册处理器 (
input_register_handler)。 - 匹配 (Match):当新的驱动加载时,它会查找有没有合适的处理程序;反之亦然。
第三层:事件处理层 (Input Event Handlers)
- 位置:最上层,直接和应用程序(用户空间)打交道。
- 职责 :
- 接收核心层转送过来的事件。
- 创建设备节点(例如
/dev/input/event0)。 - 把内核事件缓冲起来,当 APP 来
read()时,发给 APP。
- 最著名的 Handler :
evdev(Event Device)。它通吃所有输入设备,我们在开发中 99% 都是和/dev/input/eventX打交道。
架构图解
Plaintext
+-------------------------------+
| 用户空间应用程序 (APP) | (也就是你写的 read/scanf/Qt)
+-------------------------------+
^ | read()
| v
+-------------------------------+
| 设备节点 /dev/input/event0 |
+-------------------------------+
^
| (evdev.c)
+-------------------------------+
| 事件处理层 (Event Handler) | <-- 标准化接口
+-------------------------------+
^
|
+-------------------------------+
| 输入核心层 (Input Core) | <-- 负责撮合匹配
+-------------------------------+
^
| input_report_key()
+-------------------------------+
| 设备驱动层 (Device Driver) | <-- 你需要写代码的地方
+-------------------------------+
^
| (中断/轮询)
+-------------------------------+
| 硬件 (按键/触摸屏) |
+-------------------------------+
3. 核心数据结构:struct input_event
这是整个子系统的"通用货币"。无论什么设备,最终你在 APP 里读到的都是这个结构体(定义在 <linux/input.h>):
C
struct input_event {
struct timeval time; // 事件发生的时间戳
__u16 type; // 事件类型 (按键? 相对位移? 绝对位移?)
__u16 code; // 事件代码 (具体哪个键? X轴还是Y轴?)
__s32 value; // 事件值 (按下了还是松开了? 坐标是多少?)
};
type(类型) :EV_KEY: 按键(键盘、鼠标左键、触摸屏点击)。EV_REL: 相对位移(鼠标移动)。EV_ABS: 绝对位移(触摸屏坐标)。EV_SYN: 同步事件(告诉系统这一组信号发完了)。
code(代码) :- 如果 type 是
EV_KEY,code 可能是BTN_LEFT(鼠标左键) 或KEY_A(键盘A)。 - 如果 type 是
EV_ABS,code 可能是ABS_X(X轴) 或ABS_Y(Y轴)。
- 如果 type 是
value(值) :- 对于按键:
1(按下),0(松开),2(长按重复)。 - 对于坐标:具体的像素坐标值。
- 对于按键:
4. 如何调试输入系统?
在嵌入式 Linux (如你的 I.MX6ULL) 上,调试输入设备非常方便。
第一步:查看当前有哪些输入设备
使用 ls 命令查看设备节点:
Bash
ls -l /dev/input/
你会看到 event0, event1 等。
查看更详细的信息(看看 event0 到底是谁):
Bash
cat /proc/bus/input/devices
或者
Bash
ls -l /sys/class/input/event*
这能帮你确定:触摸屏是 event0 还是 event1。
第二步:使用 hexdump 查看原始数据
假设触摸屏是 /dev/input/event1。
你可以直接读它:
Bash
hexdump /dev/input/event1
然后在屏幕上点一下。你会看到一堆十六进制数据哗啦啦地打印出来。
- 现象 :有数据打印,说明驱动层 和硬件是好的。
- 缺点:看不懂,只能看到原始二进制。
第三步:使用 evtest 工具(神器,强烈推荐)
大多数嵌入式文件系统(Buildroot)都自带这个工具。
- 运行
evtest。 - 它会列出所有输入设备,让你选 ID。
- 选择你的触摸屏或按键。
- 操作硬件。
它会把二进制结构体翻译成人话:
Plaintext
Event: time 16843.22, type 3 (EV_ABS), code 0 (ABS_X), value 500
Event: time 16843.22, type 3 (EV_ABS), code 1 (ABS_Y), value 300
Event: time 16843.22, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
这表示:在坐标 (500, 300) 的位置,按下了触摸屏。
第四步:编写简单的测试代码 (APP)
你需要像操作文件一样操作它:
open("/dev/input/event1", O_RDONLY)read(fd, &my_event, sizeof(struct input_event))printf打印结构体里的内容。
总结
- 输入子系统是为了统一各类输入设备的接口。
- 核心结构体 是
struct input_event(时间、类型、代码、值)。 - 核心流程:硬件 -> 驱动 -> Core -> Handler (evdev) -> APP。
- 调试 :先用
cat /proc/bus/input/devices找设备,再用evtest抓数据。