读取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; }
关键修复说明
- 完善open错误处理:所有open失败的情况都终止执行,并打印错误信息,避免使用无效fd;
- 正确检查ioctl返回值:对
HIDIOCAPPLICATION先检查返回值是否为-1,再转换为无符号类型; - 处理read被信号中断的情况:当
errno=EINTR时重试read,避免误退出; - 遍历所有报告ID:获取设备所有输入报告的信息,确保内核能正确推送所有事件;
- 获取字段信息:调用
HIDIOCGFIELDINFO获取每个字段的详细信息,帮助内核正确初始化事件监听; - 添加close调用:所有错误路径都关闭fd,避免资源泄漏。
内容的提问来源于stack exchange,提问作者abergmeier
相关产品推荐
相关产品推荐

