外设与接口: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;
}
相关推荐
码农12138号2 小时前
Bugku HackINI 2022 Whois 详解
linux·web安全·ctf·命令执行·bugku·换行符
Joren的学习记录2 小时前
【Linux运维进阶知识】Nginx负载均衡
linux·运维·nginx
用户2190326527352 小时前
Java后端必须的Docker 部署 Redis 集群完整指南
linux·后端
胡先生不姓胡2 小时前
如何获取跨系统调用的函数调用栈
linux
里纽斯4 小时前
RK平台Watchdog硬件看门狗验证
android·linux·rk3588·watchdog·看门狗·rk平台·wtd
chem41114 小时前
魔百盒 私有网盘seafile搭建
linux·运维·网络
早睡的叶子4 小时前
VM / IREE 的调度器架构
linux·运维·架构
兄台の请冷静4 小时前
linux 安装sentinel 并加入systemctl
linux·运维·sentinel
路弥行至5 小时前
FreeRTOS任务管理详解中: FreeRTOS任务创建与删除实战教程(动态方法)
c语言·开发语言·笔记·stm32·操作系统·freertos·入门教程
skywalk81635 小时前
postmarketos一个专为智能手机和平板设备设计的开源 Linux 发行版 支持红米2
linux·智能手机·电脑