Linux Input子系统7

Linux Input子系统7(基于Linux6.6)---总结

一、总体流程

Linux 输入子系统负责管理输入设备(如键盘、鼠标、触摸屏等)及其事件的处理和传递。其主要任务是从硬件设备获取输入事件,并将这些事件传递给用户空间的应用程序,进而驱动用户界面的交互。Linux 输入子系统采用了统一的框架和抽象,使得不同类型的输入设备能够通过相同的接口进行处理。

1.1、输入设备注册

输入设备的管理是通过输入设备模型来实现的。每个输入设备都需要通过驱动程序注册到输入子系统。设备注册的过程通常包括:

  • 设备对象创建:使用 input_allocate_device() 函数分配一个 struct input_dev 结构体,代表一个输入设备。
  • 设置设备属性:设置输入设备支持的事件类型(如键盘、鼠标、触摸屏等)。
  • 设备注册:通过 input_register_device() 函数将设备注册到输入子系统,之后该设备可以开始上报事件。

例如:

struct input_dev *dev = input_allocate_device();
dev->evbit[0] = BIT(EV_KEY);  // 设备支持按键事件
dev->keybit[BIT_WORD(KEY_A)] = BIT(KEY_A);  // 设备支持按下A键
input_register_device(dev);  // 注册设备

1.2、事件类型与事件代码

输入设备通过不同类型的事件上报信息,常见的事件类型包括:

  • EV_KEY:键盘按键事件。
  • EV_REL:相对设备事件,如鼠标移动。
  • EV_ABS:绝对坐标事件,如触摸屏输入。
  • EV_SYN:同步事件,用于标识一组输入事件的结束。

每个事件类型都包含一组特定的事件代码。例如,按键事件会有 KEY_A(表示按下 A 键)作为事件代码。

1.3、输入事件的生成

当输入设备(如键盘或鼠标)产生事件时,驱动程序会通过调用 input_event() 函数将事件发送到内核。每个事件通常包含以下内容:

  • 事件类型(如按键事件、相对移动事件等)。
  • 事件代码(如按下哪个键或鼠标的移动方向)。
  • 事件值(例如按键是按下(1)还是松开(0))。

例如,按下键盘上的 A 键:

input_event(input_dev, EV_KEY, KEY_A, 1);  // 按下A键
input_event(input_dev, EV_SYN, SYN_REPORT, 0);  // 同步事件,通知事件完成

1.4、事件队列与事件分发

Linux 内核通过事件队列来管理输入事件。每个设备都有一个事件队列,输入事件首先被存储在设备的队列中。然后,输入核心会按需将事件从队列中取出并传递到用户空间。

  • 事件队列:每个输入设备都有一个事件队列,用于存储通过 input_event() 上报的事件。
  • 事件传输:事件会被传输到用户空间的应用程序,通常是图形界面系统(如 X11、Wayland)。

1.5、用户空间的事件处理

输入事件传递到用户空间后,图形界面系统(如 X Server、Wayland 或应用程序)负责处理这些事件,最终实现界面的更新或其他交互。

  • 用户空间应用程序通过 /dev/input/eventX 文件接口读取输入事件。
  • 事件通常通过 read() 或通过 poll() 等机制读取。
  • 用户空间程序解析事件并根据事件内容进行相应操作(如界面更新、输入响应等)。

例如,X Server 会处理鼠标和键盘的输入事件,将它们转发到桌面环境或应用程序。

1.6、事件同步

在多个输入设备产生事件时,输入核心通过同步事件(EV_SYN)来确保事件的顺序和正确性。EV_SYN 类型的事件用于通知内核当前一组输入事件已经完成,可以进行下一步处理。

例如,鼠标和键盘的事件可能在同一时刻产生,这时输入核心会等待直到所有事件被处理完,再通过同步事件发送到用户空间。

input_event(input_dev, EV_SYN, SYN_REPORT, 0);  // 同步事件,提交事件

1.7、输入设备的移除与注销

当输入设备不再使用或被移除时,设备驱动需要将其注销,停止接收事件并释放资源。设备注销过程通常通过 input_unregister_device() 函数完成。

input_unregister_device(input_dev);  // 注销设备
input_free_device(input_dev);  // 释放设备

二、详细流程

 

2.1、input子系统本身的注册

 input_init(void)
    class_register(&input_class);        /* sysfs的class下出现input文件夹 */
    input_proc_init();                   /* 初始化input的proc文件系统 */
    register_chrdev(INPUT_MAJOR, "input", &input_fops);    /* 注册字符设备 */

2.2、input的dev注册的流程

 input = input_allocate_device();                 /* 申请一个设备空间 */
......                                           /* 初始化input里面的数据 */
input_register_device(input);                    /* 注册该设备 */
    ......                                       /* 各种填充input里面的变量 */
    device_add(&dev->dev);                      /* 经过此步骤后sysfs的class里面的input下面会多出来一个inputx,里面展示的name等就是前面填充和初始化的 */
    list_add_tail(&dev->node, &input_dev_list);  /* 增加该dev到核心层维护的dev链表中 */
    list_for_each_entry(handler, &input_handler_list, node)    
        input_attach_handler(dev, handler);      /* 尝试匹配handler */ 
            input_match_device(handler, dev)     /* 匹配过程(算法) */   
            handler->connect(handler, dev, id);  /* 匹配上了则连接两者到handle,并创建设备(假设匹配到了evdev) */
            .......                              /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */
            input_register_handle(&evdev->handle);    /* 绑定dev到handle,handler在初始化这里以及绑定了 */
            evdev_install_chrdev(evdev);         /* 把该evdev添加到evdev维护的数组中 */
            device_add(&evdev->dev);             /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */

2.3、input的handler注册流程

 input_register_handler(&evdev_handler);
    input_table[handler->minor >> 5] = handler;            /* 注册该handler类型 */
    list_add_tail(&handler->node, &input_handler_list);    /* 把该handler加入到核心层维护的handler链表 */
    list_for_each_entry(dev, &input_dev_list, node)
	input_attach_handler(dev, handler);                /* 尝试匹配dev */ 
            input_match_device(handler, dev)               /* 匹配过程(算法) */   
            handler->connect(handler, dev, id);            /* 匹配上了则连接两者到handle */
                .......                              /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */
                input_register_handle(&evdev->handle);    /* 绑定dev到handle,handler在初始化这里以及绑定了 */
                evdev_install_chrdev(evdev);         /* 把该evdev添加到evdev维护的数组中 */
                device_add(&evdev->dev);             /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */

 

2.4、事件如何从设备驱动层传递到应用层(以按键事件为例):

 input_report_key(button_dev, KEY_LEFT, !gpio_get_value(S5PV210_GPH0(2)));
    input_event(dev, EV_KEY, code, !!value);
        input_handle_event(dev, type, code, value);
            .....                                            /* 各种分析按键类型等 */
            input_pass_event(dev, type, code, value);
            /* 通过dev->hlist找到handle */
            handle->handler->event(handle, type, code, value);    /* 进而通过handle找到handler以及里面的event函数 */
                .......                                            /* event里面打包数据成input_event格式 */
                evdev_pass_event(client, &event);                  /* 把数据放到环形数组,并通知上层 */  
                client->buffer[client->head++] = *event;      /* 数据放到缓冲区 */
                kill_fasync(&client->fasync, SIGIO, POLL_IN)  /* 异步通知应用层 */
        wake_up_interruptible(&evdev->wait);                  /* 唤醒等待队列 */           

2.5、应用层如何让读取一个事件

 evdev_read
    ......                /* 错误分析 */
    retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);    /* 睡眠,自己会进入等待队列,等待被唤醒 */
    
    /* 执行到这里说明发生了事件,是被上面的等待队列唤醒的 */
    evdev_fetch_next_event(client, &event));
        *event = client->buffer[client->tail++];                    /* 将buffer中数据取走*/
    input_event_to_user(buffer + retval, &event);
        copy_to_user(buffer, event, sizeof(struct input_event));    /* 将数据拷贝到用户空间 */    

2.6、应用层如何打开一个设备

 evdev_open
    evdev = evdev_table[i];                    /* 通过次设备号得到该event,event是在connect中放的 */
    ......                                     /* 申请一个client并初始化,即使是同一个event,打开多次也要申请多个event */
    evdev_attach_client(evdev, client);        /* 把该client加入到该evdev的打开链表中 */
    evdev_open_device(evdev);
        ......                                 /* 判断是不是已经打开了,已经打开的就单纯的把evdev里的open打开次数+1,如果打开次数是0,测需要条用核心层的open */
        input_open_device(&evdev->handle);
            ......                             /* 核心层则通过handlr里面的open计数来打开次数 ,同时该设备也有自己的user打开次数统计*/ 
            dev->open(dev);                    /* 核心层检查发现确实是第一次打开,并且设备驱动层有定义open函数,则继续调用设备驱动层的open函数 */
    nonseekable_open(inode, file);             /* 打开完后,把该打开模式设置为不能使用seek读取(输入只能一包一包读,不能跳着读) */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值