Linux input子系统(一)
发布时间 - 2025-04-17 00:00:00 点击率:次linux将键盘、鼠标、触摸屏等设备统称为输入设备,这些设备本质上是字符设备。当按下这些设备时,通常会触发一个中断,或者cpu会定期轮询这些设备。如果检测到有效的输入,cpu会将读取到的键值存储在缓冲区中,驱动的read接口允许用户层获取这些键值。无论是哪个设备产生了输入,cpu处理有效键值后的步骤是相同的,因此,linux设计了一个input框架来统一处理这些输入事件,这就是所谓的input子系统。简而言之,linux内核的输入子系统是对不同类型的输入设备进行抽象,并通过统一的服务函数来处理。
Linux输入子系统的架构可以分为以下三层:
- 驱动层(输入设备驱动):负责从底层硬件(如按键、键盘、鼠标等)获取输入,并将这些输入报告给input核心层。
-
核心层:向下为驱动层提供设备注册和操作接口,向上通知事件层处理相应的输入事件,并在/proc目录下生成设备信息。核心层的源码位于
drivers/input/input.c。 - 事件层(输入事件驱动):与应用层交互,实现了open、read、write等文件接口,并在/dev目录下生成对应的设备文件。
Linux自带的输入事件驱动程序包括:
| 输入事件驱动程序 | 说明 |
|---|---|
| evdev.c | 通用输入事件驱动,能处理大多数输入事件 |
| joydev.c | 游戏杆,操纵杆设备 |
| keyboard.c | 键盘设备 |
| mousedev.c | 鼠标设备 |
| keychrod.c | 组合按键设备 |
input device位于驱动层,表示每一个真实的输入设备,如鼠标、键盘、按键等。
struct input_dev {
const char *name; // 对应/sys/class/input/inputX/name 设备名
const char *phys; // 对应/sys/class/input/inputX/phys 系统层次结构中设备的物理路径
const char *uniq; // 对应/sys/class/input/inputX/uniq 设备的唯一识别码(如果设备有)
struct input_id id; // 设备id 用于匹配 input handler
// 设备属性和怪癖的位图
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 记录事件类型的位图 (EV_KEY, EV_REL 等)
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)]; // LED位图
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; // 音效位图
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
// 设备在数据包中生成的平均事件数(在 EV_SYN/SYN_REPORT 事件之间)由事件处理程序用来估计需要保存的缓冲区大小
unsigned int hint_events_per_packet;
unsigned int keycodemax; // 支持按键个数
unsigned int keycodesize; // 单个按键码占用多少个字节
void *keycode; // 储存按键值数组=keycodemax*keycodesize
// 改变当前键盘映射的可选方法,用于实现稀疏键盘映射。如果未提供,将使用默认机制。该方法在持有event_lock时被调用,因此不能休眠
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;
unsigned int repeat_key; // 存储最后按下的键的键码;用于实现软件自动重复
struct timer_list timer; // 用于软件自动重复的定时器
int rep[REP_CNT]; // 自动重复参数的当前值(延迟、速率)
struct input_mt *mt; // 指向多点触控状态的指针
// &struct input_absinfo元素数组,保存有关绝对坐标的信息(当前值、最小值、最大值、平面、模糊、分辨率)
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; // 反映设备按键/按钮的当前状态
unsigned long led[BITS_TO_LONGS(LED_CNT)]; // 反映当前LED的状态
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; // 反映当前音效状态
unsigned long sw[BITS_TO_LONGS(SW_CNT)]; // 反映当前设备开关状态
// 当第一个用户调用 input_open_device()时调用此方法。驱动程序必须准备设备以开始生成事件(启动轮询线程、请求 IRQ、提交 URB 等)
int (*open)(struct input_dev *dev);
// 当最后一个用户调用 input_close_device()时调用此方法。
void (*close)(struct input_dev *dev);
// 清除设备。最常用于在与设备断开连接时摆脱加载到设备中的力反馈效果
int (*flush)(struct input_dev *dev, struct file *file);
// 发送到设备的事件的事件处理程序,如EV_LED或EV_SND。设备应执行请求的操作(打开LED,播放声音等)。调用受@event_lock 保护,不得休眠 最常见的例子是键盘上面的大写灯
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
// 当前已抓取设备的input handle(通过EVIOCGRAB ioctl),当handle抓取设备时,它成为来自设备的所有输入事件的唯一接收者
struct input_handle __rcu *grab;
// 当输入核心接收并处理设备的新事件(在 input_event()中)时,将使用此自旋锁。在设备向输入核心注册后访问和、或修改设备参数(例如 keymap 或 absmin、absmax、absfuzz 等)的代码必须获得此锁。
spinlock_t event_lock;
// 序列化对 open()、close() 和 flush() 方法的调用
struct mutex mutex;
// 存储打开此设备的用户数(输入处理程序)。 input_open_device() 和 input_close_device() 使用它来确保仅在第一个用户打开设备时调用 dev->open() 并在最后一个用户关闭设备时调用 dev->close()
unsigned int users;
// 标记处于注销过程中并导致input_open_device*()与-ENODEV 故障的设备。
bool going_away;
struct device dev; // 此设备的驱动程序模型视图
struct list_head h_list; // 与设备关联的input handle 链表。访问链表时必须持有dev->mutex
struct list_head node; // 用于将设备放到 input_dev_list 上
unsigned int num_vals; // 当前帧中排队的值的数量
unsigned int max_vals; // 帧中排队的最大值数
struct input_value *vals; // 当前帧中排队的值数组
bool devres_managed; // 表示设备由devres框架管理,不需要明确取消注册或释放。
ktime_t timestamp[INPUT_CLK_MAX]; // 存储由驱动程序调用的input_set_timestamp设置的时间戳
};input handler表示一个或者一类输入设备的事件处理程序,它处理来自底层input设备的输入事件,并对应用层提供file_operations接口,用户层可以通过open、read系统调用获取input设备的输入事件。
struct input_handler {
void *private; // 驱动私有数据
// 事件处理程序。这个方法被input core调用,中断被禁用并且dev->event_lock自旋锁保持,因此它可能不会休眠
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
// 事件序列处理程序。这个方法被input core调用,中断被禁用并且dev->event_lock自旋锁保持,因此它可能不会休眠
void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);
// 类似于@event; 将普通事件处理程序与“过滤器”分开。
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
// 在将设备的id 与处理程序的id_table 进行匹配后调用,进行更精确的匹配,一般此函数为空
bool (*match)(struct input_handler *handler, struct input_dev *dev);
// 在将设备的id 与处理程序的id_table 进行匹配成功后调用,作用是将input dev和handler建立联系
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
// 设备或者驱动程序取消注册时调用,取消input 设备和handler之间的关联
void (*disconnect)(struct input_handle *handle);
// 启动给定句柄的处理程序。该函数在 connect() 方法之后以及当“抓取”设备的进程释放它时由输入核心调用
void (*start)(struct input_handle *handle);
// 当使用旧版小范围的驱动程序时设置为%true
bool legacy_minors;
int minor;
const char *name; // 驱动程序的名称,显示在 /proc/bus/input/handlers 中
const struct input_device_id *id_table; // 指向该驱动程序可以处理的 input_
device_ids 表的指针
struct list_head h_list; // 与该驱动程序关联的input handle列表
struct list_head node; // 用于将驱动程序放到 input_handler_list 上
};input handler关联到input device并创建input handle。可能有多个input handler同时关联到任何给定的input device。它们都将获得由设备生成的输入事件的副本。filter()和event()使用的参数完全一致。input core允许filter()首先执行,并且如果任何过滤器指示应过滤事件(通过从其 filter() 方法返回 %true ),则不会将事件传递给常规input handlers。
input handle用来关联input device和input handler。
struct input_handle {
void *private; // 私有数据
int open; // 显示该handle是否被打开
const char *name; // handler创建此handle时指定的名字
struct input_dev *dev; // 需要关联的input dev
struct input_handler *handler; // 与dev匹配的handler
struct list_head d_node; // 用于将此handle添加到dev的h_list
struct list_head h_node; // 用于将此handle添加到handler的h_list
};input core维护两个全局链表,分别是存放input设备的input_dev_list和input handler的input_handler_list。设备和事件的注册需要向这两个链表中添加新的节点,反之则需要删除节点。
在设备或事件注册的过程中,都会进行匹配,从而将设备和事件关联起来。下面是input设备注册过程:
当新的input设备进行注册时,会遍历所有的input_handler_list中的事件驱动一一进行匹配,匹配的条件有bustype,vendor,product,version,evbit等。当匹配成功后,再接着执行handler->connect()执行剩下的操作。实际上,如果是input事件驱动进行注册,流程也是差不多的,会遍历input_dev_list中所有的input设备依次进行匹配,匹配成功后,依然会执行handler->connect()执行剩下的操作。
input handler注册过程:
handler注册首先初始化h_list链表,然后将handler添加到全局input_handler_list,随后遍历input设备链表input_dev_list,依次取出每一个设备进行匹配,匹配过程同上,匹配完成后调用handler->connect()函数完成接下来的工作。
connect函数在前文提到的设备和事件驱动匹配后,handler->connect()是最终实现input设备和事件驱动绑定的函数。为什么需要connect函数进行绑定,而不是直接在注册阶段把driver的函数指针赋值给device的某个结构体成员?或者把device的函数指针赋值给driver的某个结构体成员?这是因为input设备是支持一对多或者多对一的,如果是一对一的关系,那么完全可以按照上述的做。于是Linux内核引入了另一个成员来完成此工作——struct handle(注意和struct handler的区别)。
当device和handler匹配后,connect函数中会将device和handler都存入handle结构体,然后再将handle结构体分别添加到device和handler的h_list链表中,这样device和handler不管是一对一还是一对多,都可以找到所有的handler或者device。本质上就是将device对应的handler用链表保存起来。
connect函数不仅实现了input handle的注册,还完成了input cdev的注册,每一次connect都会触发注册一个input handle和input cdev,而这个cdev就对应/dev/input/eventX。
# linux
# 区别
# 为什么
# 架构
# Filter
# 结构体
# 指针
# 接口
# Struct
# Event
# 事件
# input
# 链表
# 鼠标
# 遍历
# 并在
# 会将
# 键值
# 第一个
# 可选
# 按下
# 将此
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Linux系统命令中screen命令详解
Laravel如何配置Horizon来管理队列?(安装和使用)
如何在阿里云香港服务器快速搭建网站?
Laravel Octane如何提升性能_使用Laravel Octane加速你的应用
宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程
Laravel中间件如何使用_Laravel自定义中间件实现权限控制
Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议
Laravel怎么定时执行任务_Laravel任务调度器Schedule配置与Cron设置【教程】
Laravel如何处理异常和错误?(Handler示例)
Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】
javascript中的try catch异常捕获机制用法分析
佛山企业网站制作公司有哪些,沟通100网上服务官网?
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
Laravel如何使用.env文件管理环境变量?(最佳实践)
Laravel如何使用Blade组件和插槽?(Component代码示例)
Python文件异常处理策略_健壮性说明【指导】
laravel怎么通过契约(Contracts)编程_laravel契约(Contracts)编程方法
如何在IIS中新建站点并解决端口绑定冲突?
Bootstrap整体框架之CSS12栅格系统
Laravel Eloquent性能优化技巧_Laravel N+1查询问题解决
Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程
Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理
网站制作壁纸教程视频,电脑壁纸网站?
Python正则表达式进阶教程_复杂匹配与分组替换解析
如何登录建站主机?访问步骤全解析
javascript日期怎么处理_如何格式化输出
怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?
如何在IIS中配置站点IP、端口及主机头?
Laravel如何保护应用免受CSRF攻击?(原理和示例)
Windows10怎样连接蓝牙设备_Windows10蓝牙连接步骤【教程】
JavaScript Ajax实现异步通信
如何快速搭建高效简练网站?
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
魔方云NAT建站如何实现端口转发?
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
如何在浏览器中启用Flash_2025年继续使用Flash Player的方法【过时】
如何用PHP工具快速搭建高效网站?
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
如何获取免费开源的自助建站系统源码?
Laravel怎么发送邮件_Laravel Mail类SMTP配置教程
Laravel中的Facade(门面)到底是什么原理
Laravel怎么实现验证码(Captcha)功能
使用spring连接及操作mongodb3.0实例
浅谈redis在项目中的应用
Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道
如何在宝塔面板创建新站点?
制作企业网站建设方案,怎样建设一个公司网站?
香港服务器WordPress建站指南:SEO优化与高效部署策略
php json中文编码为null的解决办法
nginx修改上传文件大小限制的方法


device_ids 表的指针
struct list_head h_list; // 与该驱动程序关联的input handle列表
struct list_head node; // 用于将驱动程序放到 input_handler_list 上
};