嵌入式Linux输入系统应用编程学习总结

嵌入式Linux输入系统应用编程学习总结

目录

  • 嵌入式Linux输入系统应用编程学习总结
    • 1. 嵌入式Linux输入系统介绍
    • 2. Linux设备输入数据的几个结构体
      • 2.1 内核中表示一个输入设备的结构体
      • 2.2 APP从输入设备获取的数据类型结构体
    • 3. 查看LCD设备信息和输入数据
      • 3.1 查看设备信息
      • 3.2 使用命令读取输入的数据
      • 3.3 4种机制获取LCD设备输入数据
        • 3.3.1 使用ioctl获取设备信息
        • 3.3.2 查询方式(Polling)和休眠唤醒方式(Blocking I/O)获取设备输入的信息
        • 3.3.3 poll方式(Polling with poll)获取设备输入的信息
        • 3.3.4 selsct方式(Polling with select)获取设备输入的信息
        • 3.3.5 异步通知方式(Asynchronous I/O)获取设备输入的信息

基于韦东山IMX6ULL开发板和配套资料学习

参考教程:韦东山老师教程

1. 嵌入式Linux输入系统介绍

嵌入式Linux输入系统是处理来自各种输入设备(如键盘、鼠标、触摸屏等)事件的核心组件。在嵌入式系统中,输入子系统的设计需要特别考虑资源限制、实时性要求等因素。

从应用层到硬件最底层分为:用户空间(应用层)、内核空间(输入系统事件层、输入系统核心层、输入系统驱动层)、硬件(如键盘、鼠标、触摸屏等):

在这里插入图片描述

假设用户程序直接访问/dev/input/event0 设备节点,或者使用 tslib 访问设备节点的流程如下:

  • APP 发起读操作,若无数据则休眠
  • 用户操作设备,硬件上产生中断
  • 输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报,输入事件就是一个“ struct input_event”结构体
  • 核心层可以决定把输入事件转发给上面哪个 handler 来处理
    • 从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等
    • 最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核 buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件
    • 当 APP 正在等待数据时, evdev_handler 会把它唤醒,这样 APP 就可以返回数据
  • APP 对输入事件的处理
    • APP 获得数据的方法有 2 种:
      • 直接访问设备节点(比如/dev/input/event0,1,2,…)
      • 通过 tslib、 libinput 这类库来间接访问设备节点,这些库简化了对数据的处理

2. Linux设备输入数据的几个结构体

基于编写应用程序的角度,只需要理解这些内容:

2.1 内核中表示一个输入设备的结构体

使用 input_dev 结构体来表示输入设备:

/* include/linux/input.h */
struct input_dev {
    const char *name;                  /* 设备名称 */
    const char *phys;                  /* 设备物理路径 */
    const char *uniq;                  /* 设备唯一标识符 */
    struct input_id 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)];         /* 支持的LED位图 */
    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;                                 /* 力反馈设备 */

    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)];            /* 当前LED状态位图 */
    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;                                  /* 设备资源管理标志 */
};

2.2 APP从输入设备获取的数据类型结构体

可以得到一系列的输入事件,就是一个一个“ struct input_event”:

/* include/uapi/linux/input.h */
struct input_event {
    struct timeval time;  /* 事件发生的时间戳 */
    __u16 type;           /* 事件类型 */
    __u16 code;           /* 事件代码 */
    __s32 value;          /* 事件值 */
};

/* include/uapi/linux/time.h */
struct timeval {
    __kernel_time_t tv_sec;  /* 秒 */
    __kernel_suseconds_t tv_usec;  /* 微秒 */
};

其中事件类型type:

/* include/uapi/linux/input-event-codes.h */
/*
 * Event types
 */

#define EV_SYN			0x00			//同步事件。用于分隔事件序列,确保事件的完整性和顺序。通常用于表示一组事件的结束,或者用于同步多个事件。
#define EV_KEY			0x01            //按键事件。表示按键的按下、释放或保持。用于键盘、按钮等设备,表示按键的状态变化。
#define EV_REL			0x02            //相对轴事件。表示相对位置的变化。用于鼠标、滚轮等设备,表示相对移动。
#define EV_ABS			0x03            //绝对轴事件。表示绝对位置的变化。用于触摸屏、触摸板等设备,表示绝对位置。
#define EV_MSC			0x04            //多用途事件。表示其他类型的事件。用于表示一些特殊或不常见的事件。
#define EV_SW			0x05            //开关事件。表示开关状态的变化。用于表示开关的状态变化,如笔记本电脑的盖子开关。
#define EV_LED			0x11            //LED事件。表示LED状态的变化。用于表示LED的状态变化,如键盘上的Caps Lock灯。
#define EV_SND			0x12            //声音事件。表示声音状态的变化。用于表示声音的状态变化,如铃声。
#define EV_REP			0x14            //重复事件。表示按键的重复。用于表示按键的重复事件,如长按某个键时的重复输入。
#define EV_FF			0x15            //力反馈事件。表示力反馈效果。用于表示力反馈设备的效果,如游戏手柄的震动。
#define EV_PWR			0x16            //电源事件。表示电源状态的变化。用于表示电源状态的变化,如电池电量低。
#define EV_FF_STATUS		0x17        //力反馈状态事件。表示力反馈效果的状态。用于表示力反馈效果的状态变化。
#define EV_MAX			0x1f            //事件类型的最大值。用于定义事件类型的上限值,常用于数组或位图的大小计算。
#define EV_CNT			(EV_MAX+1)      //事件类型的计数。用于计算事件类型的总数,常用于数组或位图的大小计算。

其中事件代码code:

在这里插入图片描述

  • 键盘事件(EV_KEY)

    • KEY_:表示键盘上的具体按键,如KEY_A表示“A”键,KEY_NUMLOCK表示数字锁定键等。这些按键代码通常定义在/usr/include/linux/input-event-codes.h文件中。

    • BTN_:虽然主要用于按钮类设备,但某些键盘上的特殊按键或功能也可能使用BTN_前缀的代码,如BTN_LEFT(通常用于鼠标,但在某些上下文中可能表示键盘上的特殊功能键)。

  • 鼠标事件

    • REL_:表示相对坐标事件,如REL_XREL_Y分别表示鼠标在X轴和Y轴上的相对移动量,REL_WHEEL表示鼠标滚轮的滚动方向和量。

    • BTN_:表示鼠标按钮的按下和抬起事件,如BTN_LEFT(左键)、BTN_RIGHT(右键)、BTN_MIDDLE(中键/滚轮键)等。

  • 触摸屏事件

    • ABS_:表示绝对坐标事件,如ABS_MT_POSITION_XABS_MT_POSITION_Y分别表示触摸屏上触摸点的X轴和Y轴坐标。多点触控协议中可能还包含其他ABS_类型的代码,如ABS_MT_SLOT(表示多点触控中的触点编号)、ABS_MT_TRACKING_ID(用于跟踪触点的唯一标识符)等。

    • BTN_TOUCH:表示触摸屏上的触摸事件,当触摸发生时,该代码的值会被设置为1。

  • 其他事件类型

    • EV_MSCEV_SWEV_LEDEV_SNDEV_REPEV_FFEV_PWR等事件类型也有各自的code代码,但这些类型通常用于更专业的输入设备或特定功能,如杂项输入(MSC)、开关输入(SW)、LED控制(LED)、声音输出(SND)、自动重复(REP)、力反馈(FF)和电源管理(PWR)等。

其中事件值value:

在这里插入图片描述

  • 按键事件(EV_KEY)

    • 0:表示按键被释放或未按下。

    • 1:表示按键被按下。

    • 2(或其他非0非1值,取决于具体实现):在某些情况下,可能表示按键的长按或其他特殊状态,但这并不是标准的用法,更多依赖于具体设备和驱动程序的实现。

  • 相对坐标事件(EV_REL)

    • 对于REL_XREL_Y等相对坐标事件,value表示在X轴或Y轴上的相对移动量。正值通常表示向右或向下的移动,负值表示向左或向上的移动。

    • 对于REL_WHEEL等滚轮事件,value的正负表示滚轮滚动的方向,而绝对值通常表示滚动的量或步数。例如,value为1可能表示滚轮向上滚动一步,而-1则表示向下滚动一步。

  • 绝对坐标事件(EV_ABS)

    • 对于触摸屏和绝对定位设备,value表示触摸点或设备在X轴、Y轴上的绝对位置,或者表示其他绝对量(如压力、大小等)。

    • 具体的含义取决于code字段的值。例如,ABS_XABS_Y分别表示触摸点在X轴和Y轴上的位置,而ABS_PRESSURE则表示触摸点的压力值。

  • 其他事件类型

    • 对于其他事件类型(如EV_MSC、EV_SW、EV_LED等),value的含义可能因事件类型而异。这些类型的事件通常用于更专业的输入设备或特定功能。

    • 例如,在EV_MSC(杂项事件)中,value可能用于表示设备的特定状态或配置。在EV_SW(开关事件)中,value可能表示开关的打开或关闭状态。

事件之间的界线:

APP 读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、 Y 位置信息,也可能会上报压力值。

APP 怎么知道它已经读到了完整的数据?

驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。同步事件也是一个 input_event 结构体,它的 type、 code、 value 三项都是 0。

输入子系统支持:阻塞、非阻塞、 POLL/SELECT、异步通知的机制。

3. 查看LCD设备信息和输入数据

使用韦东山老师IMX6ULL Pro开发板和配套的LCD屏进行实操。

3.1 查看设备信息

输入设备的设备节点名为/dev/input/eventX(也可能是/dev/eventX, X 表示 0、 1、 2 等数字)。查看设备节点,可以执行以下命令:

ls /dev/input/* -l

ls /dev/event* -l

可以看到下图类似下面的信息:

在这里插入图片描述

可以在板子上执行以下命令,查看这些设备节点对应什么硬件:

cat /proc/bus/input/devices

这条指令的含义就是获取与 event 对应的相关设备信息,可以看到类似以下的结果:

在这里插入图片描述

  • I:id of the device(设备 ID):该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:

    在这里插入图片描述

  • N:name of the device:设备名称

  • P:physical path to the device in the system hierarchy:系统层次结构中设备的物理路径

  • S:sysfs path:位于 sys 文件系统的路径

  • U:unique identification code for the device(if device has it):设备的唯一标识码

  • H:list of input handles associated with the device:与设备关联的输入句柄列表

  • B:bitmaps(位图)

    PROP:device properties and quirks(设备属性)
    EV:types of events supported by the device(设备支持的事件类型)
    KEY:keys/buttons this device has(此设备具有的键/按钮)
    MSC:miscellaneous events supported by the device(设备支持的其他事件)
    LED:leds present on the device(设备上的指示灯)
    

    需要注意的是 B 位图,比如上图中“ B: EV=b”用来表示该设备支持哪类输入事件。 b 的二进制是 1011, bit0、 1、 3 为 1,表示该设备支持 0、 1、 3 这三类事件,即 EV_SYN、 EV_KEY、 EV_ABS。

举一个例子,“ B: ABS=2658000 3”如何理解?

它表示该设备支持 EV_ABS 这一类事件中的哪一些事件。这是 2 个 32 位的数 字: 0x2658000、 0x3, 高位在 前低 位在 后, 组成一 个 64 位 的数字 :“ 0x2658000,00000003”,数值为 1 的位有: 0、 1、 47、 48、 50、 53、 54,即:0、 1、 0x2f、 0x30、 0x32、 0x35、 0x36,对应以下这些宏:

在这里插入图片描述

即这款输入设备支持上述的 ABS_X 、 ABS_Y 、 ABS_MT_SLOT 、 ABS_MT_TOUCH_MAJOR 、 ABS_MT_WIDTH_MAJOR 、 ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 这些绝对位置事件。

3.2 使用命令读取输入的数据

调试输入系统时,直接执行类似下面的命令,然后操作对应的输入设备即可读出数据:

hexdump /dev/input/event0

在开发板上执行上述命令之后,点击按键或是点击触摸屏,就会打印下图信息:

在这里插入图片描述

上图中的 type 为 3 , 对应 EV_ABS ; code 为 0x35 对应 ABS_MT_POSITION_X; code 为 0x36 对应 ABS_MT_POSITION_Y。

上图中还发现有 2 个同步事件:它的 type、 code、 value 都为 0。表示电容屏上报了 2 次完整的数据。

3.3 4种机制获取LCD设备输入数据

读取输入数据的4种方式是:查询方式、休眠唤醒方式、poll方式、异步通知方式。

编程获取LCD设备输入数据之前需要先了解如何获取设备信息。

3.3.1 使用ioctl获取设备信息

ioctl(input/output control)是Linux中一个非常强大且灵活的函数,用于对设备文件的特定操作进行控制。这个函数提供了一种机制,使得用户空间程序能够向设备驱动程序发送控制命令或查询设备状态,而这些操作通常无法通过普通的read/write调用完成。

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

输入参数:

  • fd:文件描述符,指向要控制的设备文件。
  • request:设备特定的请求码,用于告诉驱动程序要执行哪种操作。
  • ...:一个可变参数列表,具体取决于request,可以是整数、指针等。

返回值:

  • 成功时,ioctl返回0;
  • 失败时,返回-1,并设置errno以指示错误类型。

有些驱动程序对 request 的格式有要求,它的格式如下:

在这里插入图片描述

比如 dir 为_IOC_READ(即 2)时,表示 APP 要读数据;为_IOC_WRITE(即 4)时,表示 APP 要写数据。

  • size 表示这个 ioctl 能传输数据的最大字节数。
  • type、 nr 的含义由具体的驱动程序决定。

比如要读取输入设备的 evbit 时, ioctl 的 request 要写为“ EVIOCGBIT(0, size)”, size 的大小可以由你决定:你想读多少字节就设置为多少。这个宏的定义如下:

#define EVIOCGBIT(ev,len)	_IOC(_IOC_READ, 'E', 0x20 + (ev), len)	/* get event bits */

开发板示例代码

#include <linux/input.h>      // 输入设备相关的头文件
#include <sys/types.h>        // 基本系统数据类型
#include <sys/stat.h>         // 文件状态相关函数
#include <fcntl.h>            // 文件控制选项
#include <sys/ioctl.h>        // 输入输出控制函数
#include <stdio.h>            // 标准输入输出函数

/*
 * 程序入口点
 * 参数:
 *   argc: 命令行参数个数
 *   argv: 命令行参数数组
 */
int main(int argc, char **argv)
{
    int fd;                  // 文件描述符
    int err;                 // 错误码
    int len;                 // 读取长度
    int i;                   // 循环变量
    unsigned char byte;      // 用于处理位掩码的字节
    int bit;                 // 位索引
    struct input_id id;      // 输入设备ID结构体
    unsigned int evbit[2];   // 用于存储事件类型位掩码的数组

    // 事件类型名称数组
    char *ev_names[] = {
        "EV_SYN ",   // 同步事件
        "EV_KEY ",   // 按键事件
        "EV_REL ",   // 相对事件
        "EV_ABS ",   // 绝对事件
        "EV_MSC ",   // 其他事件
        "EV_SW ",    // 开关事件
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "EV_LED ",   // LED事件
        "EV_SND ",   // 声音事件
        "NULL ",     // 保留
        "EV_REP ",   // 重复事件
        "EV_FF ",    // 力反馈事件
        "EV_PWR ",   // 电源事件
    };

    // 检查命令行参数
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]); // 打印使用说明
        return -1;
    }

    // 打开指定的输入设备文件
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]); // 打开设备失败
        return -1;
    }

    // 获取输入设备的ID信息
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        printf("bustype = 0x%x\n", id.bustype ); // 打印总线类型
        printf("vendor  = 0x%x\n", id.vendor  ); // 打印厂商ID
        printf("product = 0x%x\n", id.product ); // 打印产品ID
        printf("version = 0x%x\n", id.version ); // 打印版本号
    }

    // 获取设备支持的事件类型位掩码
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: "); // 打印支持的事件类型
        for (i = 0; i < len; i++)
        {
            byte = ((unsigned char *)evbit)[i]; // 获取当前字节
            for (bit = 0; bit < 8; bit++)
            {
                if (byte & (1<<bit)) { // 检查当前位是否为1
                    printf("%s ", ev_names[i*8 + bit]); // 打印对应的事件类型名称
                }
            }
        }
        printf("\n");
    }

    // 关闭设备文件
    close(fd);

    return 0;
}

开发板示例代码分析

使用ioctl获取device ID时,参考驱动程序evdev_do_ioctl,需要一个input_id结构体存放device ID的信息:

在这里插入图片描述

ioctl中需要使用EVIOCGBIT来获取evbit,分析驱动程序evdev_do_ioctl,_IOC_READ读取信息调用handle_eviocgbit函数中的case 0最终获取evbit:

在这里插入图片描述

最后把evbit中读到的数据和ev_names(ev_names中定义的是时间类型type,参考:2.2 APP从输入设备获取的数据类型结构体)中的字符串进行比较,打印出支持的event类型。

上机实验

3.3.2 查询方式(Polling)和休眠唤醒方式(Blocking I/O)获取设备输入的信息
  • 查询方式定义

    • 查询方式:在查询方式中,进程不断地检查设备的状态,以确定是否有数据可以读取或写入。这种方式通常通过循环来实现。
  • 特点

    • 主动检查:进程主动检查设备状态,不依赖于外部事件。

    • CPU占用高:频繁的检查会导致CPU占用率高,不适合高负载应用。

    • 实时性:可以实现较高的实时性,因为不需要等待外部中断。

    • 查询方式读取数据通用示例代码

      #include <unistd.h>
      #include <fcntl.h>
      #include <stdio.h>
      
      int main() {
          int fd = open("/dev/mydevice", O_RDONLY);
          if (fd < 0) {
              perror("open");
              return 1;
          }
      
          char buffer[1024];
          while (1) {
              ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
              if (bytes_read > 0) {
                  // 处理读取的数据
                  printf("Read %zd bytes\n", bytes_read);
              } else if (bytes_read == 0) {
                  // 设备关闭
                  break;
              } else {
                  perror("read");
                  break;
              }
              // 短暂休眠以减少CPU占用
              usleep(1000);
          }
      
          close(fd);
          return 0;
      }
      
  • 休眠唤醒方式定义

    • 休眠唤醒方式:在休眠唤醒方式中,进程在没有数据可读或写时进入休眠状态,等待设备产生中断或数据准备好后再被唤醒。这种方式通常通过阻塞I/O操作实现。
  • 特点

    • 被动等待:进程在没有数据可读或写时进入休眠状态,不占用CPU资源。

    • CPU占用低:只有在数据准备好时才占用CPU资源。

    • 实时性较低:需要等待设备中断或内核调度,实时性不如查询方式。

    • 休眠唤醒方式读取数据通用示例代码示例代码

      #include <unistd.h>
      #include <fcntl.h>
      #include <stdio.h>
      
      int main() {
          int fd = open("/dev/mydevice", O_RDONLY);
          if (fd < 0) {
              perror("open");
              return 1;
          }
      
          char buffer[1024];
          while (1) {
              ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
              if (bytes_read > 0) {
                  // 处理读取的数据
                  printf("Read %zd bytes\n", bytes_read);
              } else if (bytes_read == 0) {
                  // 设备关闭
                  break;
              } else {
                  perror("read");
                  break;
              }
          }
      
          close(fd);
          return 0;
      }
      

开发板示例代码

#include <linux/input.h>      // 输入设备相关的头文件
#include <sys/types.h>        // 基本系统数据类型
#include <sys/stat.h>         // 文件状态相关函数
#include <fcntl.h>            // 文件控制选项
#include <sys/ioctl.h>        // 输入输出控制函数
#include <stdio.h>            // 标准输入输出函数
#include <string.h>           // 字符串操作函数
#include <unistd.h>           // UNIX标准函数

/* 
 * 程序入口点
 * 参数:
 *   argc: 命令行参数个数
 *   argv: 命令行参数数组
 */
int main(int argc, char **argv)
{
    int fd;                  // 文件描述符
    int err;                 // 错误码
    int len;                 // 读取长度
    int i;                   // 循环变量
    unsigned char byte;      // 用于处理位掩码的字节
    int bit;                 // 位索引
    struct input_id id;      // 输入设备ID结构体
    unsigned int evbit[2];   // 用于存储事件类型位掩码的数组
    struct input_event event;// 输入事件结构体
    
    // 事件类型名称数组
    char *ev_names[] = {
        "EV_SYN ",   // 同步事件
        "EV_KEY ",   // 按键事件
        "EV_REL ",   // 相对事件
        "EV_ABS ",   // 绝对事件
        "EV_MSC ",   // 其他事件
        "EV_SW ",    // 开关事件
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "EV_LED ",   // LED事件
        "EV_SND ",   // 声音事件
        "NULL ",     // 保留
        "EV_REP ",   // 重复事件
        "EV_FF ",    // 力反馈事件
        "EV_PWR ",   // 电源事件
    };
    
    // 检查命令行参数
    if (argc < 2)
    {
        printf("Usage: %s <dev> [noblock]\n", argv[0]); // 打印使用说明
        return -1;
    }

    // 检查是否指定了非阻塞模式
    if (argc == 3 && !strcmp(argv[2], "noblock"))
    {
        fd = open(argv[1], O_RDWR | O_NONBLOCK); // 以读写和非阻塞模式打开设备
    }
    else
    {
        fd = open(argv[1], O_RDWR); // 以读写模式打开设备
    }
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]); // 打开设备失败
        return -1;
    }

    // 获取输入设备的ID信息
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        printf("bustype = 0x%x\n", id.bustype ); // 打印总线类型
        printf("vendor  = 0x%x\n", id.vendor  ); // 打印厂商ID
        printf("product = 0x%x\n", id.product ); // 打印产品ID
        printf("version = 0x%x\n", id.version ); // 打印版本号
    }

    // 获取设备支持的事件类型位掩码
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: "); // 打印支持的事件类型
        for (i = 0; i < len; i++)
        {
            byte = ((unsigned char *)evbit)[i]; // 获取当前字节
            for (bit = 0; bit < 8; bit++)
            {
                if (byte & (1<<bit)) { // 检查当前位是否为1
                    printf("%s ", ev_names[i*8 + bit]); // 打印对应的事件类型名称
                }
            }
        }
        printf("\n");
    }

    // 无限循环读取并处理输入事件
    while (1)
    {
        len = read(fd, &event, sizeof(event)); // 读取一个输入事件
        if (len == sizeof(event))
        {
            printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value); // 打印事件信息
        }
        else
        {
            printf("read err %d\n", len); // 读取错误
        }
    }

    return 0;
}

开发板示例代码分析

相比使用ioctl获取设备信息的代码相比,在open文件时多了O_NONBLOCK的判断:

使用O_NONBLOCK方式open文件时,表示“非阻塞”,APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据,否则也会立刻返回错误;

不使用O_NONBLOCK方式open文件时,表示“阻塞”,APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据;否则APP就会在内核态休眠,当有数据时驱动程序会把APP唤醒,read函数恢复执行并返回数据给APP。

通过分析驱动程序evdev_read:

在这里插入图片描述

增加了一个input_event结构体的event变量,在驱动程序evdev_read中读取到的数据就是从input_event结构体类型的变量拷贝到用户空间的buffer:

在这里插入图片描述

上机实验

在这里插入图片描述

在这里插入图片描述

3.3.3 poll方式(Polling with poll)获取设备输入的信息
  • 定义

    • poll方式poll 是一个后来引入的多路复用I/O系统调用,旨在解决 select 的一些限制。它允许一个进程监视多个文件描述符,等待其中任何一个文件描述符变为可读、可写或发生异常。
  • 函数原型

    • #include <poll.h>
      int poll(struct pollfd *fds, nfds_t nfds, int timeout);
      
  • 参数

    • fds:一个 struct pollfd 数组,每个元素包含一个文件描述符及其感兴趣的事件。

    • nfds:数组中元素的数量。

      struct pollfd {
          int fd;       // 文件描述符
          short events; // 要监视的事件
          short revents;// 实际发生的事件
      };
      
    • timeout:等待的超时时间,以毫秒为单位,可以是 -1 表示无限期等待。

      注:**fds**中events有多种类型,如下图:

      在这里插入图片描述

  • 特点

    • 动态大小的文件描述符集合poll 使用 struct pollfd 数组来表示文件描述符集合,数组的大小是动态的,没有固定的上限。

    • 不需要重新初始化poll 不会修改传入的 struct pollfd 数组,因此不需要在每次调用前重新初始化。

    • 更好的性能poll 在处理大量文件描述符时性能更好,因为它只需要遍历实际使用的文件描述符,而不是固定的集合。

    • poll方式读取数据通用示例代码示例代码

      #include <poll.h>
      #include <unistd.h>
      #include <stdio.h>
      
      int main() {
          int fd1 = 0; // 标准输入
          int fd2 = 1; // 标准输出
      
          struct pollfd fds[1];
          fds[0].fd = fd1;
          fds[0].events = POLLIN;
      
          int ret = poll(fds, 1, 5000); // 5000毫秒超时
          if (ret < 0) {
              perror("poll");
              return 1;
          } else if (ret == 0) {
              printf("Timeout occurred! No data after 5 seconds.\n");
          } else {
              if (fds[0].revents & POLLIN) {
                  char buffer[1024];
                  ssize_t bytes_read = read(fd1, buffer, sizeof(buffer));
                  if (bytes_read > 0) {
                      printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);
                  }
              }
          }
      
          return 0;
      }
      

开发板示例代码

#include <linux/input.h>      // 输入设备相关的头文件
#include <sys/types.h>        // 基本系统数据类型
#include <sys/stat.h>         // 文件状态相关函数
#include <fcntl.h>            // 文件控制选项
#include <sys/ioctl.h>        // 输入输出控制函数
#include <stdio.h>            // 标准输入输出函数
#include <string.h>           // 字符串操作函数
#include <unistd.h>           // UNIX标准函数
#include <poll.h>             // poll相关函数和结构体

/*
 * 获取输入设备信息并持续监听事件
 * 参数:
 *   argc: 命令行参数个数
 *   argv: 命令行参数数组
 */
int main(int argc, char **argv)
{
    int fd;                  // 文件描述符
    int err;                 // 错误码
    int len;                 // 读取长度
    int ret;                 // poll返回值
    int i;                   // 循环变量
    unsigned char byte;      // 用于处理位掩码的字节
    int bit;                 // 位索引
    struct input_id id;      // 输入设备ID结构体
    unsigned int evbit[2];   // 用于存储事件类型位掩码的数组
    struct input_event event; // 输入事件结构体
    struct pollfd fds[1];    // pollfd结构体数组
    nfds_t nfds = 1;         // 监视的文件描述符数量

    // 事件类型名称数组
    char *ev_names[] = {
        "EV_SYN ",   // 同步事件
        "EV_KEY ",   // 按键事件
        "EV_REL ",   // 相对事件
        "EV_ABS ",   // 绝对事件
        "EV_MSC ",   // 其他事件
        "EV_SW ",    // 开关事件
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "EV_LED ",   // LED事件
        "EV_SND ",   // 声音事件
        "NULL ",     // 保留
        "EV_REP ",   // 重复事件
        "EV_FF ",    // 力反馈事件
        "EV_PWR ",   // 电源事件
    };

    // 检查命令行参数
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]); // 打印使用说明
        return -1;
    }

    // 打开指定的输入设备文件,使用非阻塞模式
    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]); // 打开设备失败
        return -1;
    }

    // 获取输入设备的ID信息
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        printf("bustype = 0x%x\n", id.bustype ); // 打印总线类型
        printf("vendor  = 0x%x\n", id.vendor  ); // 打印厂商ID
        printf("product = 0x%x\n", id.product ); // 打印产品ID
        printf("version = 0x%x\n", id.version ); // 打印版本号
    }

    // 获取设备支持的事件类型位掩码
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: "); // 打印支持的事件类型
        for (i = 0; i < len; i++)
        {
            byte = ((unsigned char *)evbit)[i]; // 获取当前字节
            for (bit = 0; bit < 8; bit++)
            {
                if (byte & (1 << bit)) { // 检查当前位是否为1
                    printf("%s ", ev_names[i * 8 + bit]); // 打印对应的事件类型名称
                }
            }
        }
        printf("\n");
    }

    // 持续监听事件
    while (1)
    {
        fds[0].fd = fd;                  // 设置文件描述符
        fds[0].events = POLLIN;          // 设置要监视的事件(可读)
        fds[0].revents = 0;              // 清除实际发生的事件

        ret = poll(fds, nfds, 5000);     // 调用poll,等待5000毫秒
        if (ret > 0)
        {
            if (fds[0].revents & POLLIN) // 检查是否发生可读事件
            {
                while (read(fd, &event, sizeof(event)) == sizeof(event)) // 读取事件
                {
                    printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", 
                           event.type, event.code, event.value); // 打印事件信息
                }
            }
        }
        else if (ret == 0)
        {
            printf("time out\n"); // 超时
        }
        else
        {
            printf("poll err\n"); // poll出错
        }
    }

    return 0;
}

开发板示例代码分析

分析驱动程序evdev_poll主要是将文件描述符添加到等待队列中,以便在文件描述符的状态发生变化时,能够通知调用者:

在这里插入图片描述

在这里插入图片描述

上机实验

在这里插入图片描述

3.3.4 selsct方式(Polling with select)获取设备输入的信息
  • 定义

    • select方式select 是一个早期的多路复用I/O系统调用,广泛应用于Unix和类Unix系统中。它允许一个进程监视多个文件描述符,等待其中任何一个文件描述符变为可读、可写或发生异常。
  • 函数原型

    • #include <sys/select.h>
      int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
      
  • 参数

    • nfds:要监视的最大文件描述符值加1。
    • readfds:要监视的可读文件描述符集合。
    • writefds:要监视的可写文件描述符集合。
    • exceptfds:要监视的异常条件文件描述符集合。
    • timeout:等待的超时时间,可以是 NULL 表示无限期等待。
  • 特点

    • 固定大小的文件描述符集合select 使用 fd_set 结构来表示文件描述符集合,fd_set 的大小是固定的,通常是1024个文件描述符。

    • 需要重新初始化:每次调用 select 之前,都需要重新初始化 fd_set,因为 select 会修改这些集合。

    • 性能问题:随着文件描述符数量的增加,select 的性能会下降,因为它需要遍历整个 fd_set 来检查每个文件描述符的状态。

    • select方式读取数据通用示例代码示例代码

      #include <sys/select.h>
      #include <unistd.h>
      #include <stdio.h>
      
      int main() {
          int fd1 = 0; // 标准输入
          int fd2 = 1; // 标准输出
      
          fd_set readfds;
          FD_ZERO(&readfds);
          FD_SET(fd1, &readfds);
      
          struct timeval timeout;
          timeout.tv_sec = 5;
          timeout.tv_usec = 0;
      
          int ret = select(fd1 + 1, &readfds, NULL, NULL, &timeout);
          if (ret < 0) {
              perror("select");
              return 1;
          } else if (ret == 0) {
              printf("Timeout occurred! No data after 5 seconds.\n");
          } else {
              if (FD_ISSET(fd1, &readfds)) {
                  char buffer[1024];
                  ssize_t bytes_read = read(fd1, buffer, sizeof(buffer));
                  if (bytes_read > 0) {
                      printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);
                  }
              }
          }
      
          return 0;
      }
      

开发板示例代码

#include <linux/input.h>      // 输入设备相关的头文件
#include <sys/types.h>        // 基本系统数据类型
#include <sys/stat.h>         // 文件状态相关函数
#include <fcntl.h>            // 文件控制选项
#include <sys/ioctl.h>        // 输入输出控制函数
#include <stdio.h>            // 标准输入输出函数
#include <string.h>           // 字符串操作函数
#include <unistd.h>           // UNIX标准函数
#include <sys/select.h>       // select相关函数和结构体
#include <sys/time.h>         // 时间相关的头文件

/*
 * 获取输入设备信息并持续监听事件
 * 参数:
 *   argc: 命令行参数个数
 *   argv: 命令行参数数组
 */
int main(int argc, char **argv)
{
    int fd;                  // 文件描述符
    int err;                 // 错误码
    int len;                 // 读取长度
    int ret;                 // select返回值
    int i;                   // 循环变量
    unsigned char byte;      // 用于处理位掩码的字节
    int bit;                 // 位索引
    struct input_id id;      // 输入设备ID结构体
    unsigned int evbit[2];   // 用于存储事件类型位掩码的数组
    struct input_event event; // 输入事件结构体
    int nfds;                // 最大的文件描述符加1
    struct timeval tv;       // 超时时间
    fd_set readfds;          // 用于select的读文件描述符集合

    // 事件类型名称数组
    char *ev_names[] = {
        "EV_SYN ",   // 同步事件
        "EV_KEY ",   // 按键事件
        "EV_REL ",   // 相对事件
        "EV_ABS ",   // 绝对事件
        "EV_MSC ",   // 其他事件
        "EV_SW ",    // 开关事件
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "EV_LED ",   // LED事件
        "EV_SND ",   // 声音事件
        "NULL ",     // 保留
        "EV_REP ",   // 重复事件
        "EV_FF ",    // 力反馈事件
        "EV_PWR ",   // 电源事件
    };

    // 检查命令行参数
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]); // 打印使用说明
        return -1;
    }

    // 打开指定的输入设备文件,使用非阻塞模式
    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]); // 打开设备失败
        return -1;
    }

    // 获取输入设备的ID信息
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        printf("bustype = 0x%x\n", id.bustype ); // 打印总线类型
        printf("vendor  = 0x%x\n", id.vendor  ); // 打印厂商ID
        printf("product = 0x%x\n", id.product ); // 打印产品ID
        printf("version = 0x%x\n", id.version ); // 打印版本号
    }

    // 获取设备支持的事件类型位掩码
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: "); // 打印支持的事件类型
        for (i = 0; i < len; i++)
        {
            byte = ((unsigned char *)evbit)[i]; // 获取当前字节
            for (bit = 0; bit < 8; bit++)
            {
                if (byte & (1 << bit)) { // 检查当前位是否为1
                    printf("%s ", ev_names[i * 8 + bit]); // 打印对应的事件类型名称
                }
            }
        }
        printf("\n");
    }

    // 持续监听事件
    while (1)
    {
        // 设置超时时间
        tv.tv_sec  = 5;     // 超时时间为5秒
        tv.tv_usec = 0;     // 微秒部分为0

        // 初始化读文件描述符集合
        FD_ZERO(&readfds);  // 先全部清零
        FD_SET(fd, &readfds); // 将文件描述符fd添加到读集合中

        // 设置最大的文件描述符加1
        nfds = fd + 1;      // nfds 是最大的文件描述符加1

        // 调用select,等待文件描述符变为可读或超时
        ret = select(nfds, &readfds, NULL, NULL, &tv);
        if (ret > 0)  // 有文件描述符准备好
        {
            // 再次确认fd有数据
            if (FD_ISSET(fd, &readfds))
            {
                // 读取并处理事件
                while (read(fd, &event, sizeof(event)) == sizeof(event))
                {
                    printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", 
                           event.type, event.code, event.value); // 打印事件信息
                }
            }
        }
        else if (ret == 0)  // 超时
        {
            printf("time out\n"); // 打印超时信息
        }
        else   // -1: error
        {
            printf("select err\n"); // 打印错误信息
        }
    }

    return 0;
}

开发板示例代码分析

在内核空间中,驱动程序需要支持 select,通常通过实现 poll 方法来实现。poll 方法是 select 的底层实现,允许内核知道哪些文件描述符已经准备好进行I/O操作。参考poll函数的代码分析。

上机实验

在这里插入图片描述

3.3.5 异步通知方式(Asynchronous I/O)获取设备输入的信息
  • 定义

    • 异步通知方式:在异步通知方式中,进程发起I/O请求后立即返回,继续执行其他任务。当I/O操作完成时,系统通过回调函数或信号等方式通知进程。这种方式通常通过aio(Asynchronous I/O)库实现。
  • 函数原型

    • #include <signal.h>
      typedef void (*sighandler_t)(int);
      sighandler_t signal(int signum, sighandler_t handler);
      
  • 参数

    • signum:指定要处理的信号编号。信号编号是一个整数,用于唯一标识一个信号。例如,SIGINT表示中断信号(通常由Ctrl+C产生)。

      在这里插入图片描述

    • handler:是一个指向函数的指针,该函数用于处理指定的信号。这个函数应该接受一个整型参数(即信号编号),并且没有返回值(即返回类型为void)。当信号signum发生时,系统将调用这个函数。

    • 返回值:如果调用成功,signal函数返回之前的信号处理函数指针;如果调用失败,返回SIG_ERR

  • 信号处理函数

    • handler参数可以指向以下几种类型的处理函数:
      • 用户自定义的处理函数:程序员可以编写自己的信号处理函数,并将其地址作为handler参数传递给signal函数。当信号发生时,系统将调用这个函数。
      • 忽略信号:通过将handler设置为SIG_IGN,可以告诉系统忽略指定的信号。但是,请注意,有些信号是不能被忽略的,如SIGKILLSIGSTOP
      • 采用默认处理:通过将handler设置为SIG_DFL,可以告诉系统采用默认的信号处理机制。对于大多数信号来说,默认的处理机制是终止进程。
  • 特点

    • 非阻塞:进程发起I/O请求后立即返回,不等待I/O操作完成。

    • 高效:适合高并发和高性能应用,可以充分利用CPU资源。

    • 复杂:需要处理回调函数或信号,编程复杂度较高。

    • 异步通知方式使用single函数读取数据通用示例代码示例代码

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <fcntl.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <linux/input.h>
      #include <signal.h>
      
      /* 信号处理函数 */
      void handle_sigio(int signum) {
          static struct input_event event;
          int fd = fileno(stdin); // 假设我们监听的是标准输入,实际应用中应替换为设备文件描述符
      
          // 读取事件
          ssize_t bytes_read = read(fd, &event, sizeof(event));
          if (bytes_read == sizeof(event)) {
              printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
          } else if (bytes_read == -1) {
              perror("read");
          } else {
              printf("Partial read: %zd bytes\n", bytes_read);
          }
      }
      
      /* 主函数 */
      int main(int argc, char **argv) {
          int fd;
          struct sigaction sa;
      
          if (argc != 2) {
              fprintf(stderr, "Usage: %s <device>\n", argv[0]);
              return -1;
          }
      
          // 打开设备文件
          fd = open(argv[1], O_RDONLY | O_NONBLOCK);
          if (fd == -1) {
              perror("open");
              return -1;
          }
      
          // 设置文件描述符的所有者
          fcntl(fd, F_SETOWN, getpid());
      
          // 设置文件描述符为异步通知
          fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_ASYNC);
      
          // 注册信号处理函数
          memset(&sa, 0, sizeof(sa));
          sa.sa_handler = handle_sigio;
          sa.sa_flags = 0;
          if (sigaction(SIGIO, &sa, NULL) == -1) {
              perror("sigaction");
              return -1;
          }
      
          // 主循环
          while (1) {
              pause(); // 等待信号
          }
      
          close(fd);
          return 0;
      }
      

开发板示例代码

#include <linux/input.h>      // 输入设备相关的头文件
#include <sys/types.h>        // 基本系统数据类型
#include <sys/stat.h>         // 文件状态相关函数
#include <fcntl.h>            // 文件控制选项
#include <sys/ioctl.h>        // 输入输出控制函数
#include <stdio.h>            // 标准输入输出函数
#include <string.h>           // 字符串操作函数
#include <unistd.h>           // UNIX标准函数
#include <signal.h>           // 信号处理相关函数

int fd;                      // 文件描述符

// 信号处理函数
void my_sig_handler(int sig)
{
    struct input_event event; // 输入事件结构体
    // 读取并处理所有可用的输入事件
    while (read(fd, &event, sizeof(event)) == sizeof(event))
    {
        printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
    }
}

/* ./05_input_read_fasync /dev/input/event0 */
int main(int argc, char **argv)
{
    int err;                 // 错误码
    int len;                 // 读取长度
    int ret;                 // 通用返回值
    int i;                   // 循环变量
    unsigned char byte;      // 用于处理位掩码的字节
    int bit;                 // 位索引
    struct input_id id;      // 输入设备ID结构体
    unsigned int evbit[2];   // 用于存储事件类型位掩码的数组
    unsigned int flags;      // 文件描述符标志
    int count = 0;           // 计数器

    // 事件类型名称数组
    char *ev_names[] = {
        "EV_SYN ",   // 同步事件
        "EV_KEY ",   // 按键事件
        "EV_REL ",   // 相对事件
        "EV_ABS ",   // 绝对事件
        "EV_MSC ",   // 其他事件
        "EV_SW ",    // 开关事件
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "NULL ",     // 保留
        "EV_LED ",   // LED事件
        "EV_SND ",   // 声音事件
        "NULL ",     // 保留
        "EV_REP ",   // 重复事件
        "EV_FF ",    // 力反馈事件
        "EV_PWR ",   // 电源事件
    };

    // 检查命令行参数
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]); // 打印使用说明
        return -1;
    }

    // 注册信号处理函数
    signal(SIGIO, my_sig_handler); // 注册SIGIO信号处理函数

    // 打开驱动程序
    fd = open(argv[1], O_RDWR | O_NONBLOCK); // 以读写模式和非阻塞模式打开设备文件
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]); // 打开设备失败
        return -1;
    }

    // 获取输入设备的ID信息
    err = ioctl(fd, EVIOCGID, &id); // 使用ioctl获取设备ID信息
    if (err == 0)
    {
        printf("bustype = 0x%x\n", id.bustype ); // 打印总线类型
        printf("vendor  = 0x%x\n", id.vendor  ); // 打印厂商ID
        printf("product = 0x%x\n", id.product ); // 打印产品ID
        printf("version = 0x%x\n", id.version ); // 打印版本号
    }

    // 获取设备支持的事件类型位掩码
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit); // 使用ioctl获取事件类型位掩码
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: "); // 打印支持的事件类型
        for (i = 0; i < len; i++)
        {
            byte = ((unsigned char *)evbit)[i]; // 获取当前字节
            for (bit = 0; bit < 8; bit++)
            {
                if (byte & (1 << bit)) { // 检查当前位是否为1
                    printf("%s ", ev_names[i * 8 + bit]); // 打印对应的事件类型名称
                }
            }
        }
        printf("\n");
    }

    // 把APP的进程号告诉驱动程序
    fcntl(fd, F_SETOWN, getpid()); // 设置文件描述符的所有者为当前进程

    // 使能"异步通知"
    flags = fcntl(fd, F_GETFL); // 获取文件描述符的当前标志
    fcntl(fd, F_SETFL, flags | FASYNC); // 设置文件描述符为异步通知模式

    // 主循环
    while (1)
    {
        printf("main loop count = %d\n", count++); // 打印主循环计数
        sleep(2); // 暂停2秒
    }

    return 0;
}

开发板示例代码分析

需要补充fcntl函数知识:

  • 函数原型

    • #include <unistd.h>
      #include <fcntl.h>
      int fcntl(int fd, int cmd, ...); /* 可变参数,根据cmd的值决定是否需要第三个参数 */
      
  • 参数

    • fd:文件描述符,表示要操作的文件。
    • cmd:操作命令,决定fcntl函数的具体行为。
    • ...:根据cmd的值,可能需要一个额外的参数,该参数的类型可以是longstruct flock *
  • 功能(包括但不限于)

    • 复制文件描述符:使用F_DUPFD命令可以复制一个现有的文件描述符。
    • 获取/设置文件描述符标记:通过F_GETFDF_SETFD命令,可以获取或设置文件描述符的标记,如FD_CLOEXEC
    • 获取/设置文件状态标记:使用F_GETFLF_SETFL命令,可以获取或设置文件的状态标记,如O_APPENDO_NONBLOCK等。
    • 获取/设置异步I/O所有权:通过F_GETOWNF_SETOWN命令,可以获取或设置接收SIGIO或SIGURG信号的进程ID或进程组ID。
    • 获取/设置记录锁:使用F_GETLKF_SETLKF_SETLKW命令,可以获取、设置或测试文件的记录锁。

上机实验

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915840.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣=Mysql-3322- 英超积分榜排名 III(中等)

一、题目来源 3322. 英超积分榜排名 III - 力扣&#xff08;LeetCode&#xff09; 二、数据表结构 表&#xff1a;SeasonStats --------------------------- | Column Name | Type | --------------------------- | season_id | int | | team_id …

HTML之列表学习记录

练习题&#xff1a; 图所示为一个问卷调查网页&#xff0c;请制作出来。要求&#xff1a;大标题用h1标签&#xff1b;小题目用h3标签&#xff1b;前两个问题使用有序列表&#xff1b;最后一个问题使用无序列表。 代码&#xff1a; <!DOCTYPE html> <html> <he…

【ElasticSearch】定位分片不分配

记录遇到的es集群分片不分配的情况--待补全 定位分片不分配的原因 定位分片不分配的原因 在shell客户端执行如下的语句&#xff1a; curl -X GET "http://192.168.0.209:9200/_cat/shards?v&hindex,shard,prirep,state,unassigned.reason"集群中各分片的状态都…

10款PDF合并工具讲解与推荐!!!

在现在的大环境下&#xff0c;PDF文件因其跨平台、格式固定等优势&#xff0c;成为了我们工作和学习中不可或缺的一部分。是最常用的文档格式之一。然而&#xff0c;面对多个PDF文件需要合并成一个的场景&#xff0c;如何选择一款高效、易用的PDF合并工具就显得尤为重要。今天&…

「QT」窗口类 之 QWidget 窗口基类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定制…

【机器学习】如何配置anaconda环境(无脑版)

马上就要上机器学习的实验&#xff0c;这里想写一下我配置机器学习的anaconda环境的二三事 一、首先&#xff0c;下载安装包&#xff1a; Download Now | Anaconda 二、打开安装包&#xff0c;一直点NEXT进行安装 这里要记住你要下载安装的路径在哪&#xff0c;后续配置环境…

【3D Slicer】的小白入门使用指南四

开源解剖影像浏览工具Open Anatomy Browser使用及介绍 和3D slicer米有太大关系,该工具是网页版影像数据的浏览工具(可以简单理解为网页版的3D slicer) 介绍 ● 开放解剖(OA)浏览器是由神经影像分析中心开发的,基于网络浏览器技术构建的图谱查看器。 ● OA浏览器将解剖模…

Unity使用PS合并贴图

前言 使用PBR渲染&#xff0c;金属工作流时&#xff0c;默认使用一个金属度贴图&#xff0c;其中r通道保存金属度&#xff0c;a通道保存光滑度&#xff0c;g通道和b通道没使用&#xff1b; 我们很可能使用Occlusion Map&#xff0c;使用其中的g通道保存Occlusion 信息。单独使用…

Linux中.NET读取excel组件,不会出现The type initializer for ‘Gdip‘ threw an exception异常

组件&#xff0c;可通过nuget安装&#xff0c;直接搜名字&#xff1a; ExcelDataReader using ConsoleAppReadFileData.Model; using ExcelDataReader; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Task…

Qt_day4_Qt_UI设计

目录 Qt_UI设计 1. Designer 设计师&#xff08;掌握&#xff09; 2. Layout 布局&#xff08;重点&#xff09; 2.1 基本使用 2.2 高级用法 2.3 代码布局&#xff08;了解&#xff09; 3. Designer与C的关系&#xff08;熟悉&#xff09; 4. 基本组件&#xff08;掌握…

Axure网络短剧APP端原型图,竖屏微剧视频模版40页

作品概况 页面数量&#xff1a;共 40 页 使用软件&#xff1a;Axure RP 9 及以上&#xff0c;非软件无源码 适用领域&#xff1a;短剧、微短剧、竖屏视频 作品特色 本作品为网络短剧APP的Axure原型设计图&#xff0c;定位属于免费短剧软件&#xff0c;类似红果短剧、河马剧场…

网安加·百家讲坛 | 仝辉:金融机构鸿蒙应用安全合规建设方案

作者简介&#xff1a;仝辉&#xff0c;北京娜迦信息科技发展有限公司攻防安全负责人&#xff0c;深耕移动应用安全领域十余年&#xff0c;获得过CISP、CISSP、OSCP、PMP、CCRC-CIASW等相关证书&#xff0c;参与多项移动应用安全标准起草&#xff0c;参与华为、平安集团、中国移…

C语言 strlen 函数 - C语言零基础入门教程

目录 一.strlen 函数简介二.strlen 函数实战三.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 一.strlen 函数简介 在C 语言中&#xff0c;char 字符串也是一种非常重要的数据类型&#xff0c;我们可以使用 strlen 函数获取字符串长度&#xff1b;…

OceanStor Pacific系列 8.1.0 功能架构

功能架构 华为OceanStor Pacific系列提供基于三层的分布式存储架构&#xff0c;融合分布式文件、对象、大数据和块多个服务形态&#xff0c;支持文件、对象、大数据服务部署在一个集群&#xff0c;并统一管理。 华为OceanStor Pacific系列整体功能架构由存储接口层、存储服务…

Simulink中Matlab function使用全局变量

目录 一. 引言二. 普通Matlab function使用全局变量三. Simulink中的Matlab function使用全局变量四. 如何利用Matlab function的全局变量施加随机噪声 一. 引言 最近发现了之前仿真中的一个问题&#xff0c;记录一下备忘。 Matlab function中有时候需要用到全局变量&#xf…

react-markdown内容宽度溢出和换行不生效问题

情景复现&#xff1a; 解决办法&#xff0c;添加样式进行限制 /* index.css */ .markdown-container {word-break: break-word; /* 强制长单词断行 */white-space: pre-wrap; /* 保留空白符序列&#xff0c;但是正常地进行换行 */overflow-wrap: break-word; /* 在长单词或…

【C#设计模式(10)——装饰器模式(Decorator Pattern)】

前言 装饰器模式可以在运行时为对象添加额外的功&#xff0c;而无需修改原始对象的代码。这种方式比继承更加灵活。 代码 //蛋糕类&#xff08;抽象类&#xff09; public abstract class Cake {public abstract void Create(); } //奶油蛋糕类 public class CreamCake : Cak…

Diffusion Transformer模型结构解析(DiT、SD3、Flux)

Diffusion Transformer模型结构解析&#xff08;DiT、SD3、Flux&#xff09; 本文将通过 DiT、SD3、Flux 三个 DiT 相关工作&#xff0c;介绍 Diffusion 中的 Transformer 结构的应用与演进。注意 SD3 和 Flux 采用的 Flow Matching 的扩散模型形式化当然是很关键的改进&#…

给阿里云OSS绑定域名并启用SSL

为什么要这么做&#xff1f; 问题描述&#xff1a; 当用户通过 OSS 域名访问文件时&#xff0c;OSS 会在响应头中增加 Content-Disposition: attachment 和 x-oss-force-download: true&#xff0c;导致文件被强制下载而不是预览。这个问题特别影响在 2022/10/09 之后新开通 OS…

如何找到系统中bert-base-uncased默认安装位置

问题&#xff1a; 服务器中无法连接huggingface&#xff0c;故需要自己将模型文件上传 ubuntu 可以按照这个链接下载 Bert下载和使用&#xff08;以bert-base-uncased为例&#xff09; - 会自愈的哈士奇 - 博客园 里面提供了giehub里面的链接 GitHub - google-research/be…