You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取HIDDEV设备时read调用阻塞问题求助

HIDDEV read阻塞无法获取事件的问题排查与修复

你的代码存在几个关键问题,会导致读取几次事件后read阻塞,即使设备有事件产生也无法返回:

1. 未处理open失败的所有情况

当open因非EACCES原因失败(比如设备不存在、设备被占用),代码没有终止执行,而是继续使用无效的fd=-1调用后续ioctl,这会导致内核返回错误,同时污染后续的结构体数据(比如info.num_applications变成垃圾值),进而引发一系列未定义行为,最终导致read逻辑异常。

2. 错误的ioctl返回值检查

HIDIOCAPPLICATION的返回值是int类型,失败时返回-1,但你直接将其赋值给unsigned usage后用usage == -1判断,这会因为无符号整数的类型提升导致误判(-1转为无符号是0xFFFFFFFF,如果设备的usage正好是这个值,会错误认为调用失败)。正确的做法是先检查ioctl的返回值是否为-1,再转换为无符号类型。

3. 不完整的系统调用错误处理

多个ioctl(比如HIDIOCGNAME)和read的错误处理缺失:

  • HIDIOCGNAME失败时会导致name数组内容未被正确填充,但代码仍继续使用;
  • read被信号中断时(errno=EINTR)直接退出,而不是重试读取,这会让你误以为read阻塞。

4. 未处理HID设备的多报告ID场景

你只获取了第一个报告ID(HID_REPORT_ID_FIRST)的报告信息,但部分HID设备会使用多个报告ID,未处理的报告会导致内核无法正确推送事件。


修复后的代码

#include <errno.h>
#include <fcntl.h>
#include <asm/ioctl.h>
#include <linux/hiddev.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char name[128];
    snprintf(name, sizeof(name), "/dev/usb/hiddev0");

    fprintf(stdout, "Using device %s\n", name);

    int fd = open(name, O_RDONLY);
    if (fd < 0)
    {
        if (errno == EACCES)
        {
            fprintf(stderr, "No permission, try this as root.\n");
        }
        else
        {
            perror("Failed to open device");
        }
        return -1;
    }

    // 检查HIDIOCGNAME调用是否成功
    if (ioctl(fd, HIDIOCGNAME(sizeof(name)), name) == -1)
    {
        perror("HIDIOCGNAME failed");
        close(fd);
        return -1;
    }
    printf("HID device name: \"%s\"\n", name);

    struct hiddev_devinfo info;
    if (ioctl(fd, HIDIOCGDEVINFO, &info) == -1)
    {
        perror("HIDIOCGDEVINFO failed");
        close(fd);
        return -1;
    }

    printf("Found %d applications\n", info.num_applications);

    for (int i = 0; i < info.num_applications; i++)
    {
        int ret = ioctl(fd, HIDIOCAPPLICATION, i);
        if (ret == -1)
        {
            perror("HIDIOCAPPLICATION failed");
            close(fd);
            return -2;
        }
        unsigned usage = (unsigned)ret;
        printf("Usages of Application[%u]: %0X\n", i, usage);
    }

    // 遍历所有报告ID,而不仅仅是第一个
    struct hiddev_report_info rinfo;
    rinfo.report_type = HID_REPORT_TYPE_INPUT;
    rinfo.report_id = HID_REPORT_ID_FIRST;
    do {
        if (ioctl(fd, HIDIOCGREPORT, &rinfo) == -1)
        {
            if (errno == ENODEV || errno == EINVAL)
                break; // 没有更多报告ID
            perror("HIDIOCGREPORT failed");
            close(fd);
            return -3;
        }
        printf("Found report ID %0X with %d fields\n", rinfo.report_id, rinfo.num_fields);

        // 获取每个字段的信息(确保内核正确识别字段)
        struct hiddev_field_info finfo;
        for (int i = 0; i < rinfo.num_fields; i++)
        {
            finfo.report_type = rinfo.report_type;
            finfo.report_id = rinfo.report_id;
            finfo.field_index = i;
            if (ioctl(fd, HIDIOCGFIELDINFO, &finfo) == -1)
            {
                perror("HIDIOCGFIELDINFO failed");
                close(fd);
                return -4;
            }
        }

        rinfo.report_id = rinfo.next_report_id;
    } while (rinfo.report_id != HID_REPORT_ID_NONE);

    int flags = HIDDEV_FLAG_UREF;
    if (ioctl(fd, HIDIOCSFLAG, &flags) == -1)
    {
        perror("HIDIOCSFLAG failed");
        close(fd);
        return -5;
    }

    printf("Waiting on events\n");

    for (;;)
    {
        struct hiddev_usage_ref ev;
        ssize_t n = read(fd, &ev, sizeof(ev));

        if (n == -1)
        {
            if (errno == EINTR)
                continue; // 被信号中断,重试读取
            perror("read failed");
            close(fd);
            return -6;
        }
        if (n != sizeof(ev))
        {
            fprintf(stderr, "read returned incomplete data: %zd bytes\n", n);
            close(fd);
            return -6;
        }

        printf("Found {field_index: %d, report_id: %0X, report_type: %d, usage_code: %0X, usage_index: %u, value: %d}\n",
               ev.field_index, ev.report_id, ev.report_type, ev.usage_code, ev.usage_index, ev.value);
    }

    close(fd);
    return 0;
}

关键修复说明

  1. 完善open错误处理:所有open失败的情况都终止执行,并打印错误信息,避免使用无效fd;
  2. 正确检查ioctl返回值:对HIDIOCAPPLICATION先检查返回值是否为-1,再转换为无符号类型;
  3. 处理read被信号中断的情况:当errno=EINTR时重试read,避免误退出;
  4. 遍历所有报告ID:获取设备所有输入报告的信息,确保内核能正确推送所有事件;
  5. 获取字段信息:调用HIDIOCGFIELDINFO获取每个字段的详细信息,帮助内核正确初始化事件监听;
  6. 添加close调用:所有错误路径都关闭fd,避免资源泄漏。

内容的提问来源于stack exchange,提问作者abergmeier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:50:25