Linux 输入子系统的设计目标非常明确,就是 抽象化。它的实现让驱动工程师可以不用管用户空间怎么读,让应用软件工程师不用管硬件是连在 USB,I2C,还是 GPIO 上,
1. Input子系统的三层模型
Linux Input 子系统是内核中用于统一管理各类 输入设备 的框架,比如键盘、鼠标、触摸屏等设备。它采用了经典的三层模型,从下到上依次为:设备驱动层,输入核心层,事件处理层。
这种分层设计让驱动开发者只需关注硬件特定部分,而无需重复实现用户空间接口和事件分发逻辑,简化了输入设备的驱动开发。
下面,逐个介绍一下这三层的特定职责:
1.1 设备驱动层
这是最接近硬件的一层,主要有具体设备的驱动程序实现,比如按键驱动,触摸屏驱动等。
主要职责如下:
- 操作硬件寄存器,处理中断或者轮询。
- 将硬件产生的原始信号转化为标准的
input_event结构体。 - 通过核心层提供的接口,比如
input_report_key上报事件。
设备驱动层的核心数据结构:struct input_dev
- 它代表一个 具体的输入设备。
- 我们需要设置设备名称、支持的事件类型、位图等信息。
- 通过
input_allocate_device分配、input_register_device注册到核心层。
驱动开发者主要的工作就在这一层:实现硬件初始化、中断处理函数,然后调用上报 API 把事件给核心层即可,完全不用关心上层怎么处理。
1.2 输入核心层
这是整个 Input 子系统的中枢。
它的主要作用如下:
- 提供统一的注册和注销接口给驱动层。
- 管理设备
input_dev和处理器input_handler之间的匹配,他们是多对多的关系,通过input_handle联系起来。 - 接收驱动层上报的
input_event,进行初步处理,比如过滤和添加时间戳,然后分发给已匹配的事件处理层。 - 维护设备列表、处理器列表等链表结构。
核心数据结构如下:
- 驱动层的
struct input_dev。 - 事件层的
struct input_handler。 - 还有
struct input_handle将二者联系起来。
1.3 事件处理层
这是纯软件层,负责 将内核事件传递给用户空间的应用程序。
主要职责如下:
- 实现不同的事件处理逻辑,如通用事件,键盘特定事件,鼠标特定事件等。
- 为每个输入设备创建用户空间可见的设备节点,即
/dev/input/eventX,X 代表某个数字。 - 将核心层传递来的事件打包,放入缓冲区,供用户程序通过
read、ioctl等读取。
evdev 是最通用,最重要的处理器,提供原始标准化事件给用户空间。
用户空间程序通常通过 /dev/input/eventX 节点读取 struct input_event 结构体,里面包含:
- 时间戳。
- 事件类型。
- 事件码。
- 值。
2. 核心连接器input_handle
input_handle 是 Linux Input 子系统中的 核心连接器,它负责将一个具体的输入设备 input_dev 和一个事件处理器 input_handler 连接起来,形成绑定关系。
一个输入设备可以同时被多个处理器绑定,比如键盘既被 evdev 处理,又可能被内核 keyboard handler 处理,一个 handler 也可以绑定多个设备。因此,input_handle 充当桥梁,实现了多对多的关联关系。
他们之间的具体关系如下:
input_dev代表一个硬件。input_handler代表一种处理逻辑。input_handle代表一个连接。
一句话总结:一个 input_dev 可以连接多个 input_handler。每当一对 dev 和 handler 配对成功,内核就会创建一个 input_handle 结构体来记录这个连接关系。
3. 核心层的注册流程
在了解具体的流程之前,我们得先看看这三个结构体到底长啥样。
3.1 struct input_handle 定义
该结构体定义位于 include/linux/input.h中,如下:
private:最常用的,handler常把自己的数据结构挂在这里。open:控制事件是否投递,只有 open > 0 时,事件才会真正传递给这个handle。d_node和h_node:这是实现一个设备连多个handler、一个handler连多个设备的关键链表节点。name:handle的名称,由handler创建时指定。dev:指向关联的输入设备。handler:指向关联的事件处理器。
3.2 struct input_dev定义
这个结构体相当长,我们需要注意的地方已经添加了注释:
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//支持哪些大类事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//支持哪些具体按键
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//相对坐标轴
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//绝对坐标轴
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
struct input_dev_poller *poller;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;//抓取某个handle,独占该设备。
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;//所有连接到此设备的input_handle链表头。
struct list_head node;//挂在全局input_dev_list上的节点
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
ktime_t timestamp[INPUT_CLK_MAX];
};
3.3 struct input_handler定义
第 321 行表示所有属于此 handler 的 input_handle 链表头。
3.4 建立连接的过程
当你调用 input_register_device(struct input_dev *dev) 时,内核发生了下面过程:
- 将
input_dev加入到内核维护的全局链表input_dev_list中。 - 遍历全局 处理器 链表
input_handler_list。 - 调用
handler->connect,对于每一个handler,调用核心层的匹配函数。匹配通常基于id_table,比如某个handler只处理按键,不处理触摸。 - 如果匹配成功,
handler内部会调用input_register_handle,创建一个handle结构体,将两者通过指针关联起来。 - 最后,在
/sys/class/input/下生成对应的设备节点。
4. 事件的分发
下面用一个按键的场景来模拟一下事件的分发。
当用户按下一个按键,中断触发,驱动程序调用 input_report_key:
input_report_key实际上是一个宏,最终调用input_event。- 核心层首先检查该事件
input_handle_event是否合法,也就是设备是否支持该事件。 input_pass_values是核心层的关键动作,它会找到与该input_dev关联的所有input_handle。- 调用
handle->handler->events,通过handle找到对应的handler,并将事件丢给它。 - 如果是
evdev handler,它会把事件存入一个环形缓冲区,并唤醒正在read该设备文件的用户进程。
最后,我画了一个流程图供大家参考,大家可以根据下面流程图梳理整个事件发生的过程:
本文结束。