Linux input输入子系统

Linux input

更多内容可以查看我的github

Linux输入子系统框架

Linux输入子系统由驱动层、核心层、事件处理层三部分组成。

  • 驱动层:输入设备的具体驱动程序,负责与具体的硬件设备进行交互,并将底层的硬件输入转化为统一的事件形式,向核心层发送
  • 核心层:连接驱动层和事件处理层,负责对下层提供驱动层借口,向上层提供事件处理接口
  • 事件处理层:负责对输入事件进行处理,并将处理结果传递给应用程序
  • 层的设备抽象出对应的接口提供给应用层。将底层设备的触发的事件通过这个接口传达给应用层。

核心层的代码在 linux/drivers/input/input.c 中实现

重要结构体

input_dev

这个结构体属于驱动层,描述了一个具体的input设备,记录相关的硬件信息,事件位图等,

c 复制代码
struct input_dev {
	const char *name;       // 设备名称
	struct input_id id;     // 设备id,存储输入设备的总线、厂商等信息
    ...
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     // 支持事件类型
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   // 按键位图
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   // 相对位移位图
    ...
    struct list_head	h_list;     // 内核链表头
	struct list_head	node;       // 内核链表节点
};

Linux设备支持的事件类型:

事件类型 编码 事件描述
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件(鼠标,键盘等)
EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC 0x04 其它
EV_SW x05 开关
EV_LED 0x11 按键/设备灯
EV_SND 0x12 声音/警报
EV_REP 0x14 重复
EV_FF x15 力反馈
EV_PWR 0x16 电源
EV_FF_STATUS 0x17 力反馈状态
EV_MAX 0x1f 事件类型最大个数和提供位掩码支持
input_handler

这个结构体属于事件处理层,描述一个事件处理器

c 复制代码
struct input_handler {

	void *private;
    // 事件处理函数
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    
    // 设备匹配函数
	bool (*match)(struct input_handler *handler, struct input_dev *dev);

    // 设备连接函数,匹配成功后连接
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

    // 设备支持列表
	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};
input_handle

这个结构体属于核心层,描述一个配对的input设备和input设备处理器

c 复制代码
struct input_handle {

	void *private;
    
	int open;               // 打开标志
	const char *name;       // 名称

	struct input_dev *dev;
	struct input_handler *handler;

	struct list_head	d_node;
	struct list_head	h_node;
};
input_handle_list
c 复制代码
struct input_handle_list {
	struct list_head	list;
	struct input_handle	*first;
	struct input_handle	*last;
};
两条重要链表

在 input.c 中,全局维护了两条重要的链表,分别是输入设备链表和事件处理器链表

c 复制代码
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);
总结

上面结构体的链表的关系如下面两图所示(这个图好难画-.-,就在网上找了一个,原文连接

当我们使用input_register_device()注册一个设备的时候,就会将设备添加到input_dev_list链表中,同时便利input_handler_list进行匹配,匹配成功就会调用input_handler->connect()函数进行连接

事件相关结构体

input_event

事件的输入就是以一个input_event为基本单位的

c 复制代码
struct input_event {
	struct timeval time;  /* 事件发生的时间 */
	__u16 type;           /* 事件总类型 */
	__u16 code;           /* 事件子类型 */
	__s32 value;          /* 事件的值 */
};
evdev_client
c 复制代码
struct evdev_client {
	unsigned int head;
	unsigned int tail;
	unsigned int packet_head; /* [future] position of the first element of next packet */
	spinlock_t buffer_lock; /* protects access to buffer, head and tail */
	struct fasync_struct *fasync;
	struct evdev *evdev;
	struct list_head node;
	int clk_type;
	bool revoked;
	unsigned int bufsize;
	struct input_event buffer[];
};
  • head:表示客户端缓冲区中下一个要读取的事件的索引。当客户端从缓冲区读取事件时,它会从 buffer[head] 开始读取,并递增 head 的值。因此,head 指向的是最老的未读取事件的位置。
  • tail:表示客户端缓冲区中下一个要写入的事件的索引。当输入事件到达并需要被缓冲时,它将被写入到 buffer[tail] 的位置,并递增 tail 的值。因此,tail 指向的是最新的未写入事件的位置。

实际上,evdev_client 实现了一个环形队列,head是头指针,tail是尾指针,这两个指针都是以 input_event 为单位移动的。

packet_head 与 head 和 tail 不同, 它以数据包(多个input_event)为单位,主要负责记录缓冲区的入口偏移量。

buffer 就是循环队列数组,即缓冲区

所以,根据这些变量我们可以知道,当循环队列满的时候,head = tail;当循环队列空的时候,packet_head = tail

evdev
c 复制代码
struct evdev {
	int open;   // 设备打开计数
	struct input_handle handle; 
	wait_queue_head_t wait;     // 等待队列,没有事件时进程睡眠
	struct evdev_client __rcu *grab;    // 事件响应

    // 客户端链表,可以有多个进程访问设备
	struct list_head client_list;
	spinlock_t client_lock; /* protects client_list */
	struct mutex mutex;
	struct device dev;
	struct cdev cdev;
	bool exist;
};
总结
  • input_event: 表示一个输入事件
  • evdev_client: 表示一个用户空间的应用程序或实体设备与输入设备之间的连接
  • evdev: 输入设备驱动程序的接口实现,应用程序可以通过evdev与evdev_client之间的交互,实现输入事件的读取和输入

流程

这部分建议阅读源码

在输入设备驱动(input_dev)中,一般通过轮询或者中断方式获取事件的原始值,经过一些处理后,通过input_event()函数将数据上报给核心层(input_core)。

在核心层中,通过input_handle_event()input_pass_values()对数据进行处理(type、code、value),然后使用input_to_handler()函数将数据上报给事件处理层(input_handler),在input_to_handler()中,使用input_handler结构体中的事件处理函数(event、events、filter)上报,这些函数可以在evdev.c的1235行的evdev_handler中找到。

在事件处理层中,通过evdev_events()evdev_pass_values()为事件加上时间戳,完成了一个完整的input_event,然后使用__pass_event()将事件传递给用户空间(evdev_client的buffer中)

__pass_event()中,事件input_event会被填充到evdev_clientbuffer中。对于用于空间的应用程序,可以通过read()函数调用内核空间的evdev_read()函数,然后调用evdev_fetch_next_event()函数从环形缓冲区中读取input_event事件

相关推荐
2301_1472583696 分钟前
7月2日作业
java·linux·服务器
GodKK老神灭3 小时前
STM32 CCR寄存器
stm32·单片机·嵌入式硬件
xuanzdhc5 小时前
Linux 基础IO
linux·运维·服务器
愚润求学5 小时前
【Linux】网络基础
linux·运维·网络
bantinghy5 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志6 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手6 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器