Linux Input子系统6

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 应用程序等)会使用输入子系统提供的接口读取并处理输入事件。通常,事件处理流程如下:

  1. 读取事件:用户空间应用通过读取 /dev/input/eventX 设备文件,获取输入事件数据。
  2. 解析事件:读取到的原始事件数据包含事件类型、事件代码和事件值,应用程序根据事件类型和代码对事件进行解析和处理。
  3. 更新界面:根据输入事件(如鼠标移动、按键输入等),应用程序更新界面或执行其他相应操作。

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的应用程序,执行信号处理函数*/

上面就是所有的从按键发生到按键事件发生给应用程序的整个流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值