当前位置: 首页 > news >正文

(新手)Linux 输入子系统实战教程 —— 02设备信息查询 + 输入事件读取(阻塞 / 非阻塞模式)

Linux 输入子系统实战教程 —— 设备信息查询 + 输入事件读取(阻塞 / 非阻塞模式)完整学习文档

本文档基于Linux 输入设备事件读取程序编写,包含完整注释源码、核心原理、逐模块解析、真实实验现象、错误原因分析,专为嵌入式 Linux 驱动新手打造,完全贴合百问网开发板 / 虚拟机实操环境,覆盖你实验中出现的read err -1、阻塞 / 非阻塞模式、电源键设备等全部核心内容。

一、文档说明

  1. 本文档讲解的程序在旧版查询设备信息基础上升级,新增实时读取输入事件阻塞 / 非阻塞运行模式切换功能;
  2. 完整包含:代码注释、原理讲解、编译运行、实验现象、错误排查;
  3. 所有案例、现象均来自你真实的实验操作,可直接对照学习。

二、程序核心功能

  1. 保留旧版功能:查询输入设备总线类型、厂商 ID、产品 ID、版本号、支持的事件类型
  2. 新增核心功能:实时读取键盘 / 鼠标 / 电源键的输入事件,打印事件类型、编码、状态;
  3. 支持两种运行模式:
    • 阻塞模式(默认):无事件时程序卡住等待,不打印冗余信息;
    • 非阻塞模式:无事件时read直接返回,不等待(会出现read err -1);
  4. 兼容所有 Linux 输入设备:键盘、鼠标、电源键、触摸屏等。

三、核心前置知识(新手必学)

3.1 Linux 标准输入事件结构体struct input_event

内核向应用层传递所有输入设备事件,统一使用该结构体:

struct input_event { struct timeval time; // 事件发生的时间戳 __u16 type; // 事件类型(如 EV_KEY 按键事件) __u16 code; // 具体按键/坐标编码(如回车键、鼠标左键) __s32 value; // 事件状态:1=按下 0=松开 2=长按重复 };

3.2 阻塞模式 VS 非阻塞模式

表格

模式打开标志无事件时表现有事件时表现
阻塞模式(默认)O_RDWRread 函数休眠等待,程序卡住,不打印read 读取事件,打印事件信息
非阻塞模式O_RDWR | O_NONBLOCKread 立即返回 - 1,循环打印read err -1read 读取事件,打印事件信息

3.3 事件位图机制

内核使用 ** 二进制位(bit)** 表示设备支持的事件类型,1=支持,0=不支持,程序通过双层循环 + 位运算解析位图。

3.4read函数作用

从输入设备文件/dev/input/eventx中,读取内核发送的input_event输入事件。

四、完整带超详细注释源码

// Linux输入子系统核心头文件:定义输入事件、结构体、ioctl设备控制命令 #include <linux/input.h> // Linux系统调用基础头文件 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> // 提供 open() 函数,用于打开设备文件 #include <sys/ioctl.h> // 提供 ioctl() 函数,设备控制,读取硬件信息 #include <stdio.h> // 提供 printf() 打印函数 #include <string.h> // 提供 strcmp() 字符串比较函数,判断 noblock 参数 #include <unistd.h> // 提供 read()、close() 函数 /* * 程序功能:查询输入设备信息 + 实时读取输入事件 * 运行命令格式(两种模式): * 1. 阻塞模式(默认,推荐): * sudo ./02_input_read /dev/input/eventx * 2. 非阻塞模式(加 noblock 参数): * sudo ./02_input_read /dev/input/eventx noblock * 设备说明:event0=电源键,event1=键盘,event2~4=鼠标 */ int main(int argc, char **argv) { int fd; // 设备文件描述符,打开设备后用于操作硬件 int err; // 存储 ioctl 函数返回值,判断是否调用成功 int len; // 存储 read/ioctl 读取到的数据长度 int i, bit; // 双层循环计数器,用于解析事件类型位图 unsigned char byte; // 存储事件位图的单个字节数据 struct input_id id; // 内核结构体:存储设备硬件ID(总线/厂商/产品/版本) unsigned int evbit[2]; // 存储事件类型位图:2个int = 8字节 = 64位 struct input_event event; // 内核标准结构体:存储读取到的输入事件数据 // 事件类型名称数组,与内核 EV_XXX 宏一一对应,用于打印可读事件名 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 ", // 电源事件 }; // ===================== 步骤1:参数合法性检查 ===================== // 程序运行至少需要2个参数:程序名 + 输入设备路径(/dev/input/eventx) if (argc < 2) { printf("Usage: %s <dev> [noblock]\n", argv[0]); return -1; } // ===================== 步骤2:打开设备(阻塞/非阻塞模式分支) ===================== // 判断:传入3个参数,且第3个参数为 noblock → 非阻塞模式 if (argc == 3 && !strcmp(argv[2], "noblock")) { // O_NONBLOCK:非阻塞标志,无事件时read不等待,直接返回-1 fd = open(argv[1], O_RDWR | O_NONBLOCK); } // 默认情况:阻塞模式,无事件时read卡住等待,直到有事件触发 else { fd = open(argv[1], O_RDWR); } // 打开设备失败判断:无权限/设备不存在,打印错误并退出 if (fd < 0) { printf("open %s err\n", argv[1]); return -1; } // ===================== 步骤3:读取并打印设备硬件ID信息 ===================== // EVIOCGID:内核ioctl命令,获取设备总线、厂商、产品、版本号 err = ioctl(fd, EVIOCGID, &id); if (err == 0) { 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 ); } // ===================== 步骤4:读取并解析事件类型位图 ===================== // EVIOCGBIT(0, 大小):内核ioctl命令,获取设备支持的事件类型位图 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]; // 第二层循环:遍历当前字节的每一位(0~7) for (bit = 0; bit < 8; bit++) { // 位运算判断:当前位=1 → 设备支持该事件类型 if (byte & (1<<bit)) { printf("%s ", ev_names[i*8 + bit]); } } } printf("\n"); } // ===================== 步骤5:无限循环,实时读取输入事件 ===================== while (1) { // 从设备文件读取内核发送的输入事件,存入event结构体 // 阻塞模式:无事件 → 卡住等待 // 非阻塞模式:无事件 → 立即返回-1(实验现象:read err -1) len = read(fd, &event, sizeof(event)); // 读取成功判断:读取长度 == 输入事件结构体大小 if (len == sizeof(event)) { // 打印事件信息:type=事件类型,code=按键编码,value=按键状态 // value=1:按下 value=0:松开 printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value); } // 读取失败:非阻塞模式无事件 或 设备异常 else { // 非阻塞模式下,无事件时read返回-1,打印此错误(你的核心实验现象) printf("read err %d\n", len); } } // 关闭设备文件(while(1)无限循环,本行代码永远不会执行) close(fd); return 0; }

五、代码逐模块精讲

5.1 参数与模式判断模块

通过strcmp判断是否传入noblock参数,选择阻塞 / 非阻塞打开方式,是两种模式的核心分支。

5.2 设备信息查询模块

使用ioctl+EVIOCGIDEVIOCGBIT命令,读取设备硬件信息与事件位图,保留旧版核心逻辑。

5.3 事件读取循环模块

while(1)无限循环,持续调用read读取事件:

  • 阻塞模式:无事件休眠,不占用 CPU;
  • 非阻塞模式:无事件立即返回 - 1,高速循环打印read err -1

六、编译与实操运行命令

6.1 编译代码

gcc 02_input_read.c -o 02_input_read

6.2 查看系统输入设备

cat /proc/bus/input/devices

你的设备:

  • event0:电源键
  • event1:键盘
  • event2~4:鼠标

6.3 阻塞模式运行(默认,推荐)

sudo ./02_input_read /dev/input/event1

6.4 非阻塞模式运行

sudo ./02_input_read /dev/input/event1 noblock

七、真实实验现象全解析(对应你的实操结果)

7.1 现象 1:非阻塞模式疯狂打印read err -1

read err -1 read err -1 ...(无限刷屏)
  1. 完全正常,不是程序 BUG
  2. 原因:开启O_NONBLOCK非阻塞模式,无按键事件时,read函数不等待,直接返回-1
  3. while(1)死循环 → 程序高速打印read err -1

7.2 现象 2:读取电源键设备/dev/input/event0

bustype = 0x19 vendor = 0x0 product = 0x0 version = 0x0 support ev type: EV_SYN EV_KEY
  1. 设备身份:主板虚拟电源按钮,无厂商、产品信息;
  2. bustype = 0x19:电源总线类型;
  3. 支持事件:仅EV_SYN(同步)、EV_KEY(按键),按下电源键才会触发事件;
  4. 运行模式:阻塞模式,无操作时程序卡住等待,不打印任何信息。

7.3 按键事件正常输出示例

plaintext

get event: type = 0x1, code = 0x1c, value = 0x1 // 回车键按下 get event: type = 0x0, code = 0x0, value = 0x0 // 同步事件 get event: type = 0x1, code = 0x1c, value = 0x0 // 回车键松开

八、常见问题与错误排查

表格

报错 / 现象原因解决方案
read err -1疯狂刷屏非阻塞模式无事件正常现象,改用阻塞模式,或添加休眠降低刷屏
程序卡住不动阻塞模式正常等待按下键盘 / 鼠标 / 电源键触发事件,Ctrl+C 退出
open xxx err无权限 / 设备路径错误加 sudo,更换正确设备节点(event1/event0)
编译报错 strcmp 未定义缺少头文件添加#include <string.h>

九、核心知识点总结

  1. 程序完整流程:参数检查→打开设备→查询信息→解析位图→循环读事件;
  2. read err -1是非阻塞模式的正常特征,不是错误;
  3. 阻塞模式适合监听输入,非阻塞模式适合多任务并发处理;
  4. Linux 所有输入设备统一使用struct input_event传递事件;
  5. 操作硬件设备必须加sudo获取管理员权限。
http://www.jsqmd.com/news/550337/

相关文章:

  • C#ListView数据绑定组件
  • 告别接线板!用ESim电工仿真APP在手机上搞定低压电工证实操练习(附星三角启动电路教程)
  • 大模型学习避坑指南:小白也能轻松入门并收藏这份高效进阶路线
  • Z-Image-GGUF完整教程:阿里通义文生图模型从安装到出图
  • 算一算(一)经典Miller补偿极点
  • 使用ComfyUI可视化工作流构建NLP-StructBERT语义搜索应用
  • LMX2595实战:手把手教你配置JESD204B时钟与SYSREF(含相位同步避坑指南)
  • 企业级文档数字化:Umi-OCR离线光学字符识别工具全流程落地指南
  • Genome Biology:启动子设计赋予水稻多重抗病性
  • macOS极速体验OpenClaw:nanobot镜像免配置调试技巧
  • 2026最新广东广州皮具推荐!国内优质皮具生产/批发厂商权威榜单发布 - 十大品牌榜
  • 用Python+OpenCV给斗地主做个‘外挂’:手把手教你写个桌面记牌器(附源码)
  • 如何使用Rufus创建Windows 11启动盘:完整配置指南与TPM绕过方案
  • 恶劣天候激光雷达点云模拟技术研究进展与实战应用
  • 2026最新高端女包直播供应链推荐!广东广州优质服务商权威榜单 - 十大品牌榜
  • 看完就会:2026年必备一键生成论文工具榜单,免费高效产出合规稿
  • 3分钟掌握Chrome文本替换插件:让任何网页变成你的可编辑文档
  • 品牌方必看:小红书舆情监测工具怎么选?2026年小红书舆情监测工具对比测评
  • 智能窗口管理:Boss-Key实现高效工作流的创新方案
  • 深度解析Cursor试用重置工具:解决“You‘ve reached your trial request limit“的完整方案
  • 程序员别慌!想突破职业瓶颈?2026发展十大方向,网安衔接开发技能,入门超容易
  • Vue3-Date-Time-Picker:现代化Vue 3日期时间选择器的完整技术解决方案
  • 擎云 W515x/W585x(台式机)与 L420x/L540x(笔记本)的对比
  • OpenClaw 卸载不干净,为什么可能带来凭证泄漏风险
  • STM32CubeIDE实战:HAL库串口中断接收的5个常见坑点及解决方案
  • LLM综述:Reasoning Beyond Limits: Advances and Open Problems for LLMs
  • 文件流与Excel导入导出 - 超详细讲解
  • Verilog新手避坑指南:从HDLBits的Getting Started到Vectors,我踩过的那些坑
  • BongoCat:让键盘敲击变成可爱互动的桌面伙伴
  • 攻防世界 reverse题GFSJ0810-【crazy】