Linux Input子系统2(基于Linux6.6)---框架介绍
一、input输入子系统如何工作?
Linux的输入子系统负责管理和处理所有输入设备(如键盘、鼠标、触摸屏、游戏控制器等)发出的事件。它将硬件设备与操作系统的用户空间应用程序之间建立起了一个接口,使得各种输入设备可以以一致的方式与系统交互。理解Linux输入子系统如何工作,需要从几个关键点入手:设备驱动、事件传递、设备节点和与用户空间的交互。
1.1、输入设备驱动
每种输入设备(如键盘、鼠标、触摸屏等)都有其对应的设备驱动程序,这些驱动负责与硬件进行直接通信,并将输入事件传递给内核中的输入子系统。每当设备产生一个输入事件(例如按下一个键,移动鼠标,触摸屏上的触控点等),输入驱动程序就会捕获并将其转化为统一的事件格式,传递给内核中的输入子系统。
输入设备的驱动程序通常位于内核空间,处理硬件的底层细节,并将设备的事件转换为标准化的事件。
1.2、输入事件与事件类型
Linux输入子系统的核心任务之一是将硬件产生的事件转化为统一的输入事件。输入事件通过内核传递并通过事件类型进行分类。常见的事件类型包括:
EV_KEY:按键事件。对应于键盘或其他具有按钮的设备(如游戏控制器的按钮按下或释放)。EV_REL:相对位移事件。对应于鼠标或触控板的相对移动(例如鼠标在X轴和Y轴上的位移)。EV_ABS:绝对坐标事件。用于表示绝对位置的设备,如触摸屏、绘图板等。EV_SYN:同步事件。标记一组事件的结束,表示设备状态已更新。EV_MSC:扩展控制事件。例如,滚轮事件、设备状态改变等。
1.3、事件的传递
当设备驱动程序接收到硬件事件时,内核的输入子系统会对这些事件进行处理。大致流程如下:
-
硬件事件生成:硬件设备(如键盘、鼠标、触摸屏等)检测到用户的输入行为(例如,键被按下或鼠标移动),并生成一个物理事件。
-
事件传递到内核:设备的驱动程序通过内核接口将这些事件传递到输入子系统。输入子系统将硬件产生的物理事件转化为标准化的输入事件,并将其放入设备的事件队列中。
-
事件处理:输入事件通过内核的事件调度机制分发到各个正在监听这些事件的设备或进程。这些进程通常是图形界面系统(如X服务器、Wayland)或其他用户空间应用程序。
-
事件传递到用户空间:内核将事件数据通过特定的设备文件(如
/dev/input/eventX)暴露给用户空间。用户空间程序可以读取这些事件并进行处理。
1.4、输入设备文件
在Linux中,输入设备通常以设备文件的形式暴露给用户空间程序。这些文件通常位于/dev/input/目录下,每个输入设备对应一个文件,文件名一般为eventX,其中X是设备编号。例如:
/dev/input/event0:第一个输入设备。/dev/input/event1:第二个输入设备。/dev/input/mouse0:第一个鼠标设备。/dev/input/kbd:键盘设备。
这些设备文件允许用户空间的应用程序(例如图形界面系统或控制台应用)通过标准的文件I/O接口(如open()、read()、write()等)来访问输入事件。
1.5、用户空间的输入事件处理
用户空间程序(如X服务器或Wayland)通过读取设备文件来获取输入事件。它们使用这些事件来更新显示、响应用户输入并触发相应的操作。常见的应用程序如图形界面和游戏程序也会监听这些设备文件来接收用户的输入行为。
1.6、与用户空间的交互
-
evdev驱动:evdev(Event Device)驱动是Linux输入子系统的一个重要组成部分,它是输入设备和用户空间之间的桥梁。evdev驱动通过设备文件将输入事件传递给用户空间。 -
xinput命令:xinput是一个常用的工具,用于列出、配置、测试和管理输入设备。用户可以使用它来查看设备的属性、禁用设备或调整设备设置。 -
udev设备管理:udev是Linux的设备管理器,负责动态创建设备节点。当一个新的输入设备被连接到系统时,udev会自动创建设备文件(例如/dev/input/eventX),并将设备的相关信息传递给用户空间应用程序。
1.7、设备的管理和配置
-
设备节点创建:当一个输入设备连接到系统时,
udev会自动创建相应的设备文件。例如,插入一个新的USB鼠标,udev会在/dev/input/目录下创建一个新的eventX文件,并将设备的相关信息(如设备类型、名称)传递给用户空间程序。 -
权限管理:输入设备的设备文件(如
/dev/input/eventX)通常需要设置适当的权限,以便特定用户可以读取或写入事件数据。udev可以根据设备的属性(如设备类型、供应商ID等)自动设置这些权限。 -
设备配置:用户可以通过工具(如
xinput、evdev等)配置输入设备的行为。例如,禁用某些设备、调整设备的灵敏度或响应速度等。
1.8、举例说明
例如以一次鼠标按下事件为例子来说明input输入子系统的工作过程:
设备驱动层:下鼠标左键的时候就会触发中断,就会去执行中断所绑定的处理函数,在函数中就会去读取硬件寄存器来判断按下的是哪个按键和状态 ---->
将按键信息上报给input core层 ---> input core层处理好了之后就会上报给input event层,input event层会将我们的输入事件封装成一个input_event结构体放入一个缓冲区中 --->
应用层read就会将缓冲区中的数据读取出去。
此外,还存在一个返回路径 (return path)。返回路径允许给一个键盘设置 LED,给一个 force feedback joystick提供 motion commands。路径的两个方向(指从内核到用户的方向和从用户到内核的方向)使用相同的 event定义和不同的 type identifier。
二、input层次结构

1.一个dev可以对应不只一个handler,比如一个mouse设备既可以匹配到mousedev里面的handler又可以匹配到evdev里面的handler,对应于上图的dev1,一个dev对应两个handler就必须有两个handle来连接它们。
2.一个dev设备也可以只对应一个handler,比如我们自行定义的输入设备就只让匹配evdev里面的handler。
更进一步理解Input 子系统,需要理解以下4个对象:
2.1、Input_device
Input_device 代表一个输入设备,如键盘、鼠标、触摸屏等。它是输入子系统中的一个核心对象,负责接收和存储与物理输入设备相关的信息,并将输入事件传递到内核中。
作用:
- 注册与管理:每个输入设备在内核启动时都会被注册为一个
input_device,并且每个input_device都有一个唯一的标识符(如设备路径)。这些设备可以是通过物理连接(如USB、PS/2)或虚拟设备(如evdev虚拟键盘)进行的。 - 事件生成:每个
input_device可以产生与其相关的事件(如按键按下、鼠标移动等),这些事件会被提交到输入子系统,并最终传递到用户空间。 - 设备属性:
input_device还包含与设备相关的属性信息,如设备类型(键盘、鼠标、触摸屏等)、设备的状态、设备能力(如是否支持鼠标滚轮、触摸板的多点触控)等。
主要数据结构:
在内核中,input_device 通常由 struct input_dev 结构体表示。它包含了该设备的属性、事件处理函数以及设备与内核中其他组件之间的交互方式。
include/linux/input.h
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;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
ktime_t timestamp[INPUT_CLK_MAX];
bool inhibited;
};
2.2、Handler
Handler 是一个事件处理程序,它负责处理从输入设备发来的输入事件。每个 input_device 通常会注册一个或多个 handler 来处理输入事件。这些 handler 可能会直接处理事件,或将事件转发给其他组件(如图形界面、游戏引擎等)。
作用:
- 事件响应:当一个
input_device生成一个事件时,handler负责处理该事件,可能包括执行某个操作、更新设备状态或传递给更高层的用户空间程序。 - 注册与卸载:输入子系统通过注册
handler来管理不同的事件类型。例如,对于一个键盘设备,可能会有多个handler处理按键事件。 - 事件调度:事件可能会被多个
handler处理,handler会决定哪些事件需要被处理,哪些事件应该被丢弃或转发。
主要数据结构:
handler 通常是一个函数指针,指向一个回调函数。当输入设备生成事件时,内核会调用注册的 handler 来处理这些事件。
include/linux/input.h
struct input_handler {
void *private; /* 用户根据具体驱动存放的私有数据 */
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); /* 用于向上层上报输入事件的函数 */
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev); /* 函数用来匹配handler 与 input_dev 设备 */
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); /* 当handler 与 input_dev 匹配成功之后用来连接 */
void (*disconnect)(struct input_handle *handle); /* 断开handler 与 input_dev 之间的连接 */
void (*start)(struct input_handle *handle);
const struct file_operations *fops;
int minor; /* 该handler 的编号 (在input_table 数组中用来计算数组下标) input_table数组就是input子系统用来管理注册的handler的一个数据结构 */
const char *name;
const struct input_device_id *id_table; /* 里面放置着dev和本handler能匹配在一起的信息 */
struct list_head h_list; /* 用来挂接handler 上连接的所有handle 的一个链表头 */
struct list_head node; /* 作为一个链表节点挂接到 input_handler_list 链表上(input_handler_list 链表是一个由上层handler参维护的一个用来挂接所有注册的handler的链表头) */
};
2.3、Handle
Handle 是指向一个特定 handler 的指针,它用于在输入事件中传递并执行 handler。在实际使用中,handle 是一种轻量级的引用机制,用来关联特定的输入事件与处理逻辑。
作用:
- 事件处理绑定:
handle将input_device与handler绑定起来,当事件发生时,内核通过handle查找并调用对应的handler。 - 多种事件的管理:一个设备可以有多个
handler处理不同类型的事件,通过不同的handle来指定事件应当由哪个处理程序处理。
主要数据结构:
handle 主要是一个指针,指向与事件相关的 handler,它在内部事件处理机制中起到标识和引用的作用。具体实现中,它可能是一个结构体字段,用于标识具体的事件处理逻辑。
include/linux/input.h
struct input_handle {
void *private; /* handle 的私有数据 */
int open; /* 这个也是用来做打开计数的 */
const char *name;
struct input_dev *dev; /* 用来指向该handle 绑定的input_dev 结构体 */
struct input_handler *handler; /* 用来指向该handle 绑定的 handler 结构体 */
struct list_head d_node; /* 把它对应的dev里面的hlist与该handle绑定上 */
struct list_head h_node; /* 把它对应的handler里面的hlist与该handle绑定 */
};
2.4、Client
Client 代表了一个用户空间应用或内核模块,它通过输入子系统与输入设备交互。客户端可以注册自己以接收输入设备的事件,从而在应用层处理输入事件。
作用:
- 接收事件:
Client通常通过打开/dev/input/eventX设备文件来注册自己,接收输入设备的事件。 - 事件响应:当输入设备产生事件时,
Client将从设备文件中读取这些事件并进行处理。例如,X服务器或Wayland服务器会监听输入事件并根据这些事件更新图形界面。 - 事件过滤和处理:
Client可以选择过滤某些事件(如不关注某些按键或鼠标事件),并根据需要作出响应。
主要数据结构:
在内核中,Client 可能对应于用户空间进程的描述符或与设备文件相关的结构(如 struct file)。Client 可以通过 open() 打开输入设备文件,并通过 read() 或 poll() 等系统调用来获取事件。
drivers/input/evdev.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 */
wait_queue_head_t wait;
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
enum input_clock_type clk_type;
bool revoked;
unsigned long *evmasks[EV_CNT];
unsigned int bufsize;
struct input_event buffer[];
};
input子系统的核心层维护着两条中要的链表:
static LIST_HEAD(input_dev_list); /* 记录所有的输入设备 */
static LIST_HEAD(input_handler_list); /* 记录所有的事件驱动 */
每当一个新的设备或者一个新的事件驱动被系统加载(调用input_register_device()或 input_register_driver()),都会扫描整个链表,并调用函数input_match_device(struct input_handler *handler, struct input_dev *dev) 尝试配对工作。Input_handler-->id_table 记录了需要匹配的特征。
2624

被折叠的 条评论
为什么被折叠?



