深度剖析Linux Input子系统(1):宏观架构与核心原理

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 代表某个数字。
  • 将核心层传递来的事件打包,放入缓冲区,供用户程序通过 readioctl 等读取。

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。每当一对 devhandler 配对成功,内核就会创建一个 input_handle 结构体来记录这个连接关系。


3. 核心层的注册流程

在了解具体的流程之前,我们得先看看这三个结构体到底长啥样。

3.1 struct input_handle 定义

该结构体定义位于 include/linux/input.h中,如下:

  1. private:最常用的,handler 常把自己的数据结构挂在这里。
  2. open:控制事件是否投递,只有 open > 0 时,事件才会真正传递给这个 handle
  3. d_nodeh_node:这是实现一个设备连多个 handler、一个 handler 连多个设备的关键链表节点。
  4. namehandle 的名称,由 handler 创建时指定。
  5. dev:指向关联的输入设备。
  6. handler:指向关联的事件处理器。

3.2 struct input_dev定义

这个结构体相当长,我们需要注意的地方已经添加了注释:

c 复制代码
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 行表示所有属于此 handlerinput_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 该设备文件的用户进程。

最后,我画了一个流程图供大家参考,大家可以根据下面流程图梳理整个事件发生的过程:


本文结束。

相关推荐
东北甜妹2 小时前
playbook
linux·服务器·网络
我爱学习好爱好爱2 小时前
Ansible 入门:ad-hoc 临时命令与常用模块
linux·服务器·ansible
s09071362 小时前
【Zynq 进阶一】深度解析 PetaLinux 存储布局:NAND Flash 分区与 DDR 内存分配全攻略
linux·fpga开发·设备树·zynq·nand flash启动·flash分区
lwx9148522 小时前
Linux-sftp命令详解
linux·运维·服务器
wang09072 小时前
Linux性能优化之平均负载
linux·数据库·性能优化
BieberChen3 小时前
ubuntu定时执行脚本---crontab详细使用指南
linux·运维·ubuntu
小昭在路上……3 小时前
编译与链接的本质:段(Section)的生成与定位
java·linux·开发语言
风曦Kisaki3 小时前
#Linux进阶Day04 用户 sudo 提权、IP 地址配置、SELinux 安全管理
linux·tcp/ip·安全