Linux 应用层开发入门(二十)| 获取输入系统设备数据

在前面的文章中,我们已经了解了Linux输入子系统的基本框架,以及/dev/input/eventX设备节点的作用。本篇我们进入实战阶段 ------编写一个应用层程序,读取输入设备的设备基本信息(厂商ID、产品ID等)设备支持的事件类型(EV_KEY、EV_REL、EV_ABS 等)。通过本篇,你将彻底理解:

  • ioctl在输入子系统中的作用
  • EVIOCGID的原理
  • EVIOCGBIT的位图机制
  • 输入设备能力是如何用bit位表示的

1 Linux 输入子系统简介

Linux输入系统的设备节点通常位于:/dev/input/eventX中,每个eventX代表一个输入设备,例如:

  • 键盘
  • 鼠标
  • 触摸屏
  • 电源按键
  • 陀螺仪

输入设备统一使用struct input_event结构上报事件,但在真正读取事件之前,我们通常需要知道:

这个设备是什么?支持什么类型的事件?

这就需要用到ioctl

2 获取设备基本信息------EVIOCGID

Linux 内核中定义了:

cpp 复制代码
struct input_id {
    __u16 bustype;   // 总线类型
    __u16 vendor;    // 厂商ID
    __u16 product;   // 产品ID
    __u16 version;   // 版本号
};

使用:

cpp 复制代码
ioctl(fd, EVIOCGID, &id);

即可获取输入设备的基本身份信息。其中,fd是通过open()打开的设备文件描述符(如/dev/input/event0),EVIOCGID是输入子系统定义的控制命令,用于从驱动中读取设备的ID信息,&id是一个 struct input_id结构体指针,用于接收驱动返回的数据。

调用成功后,内核会将设备的总线类型(bustype)、厂商 ID(vendor)、产品 ID(product)以及版本号(version)填充到该结构体中,从而帮助应用层识别设备的硬件来源及类型。这种方式体现了 ioctl 在应用层与驱动之间进行"控制信息交互"的典型用法。

示例输出:

复制代码
bustype = 0x3
vendor  = 0x046d
product = 0xc077
version = 0x111

这些值可以帮助我们:

  • 判断是 USB 设备还是 I2C 设备
  • 识别具体厂商和产品型号

3 获取设备支持的事件类型------EVIOCGBIT

3.1 什么是事件类型?

Linux输入系统定义了多种事件类型,例如:

事件类型 说明
EV_SYN 同步事件
EV_KEY 按键事件
EV_REL 相对坐标(鼠标)
EV_ABS 绝对坐标(触摸屏)
EV_LED LED
EV_SND 声音
EV_FF 力反馈

3.2 事件类型如何存储?

Linux使用**位图(bitmask)**表示设备支持的能力。例如:

复制代码
bit 0 → EV_SYN
bit 1 → EV_KEY
bit 2 → EV_REL
bit 3 → EV_ABS
...

如果某一位为 1,表示支持该类型。

3.3 获取事件类型位图?

cpp 复制代码
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);

解释:

  • 第一个参数0表示查询"事件类型"
  • 第二个参数表示位图缓冲区大小
  • 返回值表示有效字节数

3.4 如何解析位图?

我们按字节遍历:

cs 复制代码
for(i=0; i<len; i++)
{
    byte = ((unsigned char *)evbit)[i];
    for(bit=0 ; bit<8; bit++)
    {
        if(byte & (1<<bit))
        {
            printf("%s\n", ev_names[i*8+bit]);
        }
    }
}

逻辑解析:

  • 每个字节8个bit
  • 每个bit对应一个事件类型
  • i*8+bit就是实际的事件编号

4 完整运行示例

编译:

复制代码
arm-linux-gcc 01_get_input_info.c -o get_input_info

运行:

复制代码
./get_input_info /dev/input/event0

输出示例:

复制代码
bustype = 0x3
vendor  = 0x046d
product = 0xc077
version = 0x111
support ev type:
EV_SYN
EV_KEY
EV_REL

说明该设备是一个USB鼠标。

5 程序原理总结

本程序完成了三件事:

  1. 打开输入设备
  2. 使用EVIOCGID获取设备身份信息
  3. 使用EVIOCGBIT获取支持的事件类型位图
  4. 解析bit位并打印可读名称

6 完整代码

下面给出完整版详细注释代码

cs 复制代码
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

/*
 * 使用方法:
 *      ./01_get_input_info /dev/input/event0
 *
 * 功能:
 *      1. 获取输入设备基本信息
 *      2. 获取设备支持的事件类型
 */

int main(int argc, char **argv)
{
    int fd;                 // 设备文件描述符
    int err;                // ioctl返回值
    int len;                // 返回的有效字节数
    int i;                  // 字节循环变量
    unsigned char byte;     // 当前字节
    int bit;                // 当前bit

    struct input_id id;     // 设备ID结构体

    /* 用于保存事件类型位图
     * 每个bit表示一种事件类型是否支持
     */
    unsigned int evbit[2];  

    /* 事件类型名称数组
     * 下标对应事件编号
     */
    char *ev_names[] = {
        "EV_SYN",   // 0
        "EV_KEY",   // 1
        "EV_REL",   // 2
        "EV_ABS",   // 3
        "EV_MSC",   // 4
        "EV_SW",    // 5
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "NULL",
        "EV_LED",   // 17
        "EV_SND",   // 18
        "NULL",
        "EV_REP",   // 20
        "EV_FF",    // 21
    };

    /* 检查参数 */
    if(argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }

    /* 打开输入设备 */
    fd = open(argv[1], O_RDWR);
    if(fd < 0)
    {
        printf("open %s error\n", argv[1]);
        return -1;      
    }

    /* 获取设备ID信息 */
    err = ioctl(fd, EVIOCGID, &id);
    if(err == 0)
    {
        printf("Device ID info:\n");
        printf("bustype = 0x%x\n", id.bustype);
        printf("vendor  = 0x%x\n", id.vendor);
        printf("product = 0x%x\n", id.product);
        printf("version = 0x%x\n", id.version);
    }

    /*
     * 获取事件类型位图
     * 参数说明:
     *      0 表示获取事件类型
     *      sizeof(evbit) 表示缓冲区大小
     */
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);

    if(len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type:\n");

        /* 按字节解析位图 */
        for(i=0; i<len; i++)
        {
            /* 取出当前字节 */
            byte = ((unsigned char *)evbit)[i];

            /* 遍历该字节的8个bit */
            for(bit=0 ; bit<8; bit++)
            {
                /* 判断当前bit是否为1 */
                if(byte & (1<<bit))
                {
                    int type = i*8 + bit;   // 计算事件编号

                    /* 防止数组越界 */
                    if(type < sizeof(ev_names)/sizeof(ev_names[0]) 
                       && ev_names[type] != NULL)
                    {
                        printf("  %s\n", ev_names[type]);
                    }
                }
            }
        }
    }

    close(fd);
    return 0;
}
相关推荐
yuanmenghao2 小时前
Linux 性能实战 | 第 17 篇:strace 系统调用分析与性能调优 [特殊字符]
linux·python·性能优化
hweiyu002 小时前
Linux 命令:setfacl
linux·运维·服务器
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers]char
linux·笔记·学习
社会零时工2 小时前
Ubuntu安装的OpenCV如何更换版本
linux·opencv·ubuntu
m0_528749002 小时前
C语言错误处理宏两个比较重要的
java·linux·算法
Jia ming3 小时前
Linux性能分析工具perf全面解析
linux·性能优化·perf
珠穆峰3 小时前
linux find 命令使用
linux·运维·服务器
JinchuanMaster3 小时前
Ubuntu20.04安装50系显卡驱动[不黑屏版本]
linux·人工智能·深度学习·ubuntu·机器学习·机器人·gpu算力