当前位置:首页>Linux>Linux 输入子系统核心层的实现

Linux 输入子系统核心层的实现

  • 2026-10-01 19:44:20
Linux 输入子系统核心层的实现
大家好,我是王鸽,之前文章介绍了Linux 输入子系统的大概的框架流程,Linux input子系统框架,虽然核心层这块不需要我们实现,但是还是有必要看一下代码调用的流程。
硬件中断/轮询   │   ▼设备驱动(Producer)—— drivers/input/*  - 创建并注册:struct input_dev  - 上报事件:input_report_*() + input_sync()   │   ▼Input Core(撮合/转发)—— drivers/input/input.c  - 维护设备/handler 列表  - 匹配:input_match_device()  - 绑定:handler->connect()  - 转发:input_event()   │   ▼事件处理层 Handler(Consumer)—— 以 evdev 为例  - /dev/input/eventX(字符设备接口)  - evdev_event()/evdev_read()/ioctl   │   ▼用户空间:Xorg/Wayland(libinput) 或 直接 read()/libevdev

一、核心层的核心作用

    • 管理输入设备的注册与注销,为设备层提供统一的注册接口;
    • 接收设备层上报的输入事件(如按键按下、鼠标移动),并转发给事件处理层;
    • 维护输入设备的属性(如设备类型、支持的事件类型、按键映射);
    • 为用户空间提供标准的访问接口(/dev/input/eventX)。

    二、核心层关键数据结构

    先熟悉核心层最核心的几个数据结构,理解它们才能看懂流程:

    // 输入设备核心结构体(定义在 include/linux/input.h)struct input_dev {const char *name;          // 设备名称(如 "usb-mouse")const char *phys;          // 物理路径(如 "usb-0000:00:14.0-1/input0")struct input_id id;        // 设备ID(总线类型、厂商ID、产品ID等)unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 支持的事件类型(EV_KEY/EV_REL/EV_ABS等)unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 支持的按键unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 支持的相对坐标(鼠标)unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 支持的绝对坐标(触摸屏)struct list_head node;     // 挂入核心层设备链表的节点struct input_handler *grab; // 独占该设备的handlerstruct mutex mutex;        // 保护设备的互斥锁// 其他成员(事件队列、定时器、电源管理等)};// 输入事件处理者结构体(Handler)struct input_handler {void (*event)(struct input_handle *handle, unsignedint type, unsignedint code, int value); // 事件处理函数int (*connect)(struct input_handler *handler, struct input_dev *dev, conststruct input_device_id *id); // 匹配设备的函数void (*disconnect)(struct input_handle *handle); // 断开匹配的函数struct list_head node;     // 挂入核心层handler链表的节点const struct input_device_id *id_table; // 支持的设备ID表struct list_head h_list;   // 关联的input_handle链表};// 设备与Handler的连接结构体(核心层的“桥梁”)struct input_handle {struct input_dev *dev;     // 关联的输入设备struct input_handler *handler; // 关联的Handlerstruct list_head d_node;   // 挂入input_dev的h_liststruct list_head h_node;   // 挂入input_handler的h_list};

    Linux 输入子系统核心层的代码主要集中在 drivers/input/input.c 文件中,核心流程分为设备注册流程、事件上报流程、设备注销流程三大块。就是围绕这几个函数的解读的。

    三、代表函数分类

    设备注册与注销

    // 注册输入设备intinput_register_device(struct input_dev *dev);// 注销输入设备voidinput_unregister_device(struct input_dev *dev);

    事件处理器注册与注销

    // 注册事件处理器intinput_register_handler(struct input_handler *handler);// 注销事件处理器voidinput_unregister_handler(struct input_handler *handler);
    •   把 handler 放入全局 handler 链表;

    •   反向遍历现有设备,逐个尝试连接(同上)。

    事件上报

    // 核心层事件上报函数voidinput_event(struct input_dev *dev, unsignedint type, unsignedint code, int value);

     对 dev 绑定的每个 handle:调用 handle->handler->event(handle, ...)。

    设备驱动层示例代码(伪代码)

    struct input_dev *input_dev;input_dev = input_allocate_device(); // 分配input_dev// 设置设备属性input_dev->name = "my_input_device";input_dev->id.bustype = BUS_I2C;// 设置支持的事件类型set_bit(EV_KEY, input_dev->evbit);set_bit(KEY_A, input_dev->keybit);// 注册设备input_register_device(input_dev);

    注册设备:input_register_device(struct input_dev *dev)

    大概流程如下

    input_register_device()	list_for_each_entry(handler, &input_handler_list, node)	input_attach_handler()			input_match_device()				handler->connect()
    intinput_register_device(struct input_dev *dev){static atomic_t input_no = ATOMIC_INIT(0);struct input_devres *devres = NULL;struct input_handler *handler;unsigned int packet_size;const char *path;int error;if (dev->devres_managed) {		devres = devres_alloc(devm_input_device_unregister,				      sizeof(struct input_devres), GFP_KERNEL);if (!devres)return -ENOMEM;		devres->input = dev;	}/* Every input device generates EV_SYN/SYN_REPORT events. */	__set_bit(EV_SYN, dev->evbit);/* 将KEY_RESERVED位清零 */	__clear_bit(KEY_RESERVED, dev->keybit);/* 确保dev->evbit中没有设置的位都被清零 */input_cleanse_bitmasks(dev);	packet_size = input_estimate_events_per_packet(dev);	......//初始化定时器init_timer(&dev->timer);        //REP_DELAY和REP_PERIOD是关于重复按键的,如果没有设置就赋值为默认值if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {		dev->timer.data = (long) dev;		dev->timer.function = input_repeat_key;		dev->rep[REP_DELAY] = 250;		dev->rep[REP_PERIOD] = 33;	}if (!dev->getkeycode)//获取键的扫描码		dev->getkeycode = input_default_getkeycode;if (!dev->setkeycode)//设置键的扫描码		dev->setkeycode = input_default_setkeycode;dev_set_name(&dev->dev, "input%ld",//设置内嵌dev的名字		     (unsigned long) atomic_inc_return(&input_no) - 1);	error = device_add(&dev->dev);//将input_dev内嵌的dev注册到sysfsif (error)goto err_free_vals;	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);pr_info("%s as %s\n",		dev->name ? dev->name : "Unspecified device",		path ? path : "N/A");kfree(path);	error = mutex_lock_interruptible(&input_mutex);//上锁if (error)goto err_device_del;list_add_tail(&dev->node, &input_dev_list);//将输入设备挂载到input_dev_list链表上list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);      //遍历事件处理驱动,如果找到匹配的事件处理驱动就将该设备依附上去,//生成一个handle        input_wakeup_procfs_readers();mutex_unlock(&input_mutex);   ..........}EXPORT_SYMBOL(input_register_device);

    其中input_attach_handler(dev, handler);的函数是调用如下:

    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler){const struct input_device_id *id;int error;	id = input_match_device(handler, dev);if (!id)return -ENODEV;error = handler->connect(handler, dev, id);// 匹配成功,调用connectif (error && error != -ENODEV)		pr_err("failed to attach handler %s to device %s, error: %d\n",		       handler->name, kobject_name(&dev->dev.kobj), error);return error;}

    其中handler->connect(handler, dev, id);如果匹配上就调用handler->connect函数指针,这是当事件驱动提供的连接函数,当handler和dev匹配上时被调用,会产生一个handle;

    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);

      例如evdev_handler的connect函数,生成设备节点:

      static struct input_handler evdev_handler = {    // ...    .connect    = evdev_connect,    // ...};staticintevdev_connect(struct input_handler *handler, struct input_dev *dev, conststruct input_device_id *id){    struct evdev *evdev;    int minor;    int dev_no;    int error;    minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);...............    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    evdev->dev = dev;    evdev->handle.dev = dev;    evdev->handle.handler = handler;    evdev->handle.private = evdev;    // 创建设备节点    evdev->devno = input_alloc_device_id();    dev_set_name(&evdev->dev->dev, "event%d", evdev->devno);    cdev_init(&evdev->cdev, &evdev_fops);    cdev_add(&evdev->cdev, MKDEV(INPUT_MAJOR, evdev->devno), 1);    input_register_handle(&evdev->handle);    return 0;} cdev_add(&evdev->cdev, MKDEV(INPUT_MAJOR, evdev->devno), 1);加入到了字符设备中。
      • 核心层作用:  把 dev 放入全局设备链表;

      •   遍历所有 input_handler,用 input_match_device() 按 id_table + 能力位图 匹配;

      •  成功则调用 handler->connect(handler, dev, id),生成 input_handle 绑定二者。

      Input Core(撮合/转发)—— drivers/input/input.c  - 维护设备/handler 列表  - 匹配:input_match_device()  - 绑定:handler->connect()  - 转发:input_event()

      另外

      static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

        |---id = input_match_device(handler, dev);调用如下

      • 整个匹配过程事件驱动占主导,判断设备驱动是否符合事件驱动的要求,如果符合就返回匹配上的struct input_device_id结构体指针;

      • handler->id_table指针可能指向好几个struct input_device_id结构体,只要有一个匹配上就算匹配成功;

      • 匹配中最重要的是事件类型bit位的检查,事情驱动中没有设置的bit位代表事件驱动不关心这个bit位,因为每个事件驱动对应不同类型的输入设备,所以支持的输入事件是有区别的;

      事件上报函数 input_event(),input_handle_event(),input_pass_event()函数

      为什么要说input_event,因为上报事件:input_report_*() + input_sync()最后调用的是input_event。

      staticinlinevoidinput_sync(struct input_dev *dev){    input_event(dev, EV_SYN, SYN_REPORT, 0);}

      而input_event 函数原型是

      voidinput_event(struct input_dev *dev,		 unsigned int type, unsigned int code, int value){unsigned long flags;//判断设备驱动是否支持该输入事件if (is_event_supported(type, dev->evbit, EV_MAX)) {    spin_lock_irqsave(&dev->event_lock, flags);    input_handle_event(dev, type, code, value);    spin_unlock_irqrestore(&dev->event_lock, flags);	}}EXPORT_SYMBOL(input_event);

      static void input_handle_event()函数

      staticvoidinput_handle_event(struct input_dev *dev,			       unsigned int type, unsigned int code, int value)
      传参
      含义
      struct input_dev *dev
      输入设备结构体
      unsigned int type
      输入事件的大类
      unsigned int code
      输入事件的子类
      int value
      输入事件的值

       input_get_disposition()会根据事件的类型返回不同的值。整个函数会对传入的不同的输入事件进行判断和处理,最终是调用input_pass_values()函数将输入事件上报到事件驱动层;

      和input_to_handler函数结合来看

      input_pass_values()函数功能就是遍历和设备匹配的事件驱动,调用事件驱动handler->event方法,把输入事件传到事件驱动层;

      通用事件处理驱动的实现

      事件处理层 Handler(Consumer)—— 以 evdev 为例  - /dev/input/eventX(字符设备接口)  - evdev_event()/evdev_read()/ioctl

      通用事件处理驱动是万能匹配的,简单理解就是无论什么类型的输入设备都能和通用事件处理驱动匹配上,也就是每个输入设备都会有一个对应的/dev/input/eventX设备节点;

      当应用程序执行open("/dev/input/eventX", O_RDWR)时等调用的时候,系统调用经过文件系统一系列操作后就会执行file_operations中的成员函数。

      这些函数会从事件处理层到input core层再到驱动程序的input_dev对应的方法(例如open)依次执行下去。

      代码位于kernel/drivers/input/evdev.c

      static const struct file_operations evdev_fops = {	.owner		= THIS_MODULE,	.read		= evdev_read,	.write		= evdev_write,	.poll		= evdev_poll,	.open		= evdev_open,	.release	= evdev_release,	.unlocked_ioctl	= evdev_ioctl,#ifdef CONFIG_COMPAT	.compat_ioctl	= evdev_ioctl_compat,#endif	.fasync		= evdev_fasync,	.flush		= evdev_flush,	.llseek		= no_llseek,};

      比如evdev_open函数代表注册一个client,此后准备接收数据。

      staticintevdev_open(struct inode *inode, structfile *file){struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);	unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);	unsigned int size = sizeof(struct evdev_client) +					bufsize * sizeof(struct input_event);struct evdev_client *client;int error;	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);// 第二,给evdev_client结构分配内存空间。if (!client)		client = vzalloc(size);if (!client)return -ENOMEM;	client->bufsize = bufsize;	spin_lock_init(&client->buffer_lock);	snprintf(client->name, sizeof(client->name), "%s-%d",			dev_name(&evdev->dev), task_tgid_vnr(current));	client->evdev = evdev;// 第三,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上	evdev_attach_client(evdev, client);	error = evdev_open_device(evdev); // 第四,调用evdev_open_device()if (error)goto err_free_client;file->private_data = client;	nonseekable_open(inode, file);// 第五,设置文件的模式return 0; err_free_client:	evdev_detach_client(evdev, client);	kfree(client);return error;}

      这块代码的解读:

      1.struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev); 得到 struct evdev *evdev 具体是这样子:

      struct evdev {    int open;  // open,当用户open此设备时,open的值加1。    struct input_handle handle; // 包括了匹配的 dev 与 handler    wait_queue_head_t wait; // 等待队列    struct evdev_client __rcu *grab; // 可以为evdev实例指定一个struct evdev_client实例,这样在传递Input消息时就只会传递给这一个struct evdev_client实例,而不会传递给所有的struct evdev_client实例。每open一次就会生成一个struct evdev_client实例。    struct list_head client_list; // 用来把所有的struct client_list实例连接在一起    spinlock_t client_lock; /* protects client_list */    struct mutex mutex; // 同步相关的锁    struct device dev; // dev,用来嵌入到设备模型中。    struct cdev cdev; //    bool exist;  // struct evdev被成功实例化后,exist的值就为true};
      client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);

      2.新建evdev_client对象,结构体如下:

      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 wake_lock wake_lock;bool use_wake_lock;char name[28];struct fasync_struct *fasync;struct evdev *evdev;struct list_head node;int clkid;unsigned int bufsize;struct input_event buffer[];};
      unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev); 
      staticunsignedintevdev_compute_buffer_size(struct input_dev *dev){unsigned int n_events =max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,		    EVDEV_MIN_BUFFER_SIZE);return roundup_pow_of_two(n_events);}

      bufsize成员:表示当前输入设备的输入事件缓冲区最多可以存放事件的个数。

      值得注意的是,输入事件缓冲区的大小不是静态的,这里采用的是基于柔型数组进行动态申请;

      在计算bufsize的时候,进行向上对齐2的幂。这样子在判断的时候可以做一个小的优化。

      上报上来的数据需要一定缓冲,有时候一个设备会上报多个事件,那么需要能够存储多个事件的缓冲区。
      3.将client的evdev指向当前evdev,并且将client挂接到evdev的链表上
      client->evdev = evdev;// 第三,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上	evdev_attach_client(evdev, client);staticvoidevdev_attach_client(struct evdev *evdev,struct evdev_client *client){	spin_lock(&evdev->client_lock);	list_add_tail_rcu(&client->node, &evdev->client_list);	spin_unlock(&evdev->client_lock);}
      一个evdev可以对应着多个client。
      evdev和client 关系图

      4.error = evdev_open_device(evdev); 

      其具体函数

      staticintevdev_open_device(struct evdev *evdev){int retval;	retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;if (!evdev->exist)		retval = -ENODEV;else if (!evdev->open++) {		retval = input_open_device(&evdev->handle);if (retval)			evdev->open--;	}mutex_unlock(&evdev->mutex);return retval;}intinput_open_device(struct input_handle *handle){struct input_dev *dev = handle->dev;int retval;	retval = mutex_lock_interruptible(&dev->mutex);if (retval)return retval;if (dev->going_away) {		retval = -ENODEV;goto out;	}	handle->open++;if (!dev->users++ && dev->open)		retval = dev->open(dev);if (retval) {		dev->users--;if (!--handle->open) {/*			 * Make sure we are not delivering any more events			 * through this handle			 */synchronize_rcu();		}	} out:mutex_unlock(&dev->mutex);return retval;}EXPORT_SYMBOL(input_open_device);

      最后发现input_open_device()的核心是对handle->open和dev->users成员自增,调用dev->open()。

      另外的evdev_read,evdev_poll等函数可以自己追踪一下。

      应用层中的open/close 机制:

      •   第一次有人打开某个 handler 对应的设备文件(如 /dev/input/eventX) → input_open_device() 计数从 0→1 → 调 dev->open()(设备驱动提供,常用于上电/使能中断)。

      •   最后一个关闭 → input_close_device() 计数从 1→0 → 调 dev->close()。

      核心层关键工作流程

      1. 设备注册
        驱动层调用input_register_device()注册设备
      2. 事件处理器匹配
        核心层遍历已注册的input_handler,匹配成功后调用connect()
      3. 设备节点创建
        事件处理层创建/dev/input/eventX设备节点
      4. 事件上报
        驱动层通过input_report_*()上报事件
      5. 事件分发
        核心层调用input_event()将事件分发给所有注册的input_handler
      6. 用户空间处理
        用户程序通过read()从设备节点获取事件

      下篇文章可以讲解一下具体案例按键和触摸屏设备驱动。

      谢谢阅读收藏!

      最新文章

      随机文章