Linux Input子系统6(基于Linux6.6)---数据传输流程介绍
一、概述
在 Linux 输入子系统中,输入设备(如键盘、鼠标、触摸屏等)通过内核上报数据的流程涉及多个步骤,包括设备的注册、事件的产生、事件的传输和最终传递到用户空间。以下是输入数据上报的详细传输流程:
1.1、输入设备的注册
在 Linux 中,每个输入设备都需要通过驱动程序注册到输入子系统,以便系统能够识别并处理来自设备的事件。设备的注册通常在驱动的初始化函数中进行,步骤如下:
- 设备对象创建:通过
input_allocate_device()
分配一个输入设备对象input_dev
。 - 设置设备的属性:配置输入设备支持的事件类型(如按键、鼠标移动等)以及相关的设备属性。
- 注册设备:通过
input_register_device()
函数将设备注册到输入子系统。
注册之后,输入设备会被内核管理,并且可以开始接收来自硬件的输入事件。
struct input_dev *input_device = input_allocate_device();
input_device->evbit[0] = BIT(EV_KEY); // 按键事件
input_device->keybit[BIT_WORD(KEY_A)] = BIT(KEY_A); // 例如按下键盘的A键
input_register_device(input_device);
1.2、输入事件的生成
当物理输入设备(如键盘、鼠标等)产生事件时,设备驱动程序会调用 input_event()
函数将事件数据传递到内核。输入事件可以是不同类型的(如按键、鼠标移动、触摸等),并且每个事件包含以下信息:
- 事件类型:例如按键事件、坐标事件等。
- 事件代码:例如按下某个键或鼠标移动的方向。
- 事件值:例如按键的状态(按下或松开),鼠标的移动量等。
input_event(input_device, EV_KEY, KEY_A, 1); // 按下A键
input_event(input_device, EV_SYN, SYN_REPORT, 0); // 同步事件,通知内核事件的结束
1.3、事件的处理与传输
当输入事件通过 input_event()
被记录后,Linux 内核会将这些事件放入事件队列中,并根据事件类型进行分发。
- 事件队列:输入事件首先被放入内核中的事件队列,每个输入设备有一个事件队列,内核会定期从设备队列中取出事件并进行处理。
- 输入核心的处理:内核通过输入核心模块对这些事件进行分发,决定如何处理这些事件。输入核心会将事件转发给相应的用户空间程序(如 X11、Wayland 或其他图形界面系统)。
1.4、用户空间的事件处理
输入核心将事件从内核空间传递到用户空间的应用程序,通常是图形界面系统(如 X Server、Wayland),它们负责处理输入事件并更新界面。输入事件的传输通常通过以下几种方式完成:
-
/dev/input 设备:在 Linux 中,每个输入设备(如键盘、鼠标)通常会在
/dev/input/
目录下创建一个设备文件(如/dev/input/eventX
),用户空间的应用程序可以通过读取这些设备文件获取输入事件。 -
事件通知机制:除了直接读取设备文件,用户空间还可以通过
select()
或poll()
等系统调用监听输入设备的事件。当有新的事件到来时,应用程序会被通知并处理这些事件。
1.5、用户空间应用处理事件
用户空间的应用程序(例如 X Server、Wayland、GUI 应用程序等)会使用输入子系统提供的接口读取并处理输入事件。通常,事件处理流程如下:
- 读取事件:用户空间应用通过读取
/dev/input/eventX
设备文件,获取输入事件数据。 - 解析事件:读取到的原始事件数据包含事件类型、事件代码和事件值,应用程序根据事件类型和代码对事件进行解析和处理。
- 更新界面:根据输入事件(如鼠标移动、按键输入等),应用程序更新界面或执行其他相应操作。
1.6、事件的传输与同步
在多个设备和事件源同时存在的情况下,输入事件的传输和同步尤为重要。输入核心会处理事件的同步,以确保事件按顺序传输。EV_SYN
事件(同步事件)用来标识一组事件的结束,通知系统应该将当前的输入事件队列提交到上层应用程序。
例如,当一个设备的多个事件发生时(如鼠标的移动和按钮点击),系统会在事件队列中记录这些事件,并通过 EV_SYN
来通知输入核心完成当前一组事件的传输。
input_event(input_device, EV_SYN, SYN_REPORT, 0); // 同步事件
二、详细分析
2.1、input_event
input_report_key函数来上报键值和按键状态,分析一下它的上报流程。
可以看系统提供了很多来上报各种信息的函数(适用,键盘,鼠标,触摸屏等各种上报信息)
它们都是掉用同一的接口input_enent来实现的,所以input_event是分析的重点。
include/linux/input.h
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value); /* 注意这里value是两次取反,即输入给input_event的只可能是0或1 */
}
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_REL, code, value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_FF_STATUS, code, value);
}
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_SW, code, !!value);
}
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
static inline void input_mt_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_MT_REPORT, 0);
}
看一下input_event的实现。
drivers/input/input.c
/**
* input_event() - report new input event
* @dev: device that generated the event
* @type: type of the event
* @code: event code
* @value: value of the event
*
* This function should be used by drivers implementing various input
* devices to report input events. See also input_inject_event().
*
* NOTE: input_event() may be safely used right after input device was
* allocated with input_allocate_device(), even before it is registered
* with input_register_device(), but the event will not reach any of the
* input handlers. Such early invocation of input_event() may be used
* to 'seed' initial state of a switch or initial position of absolute
* axis, etc.
*/
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
/*调用 is_event_supported()函数检查输入设备是否支持该事件*/
if (is_event_supported(type, dev->evbit, EV_MAX)) {
/* 调用 spin_lock_irqsave()函数对将事件锁锁定 */
spin_lock_irqsave(&dev->event_lock, flags);
/* add_input_randomness()函数对事件发送没有一点用处,只是用来对随机数熵池增加一些贡献,因为按键输入是一种随机事件,所以对熵池是有贡献的 */
add_input_randomness(type, code, value);
/* 函数来继续输入子系统的相关模块发送数据。该函数较为复杂,下面单独进行分析 */
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
上面用到的两个工具函数
/* 测试addr数组中第nr位是不是为1,是则返回1,不是则返回0 */
static __always_inline int test_bit(unsigned int nr, const unsigned long *addr)
{
return ((1UL << (nr % BITS_PER_LONG)) &
(((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0;
}
static inline int is_event_supported(unsigned int code,
unsigned long *bm, unsigned int max)
{
return code <= max && test_bit(code, bm);
}
2.2、input_handle_event
按键上报的处理函数。
drivers/input/input.c
#define INPUT_IGNORE_EVENT 0
#define INPUT_PASS_TO_HANDLERS 1
#define INPUT_PASS_TO_DEVICE 2
#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
/* 定义了一个 disposition 变量,该变量表示使用什么样的方式处理事件。此处初始化为 INPUT_IGNORE_EVENT,表示如果后面没有对该变量重新赋值,则忽略这个事件 */
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = 1;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case SYN_MT_REPORT:
dev->sync = 0;
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
/* 函数判断是否支持该按键,同时判断按键状态是否改变过 */
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) { /* 判断value是不是改变过,比如某个键长按,则它在按下过程一致是1 */
/* EV_KEY正常是不会有0和1之外的value值的 */
if (value != 2) {
__change_bit(code, dev->key); /* 把key中code位取反 */
if (value)
/* 如果刚按下键,则开始重复上报数据(具体报不报还要看dev的配置) */
input_start_autorepeat(dev, code);
else
/* 松开按键则停止重复上报 */
input_stop_autorepeat(dev);
}
disposition = INPUT_PASS_TO_HANDLERS; /* 更新disposition */
}
break;
case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) {
__change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX)) {
if (test_bit(code, input_abs_bypass)) {
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
value = input_defuzz_abs_event(value,
dev->abs[code], dev->absfuzz[code]);
if (dev->abs[code] != value) {
dev->abs[code] = value;
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS;
break;
case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL;
break;
case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) {
__change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) {
if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_REP:
if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
disposition = INPUT_PASS_TO_ALL;
break;
case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
/* 处理 EV_SYN 事件,这里并不对其进行关心。*/ */
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = 0;
/* 一些特殊事件需要对dev也上报(比如led点灯等),通常我们的dev里面不会设置event事件 */
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
/* 真正的向上层handler上报事件 */
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}
上报事件的处理(这里主要是区分是固定给某个handler上报,还是查询目前打开着event的就上报)
/*
* Pass event first through all filters and then, if event has not been
* filtered out, through all open handles. This function is called with
* dev->event_lock held and interrupts disabled.
*/
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handler *handler;
struct input_handle *handle;
rcu_read_lock();
/* grab 是强制为 input device 绑定的 handler,如果存在就直接调用绑定的handler里面的event即可 */
handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else {
bool filtered = false;
/* 如果没有为device绑定具体的handle,则遍历这个dev上的所有handle,同时会向应用层已经 open打开过的发送具体的事件信息,
handle里面的open是做打开次数统计的,非0即有应用程序打开。这里要注意的是向所有的打开的都发送事件 */
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue;
handler = handle->handler;
if (!handler->filter) { /* 看handler要不要进行再次筛选,通常evdev和mousedev等都是不需要进行筛选了 */
if (filtered)
break;
/* 真正的调用handler里面的event向应用程序上报数据 */
handler->event(handle, type, code, value);
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
}
rcu_read_unlock();
}
2.3、handler层上报
具体的handler(以evdev为例)层的上报。
/*
* Pass incoming event to all connected clients.
*/
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private; /* evdev本身绑定在handle里面的private */
struct evdev_client *client;
struct input_event event;
struct timespec ts;
/* 输入信息打包成标准格式 */
ktime_get_ts(&ts);
event.time.tv_sec = ts.tv_sec;
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
event.type = type;
event.code = code;
event.value = value;
rcu_read_lock();
/* 如果该evdev有个专用的client,那么就将事件发给它如果该evdev不存在专用的client,那个就把该事件发送给evdev上client_list链表上所有的client */
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event); /* 打包数据 */
rcu_read_unlock();
/* 唤醒等到队列上的进程,注意这里是唤醒,肯定有别的地方定义(connect)和让它睡眠(read),注意我会标记的 */
wake_up_interruptible(&evdev->wait);
}
2.4、异步通知
使用异步通知通知上层。
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/*
* Interrupts are disabled, just acquire the lock
*/
spin_lock(&client->buffer_lock);
wake_lock_timeout(&client->wake_lock, 5 * HZ); /* 设置一个5s的超时锁,5s后自动解锁,超时后内核可以进入休眠模式(即5s没输入则进入休眠) */
client->buffer[client->head++] = *event; /* 把键值写入client缓冲队列中,方便app层读(app层可能一次读多个输入信息) */
client->head &= EVDEV_BUFFER_SIZE - 1;
spin_unlock(&client->buffer_lock);
if (event->type == EV_SYN)
kill_fasync(&client->fasync, SIGIO, POLL_IN); /* 发送一个异步通知,通知该打开该client的应用程序,执行信号处理函数*/
上面就是所有的从按键发生到按键事件发生给应用程序的整个流程。