外设与接口:input子系统

1 input子系统

input 子系统是 Linux 对输入设备提供的统一驱动框架。如按键、键盘、触摸屏和鼠标等输入设备的驱动方式是类似的,当出现按键、触摸等操作时,硬件产生中断,然后 CPU 直接读取引脚电平,或通过 SPI、I2C 等通讯方式从设备的寄存器读取具体的按键值或触摸坐标,然后把这些信息提交给内核。使用 input 子系统 驱动的输入设备可以通过统一的数据结构提交给内核,该数据结构包括输入的时间、类型、代号以及具体的键值或坐标,而内核则通过 /dev/input 目录下的文件接口传递给用户空间。

2 查看输入设备(Shell)

查看设备节点:

bash 复制代码
# ls /dev/input/
by-path  event0  event1  event2  event3  event4  mice  mouse0

查看设备详情:

bash 复制代码
# cat /proc/bus/input/devices
I: Bus=0003 Vendor=046d Product=c092 Version=0111
N: Name="Logitech G102 LIGHTSYNC Gaming Mouse"
P: Phys=usb-fd840000.usb-1/input0
S: Sysfs=/devices/platform/fd840000.usb/usb4/4-1/4-1:1.0/0003:046D:C092.0001/input/input6
U: Uniq=207C31705742
H: Handlers=mouse1 event5 dmcfreq
...

寻找 Name 字段,找到想要控制的设备,记下设备对应的 Handlers (例如 event5),编写程序需要用到。

3 输入读取(C程序)

无论底层硬件是什么,应用层读取到的数据都是统一的结构体 input_event

c 复制代码
struct input_event {
    struct timeval time; // 时间戳
    __u16 type;          // 事件类型 (按键? 相对位移? 绝对位移?)
    __u16 code;          // 事件代码 (哪个键? X轴还是Y轴?)
    __s32 value;         // 事件值   (按下/松开? 移动了多少?)
};

完整代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h> // 定义了 input_event 和 EV_KEY 等宏
#include <time.h>

int main(int argc, char **argv)
{
    int fd;
    struct input_event ev;

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

    // 2. 打开设备节点 (阻塞模式)
    fd = open(argv[1], O_RDONLY);
    if (fd < 0)
    {
        perror("open device");
        return -1;
    }

    printf("Reading input events from %s...\n", argv[1]);

    // 3. 循环读取事件
    while (1)
    {
        // read 会阻塞,直到有事件发生(按下按键、移动鼠标)
        int len = read(fd, &ev, sizeof(ev));

        if (len == sizeof(ev))
        {
            struct tm *tm_info;
            char time_fmt[64];
            time_t raw_time = ev.time.tv_sec; // 获取秒数

            tm_info = localtime(&raw_time); // 转为本地时间结构体
            strftime(time_fmt, sizeof(time_fmt), "%Y-%m-%d %H:%M:%S", tm_info); // 格式化为字符串
            printf("[%s.%03ld] ", time_fmt, ev.time.tv_usec/1000);

            // 解析事件类型
            switch (ev.type)
            {
            case EV_SYN: // 同步事件 (0x00),它像一个句号,用于分隔不同时刻的事件
                printf("Type: EV_SYN (Sync)\n");
                break;

            case EV_KEY: // 按键事件 (0x01)
                printf("Type: EV_KEY, Code: %d, Value: %d (%s)\n",
                       ev.code, ev.value,
                       ev.value == 1 ? "Press" : (ev.value == 0 ? "Release" : "Repeat"));
                break;

            case EV_REL: // 相对位移 (0x02) - 鼠标
                printf("Type: EV_REL, Code: %d (%s), Value: %d\n",
                       ev.code,
                       ev.code == REL_X ? "X" : (ev.code == REL_Y ? "Y" : "Other"),
                       ev.value);
                break;

            case EV_ABS: // 绝对位移 (0x03) - 触摸屏
                printf("Type: EV_ABS, Code: %d, Value: %d\n", ev.code, ev.value);
                break;

            default:
                printf("Type: 0x%x, Code: %d, Value: %d\n", ev.type, ev.code, ev.value);
            }
        }
    }

    close(fd);
    return 0;
}

4 输入读取(IO 多路复用)

由于read事件文件操作会阻塞,那么采用这种方式就无法同时检测两个输入设备了,这种时候可以通过select或poll等IO多路复用的操作达成目的。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <time.h> 
#include <errno.h>

int main(int argc, char **argv)
{
    int fd;
    struct input_event ev;
    struct pollfd fds[1]; // 定义 poll 结构体数组(虽然这里只监听 1 个)
    int ret;

    if (argc != 2)
    {
        printf("Usage: %s <device_path>\n", argv[0]);
        printf("Example: %s /dev/input/event0\n", argv[0]);
        return -1;
    }

    // 1. 打开设备
    fd = open(argv[1], O_RDONLY);
    if (fd < 0)
    {
        perror("open device");
        return -1;
    }

    // 2. 配置 poll 监听结构体
    fds[0].fd = fd;         // 监听哪个文件?
    fds[0].events = POLLIN; // 监听什么事件? POLLIN 表示"有数据可读"

    printf("Listening on %s (Timeout: 5s)...\n", argv[1]);

    while (1)
    {
        // 3. 调用 poll (核心)
        // 参数: 结构体数组, 数组大小, 超时时间(毫秒)
        // 5000ms = 5秒
        ret = poll(fds, 1, 5000);

        if (ret == 0)
        {
            // === 情况 A: 超时 (5秒内没人动鼠标) ===
            printf("Timeout! (I am still alive, can do other jobs...)\n");
        }
        else if (ret < 0)
        {
            // === 情况 B: 出错 ===
            perror("poll error");
            break;
        }
        else
        {
            // === 情况 C: 有事件发生 (ret > 0) ===
            // 判断是否是我们监听的那个文件发生了 POLLIN 事件
            if (fds[0].revents & POLLIN)
            {
                // 此时再调用 read 不会阻塞,因为 poll 保证了有数据可读
                int len = read(fd, &ev, sizeof(ev));

                if (len == sizeof(ev))
                {
                    // 时间格式化
                    struct tm *tm_info;
                    char time_fmt[64];
                    time_t raw_time = ev.time.tv_sec;
                    tm_info = localtime(&raw_time);
                    strftime(time_fmt, sizeof(time_fmt), "%H:%M:%S", tm_info);

                    // 打印关键信息
                    printf("[%s] Type: %d, Code: %d, Value: %d\n",
                           time_fmt, ev.type, ev.code, ev.value);
                }
            }
        }
    }

    close(fd);
    return 0;
}
相关推荐
魂万劫16 小时前
如何在虚拟机VM上|Linux环境内安装windows
linux·运维·服务器·windows
序属秋秋秋16 小时前
《Linux系统编程之进程控制》【进程等待】
linux·c语言·c++·进程·系统编程·进程控制·进程等待
zfj32116 小时前
top 命令中的 wa (IO wait) 指标,理论上几乎完全是由磁盘IO(包括swap)引起的,而不是网络IO
linux·网络·top·iowait
Xの哲學17 小时前
Linux网卡注册流程深度解析: 从硬件探测到网络栈
linux·服务器·网络·算法·边缘计算
用户61354114601617 小时前
libicu-62.1-6.ky10.x86_64.rpm 安装步骤详解(麒麟V10系统)
linux
lingran__18 小时前
C语言自定义类型详解 (1.1w字版)
c语言·开发语言
秋42718 小时前
防火墙基本介绍与使用
linux·网络协议·安全·网络安全·架构·系统安全
取加若则_19 小时前
深入解析Linux进程优先级机制
linux·服务器
点亮一颗LED(从入门到放弃)19 小时前
设备模型(10)
linux·服务器·前端
Web极客码19 小时前
使用VPS主机进行数据分析的主要优势
linux·windows·vps主机