当前位置:首页>Linux>Linux(33)-输入子系统

Linux(33)-输入子系统

  • 2026-08-24 00:39:42
Linux(33)-输入子系统

Linux(33)-输入子系统

前言

在Linux系统中,无论是键盘敲击、鼠标移动、触摸屏滑动,还是游戏手柄摇杆操作,所有输入设备都通过同一套框架与系统交互——这就是Linux输入子系统(Input Subsystem)。作为内核中设计最精巧、最经典的子系统之一,它屏蔽了底层硬件差异,为上层应用提供了统一的交互接口,是构建整个人机交互生态的核心基石。本文将从架构原理、核心机制到驱动开发实战,带你全面拆解输入子系统的运作逻辑。

为什么需要输入子系统

在没有统一框架的时代,每款输入设备都需要单独编写字符设备驱动:开发者既要处理底层硬件寄存器操作,还要自行实现read、write、ioctl等用户态接口,不同设备的数据格式也无法互通。这导致了两个严重问题:

  • 驱动开发冗余:键盘、鼠标、触摸屏、游戏手柄……每种设备都要从零开始实现一套文件操作接口,大量重复劳动。

  • 应用适配成本高:上层应用程序(如X11、GUI框架)必须针对每种设备编写不同的解析逻辑,系统扩展性极差。

  • 输入子系统的出现一举解决了这些痛点,其核心价值体现在三个方面:

    • 统一抽象:将所有输入设备抽象为input_dev结构体,所有事件统一编码为{type, code, value}三元组。例如EV_KEY / KEY_A / 1就代表"A键按下",EV_ABS / ABS_X / 512代表"X轴绝对坐标为512"。
    • 分层解耦:驱动开发者只需关注硬件操作和事件上报,无需处理用户态接口;应用开发者只需读取/dev/input/eventX即可获取所有设备事件。
    • 高扩展性:新增设备只需编写设备驱动,新增事件分发方式只需实现新的处理器(Handler),两者互不影响,完美体现了Linux内核"机制与策略分离"的设计哲学

设计架构

各层职责详解

设备驱动层(最底层)

直接与物理硬件交互,负责初始化GPIO/中断控制器、配置时钟、注册中断处理函数、采集原始输入数据(如扫描码、ADC坐标值、压力值),并完成硬件去抖动等底层时序控制。它将原始硬件信号转换为标准事件,通过核心层提供的接口向上汇报。

核心层(中间层)

位于drivers/input/input.c,是整个子系统的"中枢神经"。它提供统一的设备注册/注销接口、事件分发引擎、设备与处理器的自动匹配机制,同时维护全局的设备链表和处理器链表。驱动开发者不需要直接操作这一层的大部分代码,但它是连接上下两层的桥梁。

事件处理层(最上层)

将核心层分发的标准事件流按需路由至不同的用户接口。典型的事件处理器包括:

处理器
设备节点
功能描述
evdev
/dev/input/eventX
最通用的处理器,为每个输入设备创建独立节点,支持所有事件类型,被Qt、Wayland、X11等广泛依赖
mousedev
/dev/input/mice
模拟传统PS/2鼠标协议,提供相对位移简化接口
joydev
/dev/input/jsX
适配游戏控制器
keyboard
/dev/ttyN
作为虚拟终端的键盘输入,不产生/dev/input/下的节点

核心数据结构

在Linux输入子系统中,input_dev、input_handler、input_handle是三个最核心的数据结构,它们之间的关系构成了整个输入框架的骨架。

input_dev:输入设备的"身份证"(最上层)

input_dev定义在include/linux/input.h中,代表一个具体的输入设备实例。无论是键盘、鼠标、触摸屏还是游戏手柄,在输入子系统中都被抽象为一个input_dev结构体。

structinput_dev {
constchar *name;              /* 设备名称 */
constchar *phys;              /* 设备在系统中的物理路径 */
constchar *uniq;              /* 设备唯一标识符 */
structinput_idid;/* 设备ID(总线类型、厂商、产品、版本) */

/* ===== 能力位图:声明设备支持什么 ===== */
unsignedlong propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsignedlong evbit[BITS_TO_LONGS(EV_CNT)];     /* 支持的事件类型 */
unsignedlong keybit[BITS_TO_LONGS(KEY_CNT)];   /* 支持的按键码 */
unsignedlong relbit[BITS_TO_LONGS(REL_CNT)];   /* 支持的相对轴 */
unsignedlong absbit[BITS_TO_LONGS(ABS_CNT)];   /* 支持的绝对轴 */
unsignedlong mscbit[BITS_TO_LONGS(MSC_CNT)];
unsignedlong ledbit[BITS_TO_LONGS(LED_CNT)];
unsignedlong sndbit[BITS_TO_LONGS(SND_CNT)];
unsignedlong ffbit[BITS_TO_LONGS(FF_CNT)];
unsignedlong swbit[BITS_TO_LONGS(SW_CNT)];

/* ===== 状态缓存 ===== */
unsignedlong key[BITS_TO_LONGS(KEY_CNT)];      /* 当前按键状态 */
unsignedlong led[BITS_TO_LONGS(LED_CNT)];
unsignedlong snd[BITS_TO_LONGS(SND_CNT)];
unsignedlong sw[BITS_TO_LONGS(SW_CNT)];

/* ===== 操作回调 ===== */
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsignedint type,
unsignedint code, int value);

/* ===== 链表与同步 ===== */
structinput_handle __rcu *grab;/* 当前独占设备的handle */
spinlock_t event_lock;
structmutexmutex;
unsignedint users;                /* 打开该设备的用户数 */
bool going_away;
structdevicedev;/* 嵌入的通用设备模型 */
structlist_headh_list;/* 关联的handle链表 */
structlist_headnode;/* 挂到全局input_dev_list */

/* ===== 事件批处理 ===== */
unsignedint num_vals;
unsignedint max_vals;
structinput_value *vals;
    ...
};

核心字段分类解读

字段
作用
示例
name
用户可见的设备名称
"gpio_keys"
phys
设备在系统中的物理路径
"gpio-keys/input0"
uniq
设备唯一标识(如USB序列号)
多数设备为空
id
包含bustypevendorproductversion,用于与Handler匹配
bustype=BUS_HOST

能力位图是设备驱动注册时最重要的配置,它告诉核心层和事件处理器"这个设备能产生哪些事件"。

  • vbit:声明支持的事件类型(EV_KEY、EV_ABS、EV_REL等),每一位对应一种事件类型
  • keybit:在EV_KEY基础上,进一步声明支持哪些具体按键码(KEY_A、BTN_TOUCH等)
  • relbit:声明支持的相对轴(REL_X、REL_Y等),鼠标需要设置
  • absbit:声明支持的绝对轴(ABS_X、ABS_Y、ABS_MT_SLOT等),触摸屏需要设置
  • ffbit:声明支持的力反馈效果类型

位图决定了匹配结果:Handler通过检查这些位图来判断是否能处理该设备。例如evdev的id_table中driver_info = 1表示匹配所有设备,而mousedev会检查EV_REL和BTN_MOUSE等位。

状态缓存区

  • key[]:实时反映设备当前所有按键的按下/释放状态
  • led[]:反映当前LED状态(如CapsLock灯)
  • snd[]、sw[]:分别反映声音效果和开关状态

这些状态位图在事件上报时由核心层自动更新,驱动无需手动维护。

操作回调区

回调
调用时机
典型用途
open
第一个用户打开设备时
启动硬件(开中断、启动轮询线程等)
close
最后一个用户关闭设备时
停止硬件(关中断、停轮询等)
event
收到发往设备的事件时
响应LED控制、声音播放等下行命令
flush
设备被flush时
清除力反馈效果等

链表节点区

  • node:将设备挂到全局input_dev_list链表上,核心层通过遍历该链表实现设备管理

  • h_list:挂载所有与该设备关联的input_handle,一个设备可以匹配多个Handler,因此有多个handle

生命周期管理

input_allocate_device()  →  配置字段  →  input_register_device()
                                              ↓
                                    遍历input_handler_list
                                    逐一尝试匹配(input_attach_handler)
                                              ↓
                                    匹配成功则调用handler->connect()
                                    创建input_handle并创建设备节点
                                              ↓
input_unregister_device()  →  自动清理所有关联的handle和设备节点

input_allocate_device()内部会完成:内存分配、device_initialize()、互斥锁/自旋锁/定时器初始化、链表头初始化、设备名自动编号(input0、input1...)。

input_handler:事件处理器的"模板"

input_handler定义在include/linux/input.h中,代表一类事件处理程序。它是纯软件层,不涉及任何硬件操作,职责是将内核事件传递到用户空间或内核其他子系统。

structinput_handler {
void *private;                     /* 处理器私有数据 */

/* ===== 事件处理回调 ===== */
void (*event)(struct input_handle *handle, unsignedint type,
unsignedint code, int value);
unsignedint(*events)(struct input_handle *handle,
const struct input_value *vals,
unsignedint count)
;

/* ===== 过滤器 ===== */
bool (*filter)(struct input_handle *handle, unsignedint type,
unsignedint code, int value);

/* ===== 自定义匹配 ===== */
bool (*match)(struct input_handler *handler, struct input_dev *dev);

/* ===== 连接管理 ===== */
int (*connect)(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);

bool legacy_minors;
int minor;
constchar *name;                          /* 处理器名称 */
conststructinput_device_id *id_table;/* 匹配规则表 */
conststructinput_device_id *blacklist;/* 黑名单 */
structlist_headh_list;/* 关联的handle链表 */
structlist_headnode;/* 全局input_handler_list链表 */
};

核心字段分类解读事件处理回调

回调
作用
说明
event
处理单个事件
最基础的回调,逐个处理{type, code, value}
events
批量处理事件
性能更优,一次处理多个事件,优先级高于event
filter
事件过滤/拦截
在事件到达普通handler之前拦截,返回true则丢弃该事件

核心层分发事件时,优先调用events(批量),若未实现则回退到逐个调用event。

匹配机制

匹配由两部分组成:

  • id_table:静态匹配规则表,每项是一个input_device_id结构体,包含flags(指定匹配哪些字段)、bustype、vendor、product、version以及各事件位图。核心层的input_match_device()逐项比对,全部匹配才算成功。
  • match:动态匹配回调,在id_table匹配通过后额外调用,用于实现更灵活的匹配逻辑。
  • blacklist:黑名单,匹配成功的设备若在黑名单中则被排除。

以evdev为例,它的id_table只有一项{ .driver_info = 1 },意味着匹配所有设备——这就是为什么每个输入设备都会生成/dev/input/eventX节点。

连接管理回调

回调
调用时机
典型工作
connect
设备与Handler匹配成功时
分配input_handle、创建设备节点、初始化缓冲区等
disconnect
设备注销或Handler卸载时
清理资源、删除设备节点
start
handle注册完成后
执行启动操作(如开始轮询)

链表节点

  • node:将Handler挂到全局input_handler_list链表
  • h_list:挂载所有与该Handler关联的input_handle

内核中常见的Handler实例

Handler
名称
设备节点
匹配规则
evdev
"evdev"/dev/input/eventX
匹配所有设备
mousedev
"mousedev"/dev/input/mice
匹配EV_REL + 鼠标按键
joydev
"joydev"/dev/input/jsX
匹配EV_ABS + 摇杆轴
keyboard
"kbd"/dev/ttyN
匹配键盘类设备

三、input_handle:连接设备与处理器

定义与定位

input_handle是三个结构体中最"轻量"却最关键的,它不包含任何业务逻辑,纯粹是一个连接节点。

structinput_handle {
void *private;                      /* Handler私有数据指针 */
int open;                           /* 打开计数(>0才投递事件) */
constchar *name;                   /* Handler给handle取的名字 */
structinput_dev *dev;/* 指向关联的输入设备 */
structinput_handler *handler;/* 指向关联的事件处理器 */
structlist_headd_node;/* 挂到设备的h_list */
structlist_headh_node;/* 挂到处理器的h_list */
};

各字段详解

字段
作用
private
Handler在connect时通常将自己的上下文结构体挂在这里。例如evdev_connecthandle.private = evdev,后续事件处理时通过handle->private取回evdev实例
open
打开计数器。只有open > 0时,核心层才会向该handle投递事件。用户调用open("/dev/input/eventX")时递增,close时递减
dev
反向指针,指向关联的input_dev
handler
反向指针,指向关联的input_handler
d_node
通过此节点挂到input_dev->h_list链表上
h_node
通过此节点挂到input_handler->h_list链表上

为什么需要input_handle?核心原因是多对多关系:

  • 一个设备可以匹配多个Handler(如鼠标同时匹配evdev和mousedev)
  • 一个Handler可以处理多个设备(如evdev处理所有输入设备)

如果直接让input_dev和input_handler一对一关联,就无法表达这种多对多关系。input_handle作为中间节点,通过双向链表分别挂在设备和Handler的h_list上,优雅地解决了这个问题。

通过具体例子理解三大结构体的协作流程

一、场景设定

假设我们有一个嵌入式开发板,上面接了两个硬件:

  • 一个GPIO按键(连接到GPIO 50)
  • 一个I2C触摸屏(地址0x38) 系统内核中已经内置了 evdev 和 keyboard 两个Handler。

二、设备注册:input_dev

例1:GPIO按键驱动注册,驱动开发者编写如下代码:

staticstructinput_dev *key_dev;

staticint __init gpio_key_init(void)
{
/* 第1步:分配 input_dev */
    key_dev = input_allocate_device();

/* 第2步:填写身份信息 */
    key_dev->name = "gpio_key_enter";
    key_dev->id.bustype = BUS_HOST;
    key_dev->id.vendor  = 0x0001;
    key_dev->id.product = 0x0001;

/* 第3步:声明能力位图(最关键!) */
    __set_bit(EV_KEY, key_dev->evbit);       /* 我能产生按键事件 */
    __set_bit(KEY_ENTER, key_dev->keybit);   /* 我支持Enter键 */

/* 第4步:设置回调 */
    key_dev->open  = gpio_key_open;   /* 有人读我时才开中断 */
    key_dev->close = gpio_key_close;  /* 没人读我时关中断省电 */

/* 第5步:注册到核心层 */
    input_register_device(key_dev);
}

注册时核心层内部发生了什么?

// drivers/input/input.c 中的简化逻辑
intinput_register_device(struct input_dev *dev)
{
// ① 把 dev 挂到全局链表 input_dev_list 上
    list_add_tail(&dev->node, &input_dev_list);

// ② 遍历已注册的所有 handler,逐一尝试匹配
    list_for_each_entry(handler, &input_handler_list, node) {
        input_attach_handler(dev, handler);
    }
}

此时系统中 input_handler_list 上已经有 evdev 和 keyboard 两个Handler,核心层会逐一匹配:

匹配 evdev: → evdev 的 id_table 是 { .driver_info = 1 },表示"来者不拒" → 匹配成功!调用 evdev->connect()

匹配 keyboard: → keyboard 检查 keybit 中是否包含键盘相关键码 → KEY_ENTER 属于键盘键码,匹配成功!调用 keyboard->connect()

evdev->connect() 内部做了什么?

staticintevdev_connect(struct input_handler *handler,
                         struct input_dev *dev,
const struct input_device_id *id)

{
// ① 分配一个 evdev 实例(Handler的私有上下文)
structevdev *evdev = kzalloc(sizeof(*evdev), GFP_KERNEL);

// ② 分配一个 input_handle(连接器)
structinput_handle *handle = kzalloc(sizeof(*handle), GFP_KERNEL);

// ③ 建立三者之间的引用关系(最关键!)
    handle->dev     = dev;          // handle 指向设备
    handle->handler = handler;      // handle 指向处理器
    handle->private = evdev;        // handle 携带Handler的私有数据

// ④ 注册 handle(把它挂到两端的链表上)
    input_register_handle(handle);
// 内部实现:
//   list_add_rcu(&handle->d_node, &dev->h_list);      // 挂到设备的链表
//   list_add(&handle->h_node, &handler->h_list);       // 挂到Handler的链表

// ⑤ 创建用户态可见的设备节点
    cdev_init(&evdev->cdev, &evdev_fops);
    device_create(evdev_class, &dev->dev, devt, NULL"event%d", minor);
// 结果:/dev/input/event0 诞生了!
}

keyboard->connect() 类似

staticintkbd_connect(struct input_handler *handler,
                       struct input_dev *dev,
const struct input_device_id *id)

{
structinput_handle *handle = kzalloc(sizeof(*handle), GFP_KERNEL);

    handle->dev     = dev;
    handle->handler = handler;
    handle->private = kbd_data;    // keyboard的私有数据

    input_register_handle(handle);
// 挂到 dev->h_list 和 keyboard->h_list 上
// 注意:keyboard 不创建 /dev/input/ 下的节点,它绑定到 /dev/ttyN
}

注册完成后的内存状态:

全局 input_dev_list:
┌─────────────────────────────────────────────┐
│ input_dev "gpio_key_enter"                  │
│   name = "gpio_key_enter"                   │
│   evbit = [EV_KEY]                          │
│   keybit = [KEY_ENTER]                      │
│   open() = gpio_key_open                    │
│   close() = gpio_key_close                  │
│   h_list ──→ ┌──────────────────────┐       │
│              │ input_handle #1      │       │
│              │   dev ──→ 自己       │       │
│              │   handler ──→ evdev  │       │
│              │   private ──→ evdev实例│      │
│              │   open = 0(无人读取)│       │
│              │   d_node ← 挂在h_list│       │
│              ├──────────────────────┤       │
│              │ input_handle #2      │       │
│              │   dev ──→ 自己       │       │
│              │   handler ──→ kbd    │       │
│              │   private ──→ kbd_data│      │
│              │   open = 0           │       │
│              │   d_node ← 挂在h_list│       │
│              └──────────────────────┘       │
└─────────────────────────────────────────────┘

全局 input_handler_list:
┌─────────────────────────────────────────────┐
│ input_handler "evdev"                       │
│   h_list ──→ [input_handle #1] ──→ ...      │
├─────────────────────────────────────────────┤
│ input_handler "kbd"                         │
│   h_list ──→ [input_handle #2] ──→ ...      │
└─────────────────────────────────────────────┘

三、用户打开设备handle->open 的变化

用户态程序执行:

cat /dev/input/event0

这触发了 evdev 的文件操作:

// evdev.c
staticintevdev_open(struct inode *inode, struct file *file)
{
structevdev *evdev = container_of(inode->i_cdevstructevdevcdev);
structevdev_client *client = kzalloc(...);// 分配客户端缓冲区

// 找到对应的 handle
structinput_handle *handle = ...;// 通过evdev找到

// 核心操作:调用 input_open_device
    input_open_device(handle);
// 内部实现:
//   handle->open++;               // open计数 0→1
//   if (handle->open == 1)
//       dev->open(dev);           // 第一次打开,调用设备的open回调!
//                                 // → gpio_key_open() 被调用
//                                 // → 申请中断、启动硬件
}

关键点:handle->open 从0变为1时,核心层自动调用 dev->open()。这就是"按需供电"机制——没人读事件时硬件可以休眠,有人读时才启动。 此时 input_handle #1 的状态变为:

input_handle #1:
  dev ──→ gpio_key_enter
  handler ──→ evdev
  open = 1          ← 从0变为1,事件开始投递到这个handle
private ──→ evdev实例

而 input_handle #2(keyboard)的 open 仍为0,所以事件不会投递给keyboard(除非tty终端也打开了它)。

四、事件上报:三者协作的高潮时刻

用户按下了GPIO按键,硬件产生中断。以下是事件从硬件到用户态的完整流转:第1步:设备驱动层——中断服务程序

staticirqreturn_tgpio_key_isr(int irq, void *dev_id)
{
int value = gpio_get_value(50);

// 调用输入子系统提供的上报函数
    input_report_key(key_dev, KEY_ENTER, !value);
//  ↑ 这个函数内部做了什么?
//  input_report_key() → input_event(dev, EV_KEY, KEY_ENTER, !value)

    input_sync(key_dev);
//  ↑ 等价于 input_event(dev, EV_SYN, SYN_REPORT, 0)
}

第2步:核心层——事件分发引擎input_event() 内部调用链:

voidinput_event(struct input_dev *dev, unsignedint type,
unsignedint code, int value)

{
// ① 更新设备状态缓存
if (type == EV_KEY)
        __set_bit(value ? code : 0, dev->key);  // 更新按键状态位图

// ② 将事件暂存到设备的批量缓冲区
    dev->vals[dev->num_vals++] = { type, code, value };

// ③ 调用 input_handle_event 处理
    input_handle_event(dev, type, code, value);
}

// input_sync() 触发时,缓冲区中有两个事件:
//   vals[0] = { EV_KEY, KEY_ENTER, 1 }
//   vals[1] = { EV_SYN, SYN_REPORT, 0 }
// 然后调用:
voidinput_pass_values(struct input_dev *dev,
const struct input_value *vals,
unsignedint count)

{
structinput_handle *handle;

// ④ 检查是否有独占(grab)
    handle = rcu_dereference(dev->grab);
if (handle) {
// 有独占:只投递给这一个handle
        handle->handler->event(handle, type, code, value);
return;
    }

// ⑤ 无独占:遍历设备的所有handle
    rcu_read_lock();
    list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
// 关键判断:这个handle有没有被打开?
if (handle->open) {
// open > 0,投递事件!
            handle->handler->events(handle, vals, count);
// 或 handle->handler->event(handle, type, code, value);
        }
// open == 0 的handle直接跳过,不浪费CPU
    }
    rcu_read_unlock();
}

这里就是三者协作的核心! 核心层做的事非常简单:

  • 从 dev->h_list 取出所有 input_handle
  • 检查每个 handle->open 是否大于0
  • 通过 handle->handler 找到对应的Handler,调用其事件处理回调

第3步:事件处理器层——evdev处理事件input_event() 内部调用链:

// evdev.c
staticunsignedintevdev_events(struct input_handle *handle,
const struct input_value *vals,
unsignedint count)

{
// 通过 handle->private 取回自己的上下文
structevdev *evdev = handle->private;
structevdev_client *client;

// 遍历所有正在读取的客户端
    rcu_read_lock();
    list_for_each_entry_rcu(client, &evdev->client_list, node) {
// 将事件写入客户端的环形缓冲区
for (i = 0; i < count; i++) {
            client->buffer[head].time  = ...;
            client->buffer[head].type  = vals[i].type;
            client->buffer[head].code  = vals[i].code;
            client->buffer[head].value = vals[i].value;
        }
// 唤醒正在阻塞等待的用户进程
        wake_up_interruptible(&client->wait);
    }
    rcu_read_unlock();
}

第4步:用户态——读取事件

// 用户态程序
structinput_eventev;
read(fd, &ev, sizeof(ev));

// 此时 ev 的内容为:
// ev.type  = EV_KEY (0x01)
// ev.code  = KEY_ENTER (28)
// ev.value = 1  (按下)
//
// 紧接着再 read 一次:
// ev.type  = EV_SYN (0x00)
// ev.code  = SYN_REPORT (0)
// ev.value = 0

五、完整流程串联图把上面的例子串起来,一次按键的完整生命周期:

时间线 ──────────────────────────────────────────────────────→

① 模块加载
   gpio_key_init()
     │
     ├─ input_allocate_device() → 创建 input_dev "gpio_key_enter"
     ├─ 设置 evbit=[EV_KEY], keybit=[KEY_ENTER]
     └─ input_register_device()
           │
           ├─ dev 加入 input_dev_list
           └─ 遍历 handler_list,逐一匹配
                 │
                 ├─ evdev 匹配成功 → connect()
                 │    ├─ 创建 input_handle #1
                 │    ├─ handle#1.dev = dev
                 │    ├─ handle#1.handler = evdev
                 │    ├─ 挂到 dev->h_list 和 evdev->h_list
                 │    └─ 创建 /dev/input/event0
                 │
                 └─ keyboard 匹配成功 → connect()
                      ├─ 创建 input_handle #2
                      ├─ handle#2.dev = dev
                      ├─ handle#2.handler = keyboard
                      └─ 挂到 dev->h_list 和 keyboard->h_list

② 用户打开设备
   open("/dev/input/event0")
     │
     └─ evdev_open()
           └─ input_open_device(handle#1)
                 ├─ handle#1.open: 0 → 1
                 └─ dev->open() → gpio_key_open() → 申请GPIO中断

③ 用户按下按键
   GPIO中断触发
     │
     └─ gpio_key_isr()
           ├─ input_report_key(dev, KEY_ENTER, 1)
           │    └─ input_event(dev, EV_KEY, KEY_ENTER, 1)
           │         └─ input_pass_values()
           │               │
           │               ├─ 遍历 dev->h_list:
           │               │
           │               ├─ handle#1: open=1 ✓
           │               │    └─ evdev->events()
           │               │         └─ 写入客户端缓冲区
           │               │         └─ wake_up() 唤醒用户进程
           │               │
           │               └─ handle#2: open=0 ✗ 跳过!
           │
           └─ input_sync(dev)
                └─ input_event(dev, EV_SYN, SYN_REPORT, 0)
                     └─ 同上流程,发送同步事件

④ 用户态读取
   read(fd, &ev, sizeof(ev))
     │
     └─ evdev_read()
           └─ 从缓冲区取出 struct input_event 拷贝到用户空间
              → { EV_KEY, KEY_ENTER, 1 }
              → { EV_SYN, SYN_REPORT, 0 }

⑤ 用户关闭设备
   close(fd)
     │
     └─ evdev_release()
           └─ input_close_device(handle#1)
                 ├─ handle#1.open: 1 → 0
                 └─ dev->close() → gpio_key_close() → 释放中断、硬件休眠

触摸屏的例子——对比理解差异

触摸屏驱动注册时,能力配置不同:

// 触摸屏驱动
__set_bit(EV_ABS, touch_dev->evbit);       /* 绝对坐标事件 */
__set_bit(EV_KEY, touch_dev->evbit);       /* 按键事件 */
__set_bit(BTN_TOUCH, touch_dev->keybit);   /* 触摸按键 */
input_set_abs_params(touch_dev, ABS_X, 0102300);
input_set_abs_params(touch_dev, ABS_Y, 076700);

注册时匹配结果:

evdev 匹配成功 → 创建 handle → /dev/input/event1
mousedev 匹配?→ 检查 keybit 是否有 BTN_MOUSE → BTN_TOUCH ≠ BTN_MOUSE → 不匹配!
keyboard 匹配?→ 检查 keybit 是否有键盘键码 → BTN_TOUCH 不是键盘键码 → 不匹配!

所以触摸屏只匹配了 evdev 一个Handler,它的 h_list 上只有一个 input_handle。 用户触摸屏幕时:

input_report_abs(touch_dev, ABS_X, 512);   // X坐标
input_report_abs(touch_dev, ABS_Y, 384);   // Y坐标
input_report_key(touch_dev, BTN_TOUCH, 1); // 触摸按下
input_sync(touch_dev);                     // 同步

核心层分发时只遍历到一个handle(evdev),直接投递,用户态读到:

{ EV_ABS, ABS_X, 512 }
{ EV_ABS, ABS_Y, 384 }
{ EV_KEY, BTN_TOUCH, 1 }
{ EV_SYN, SYN_REPORT, 0 }

结构体
一句话
类比
input_dev
"我是谁,我能产生什么事件"
一个歌手
input_handler
"我能处理什么设备,我拿到事件后怎么往外送"
一个直播平台
input_handle
"这个歌手签约了这个平台"
一份签约合同
  • 歌手(input_dev)注册时,平台(input_handler)检查歌手的风格(能力位图)是否符合自己的定位
  • 符合就签合同(创建 input_handle),把合同分别存档在歌手档案(dev->h_list)和平台合约库(handler->h_list)里
  • 歌手演出(上报事件)时,按照合同列表逐一通知所有签约平台,平台再推送给各自的观众(用户态进程)
  • 合同上有个"是否生效"标记(handle->open),没人看的平台就不通知了,节省精力

例子

以下以一个GPIO按键为例,从设备树描述、内核驱动编写到用户态应用读取,完整展示输入子系统的全链路。

一、设备树(Device Tree)

设备树负责描述硬件信息,告诉驱动"按键接在哪个GPIO上、什么电平触发"。

/ {
    gpio_keys {
        compatible = "gpio-keys";          /* 匹配驱动的compatible字段 */
#address-cells = <1>;
#size-cells = <0>;
        autorepeat;                        /* 启用内核自动重复功能 */

        key_enter {
            label = "Enter Key";           /* 按键名称,会出现在 /proc/bus/input/devices */
            gpios = <&gpio1 50 GPIO_ACTIVE_LOW>;  /* GPIO1的第50号引脚,低电平有效 */
            linux,code = <28>;            /* 映射为KEY_ENTER(28) */
            debounce-interval = <20>;      /* 去抖动20ms */
            wakeup-source;                 /* 可作为唤醒源 */
        };

        key_volumeup {
            label = "Volume Up Key";
            gpios = <&gpio1 51 GPIO_ACTIVE_LOW>;
            linux,code = <115>;            /* KEY_VOLUMEUP */
            debounce-interval = <20>;
        };

        key_volumedown {
            label = "Volume Down Key";
            gpios = <&gpio1 52 GPIO_ACTIVE_LOW>;
            linux,code = <114>;            /* KEY_VOLUMEDOWN */
            debounce-interval = <20>;
        };
    };
};

设备树解析

字段
含义
compatible = "gpio-keys"
内核通过此字符串匹配驱动,类似USB的VID/PID
gpios = <&gpio1 50 GPIO_ACTIVE_LOW>
指定GPIO控制器(gpio1)、引脚号(50)、有效电平(低电平=按下)
linux,code = <28>
该按键映射到Linux输入子系统的哪个键码(28 = KEY_ENTER)
debounce-interval
硬件去抖时间,避免机械触点抖动产生多次中断
autorepeat
长按时内核自动生成重复事件(EV_REP)
wakeup-source
系统休眠时该按键可唤醒系统

二、内核驱动层

驱动层分为两部分:平台驱动框架(负责匹配设备树、管理生命周期)和输入子系统接口(负责事件上报)。

完整驱动代码

/*
 * gpio_keys_custom.c - 自定义GPIO按键输入驱动
 *
 * 功能:读取设备树中描述的GPIO按键,将硬件中断转换为
 *       Linux输入子系统的标准事件,上报到用户空间。
 */


#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/input.h>
#include<linux/gpio.h>
#include<linux/interrupt.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/slab.h>
#include<linux/timer.h>
#include<linux/pm_wakeirq.h>

/* ===== 按键私有数据结构 ===== */
structgpio_key_data {
int gpio;                    /* GPIO编号 */
int irq;                     /* 对应的中断号 */
unsignedint keycode;        /* Linux键码(如KEY_ENTER) */
constchar *label;           /* 按键名称 */
structinput_dev *input;/* 所属的 input_dev(所有按键共享) */
structtimer_listdebounce_timer;/* 软件去抖定时器 */
int active_low;              /* 是否低电平有效 */
};

/* 整个设备的全局数据 */
structgpio_keys_drvdata {
structinput_dev *input;/* 输入设备实例 */
structgpio_key_data *keys;/* 按键数组 */
int num_keys;                         /* 按键数量 */
};

/* ===== 软件去抖定时器回调 ===== */
staticvoidgpio_key_debounce(unsignedlong data)
{
structgpio_key_data *key = (structgpio_key_data *)data;
int level = gpio_get_value(key->gpio);
int pressed = key->active_low ? !level : level;

/* 上报按键事件到输入子系统核心层 */
    input_report_key(key->input, key->keycode, pressed);

/* 发送同步事件,标记本帧结束 */
    input_sync(key->input);

    pr_debug("gpio_keys: %s %s (code=%d)\n",
             key->label, pressed ? "pressed" : "released", key->keycode);
}

/* ===== 中断服务程序 ===== */
staticirqreturn_tgpio_key_isr(int irq, void *dev_id)
{
structgpio_key_data *key = dev_id;

/*
     * 不在中断中直接上报事件,而是启动定时器延迟处理。
     * 这样做有两个好处:
     * 1. 实现软件去抖(20ms后电平稳定再读取)
     * 2. 避免在中断上下文中执行耗时操作
     */

    mod_timer(&key->debounce_timer, jiffies + msecs_to_jiffies(20));

return IRQ_HANDLED;
}

/* ===== input_dev 的 open 回调 ===== */
staticintgpio_keys_open(struct input_dev *input)
{
structgpio_keys_drvdata *ddata = input_get_drvdata(input);
int i, ret;

/* 第一个用户打开设备时,申请所有按键的中断 */
for (i = 0; i < ddata->num_keys; i++) {
structgpio_key_data *key = &ddata->keys[i];

        ret = request_any_context_irq(key->irq, gpio_key_isr,
                                       IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                                       key->label, key);
if (ret < 0) {
            pr_err("gpio_keys: failed to request IRQ %d for %s\n",
                   key->irq, key->label);
goto err_free_irqs;
        }
    }

    pr_info("gpio_keys: all interrupts enabled, device active\n");
return0;

err_free_irqs:
while (--i >= 0)
        free_irq(ddata->keys[i].irq, &ddata->keys[i]);
return ret;
}

/* ===== input_dev 的 close 回调 ===== */
staticvoidgpio_keys_close(struct input_dev *input)
{
structgpio_keys_drvdata *ddata = input_get_drvdata(input);
int i;

/* 最后一个用户关闭设备时,释放所有中断,硬件进入低功耗 */
for (i = 0; i < ddata->num_keys; i++)
        free_irq(ddata->keys[i].irq, &ddata->keys[i]);

    pr_info("gpio_keys: all interrupts freed, device suspended\n");
}

/* ===== 平台驱动 probe 函数 ===== */
staticintgpio_keys_probe(struct platform_device *pdev)
{
structdevice *dev = &pdev->dev;
structdevice_node *np = dev->of_node;
structgpio_keys_drvdata *ddata;
structinput_dev *input;
structgpio_key_data *keys;
int num_keys, i, ret;

/* ---- 第1步:统计设备树中定义了多少个按键子节点 ---- */
    num_keys = of_get_child_count(np);
if (num_keys == 0) {
        dev_err(dev, "no key nodes found in device tree\n");
return -EINVAL;
    }

/* ---- 第2步:分配驱动数据 ---- */
    ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;

    keys = devm_kcalloc(dev, num_keys, sizeof(*keys), GFP_KERNEL);
if (!keys)
return -ENOMEM;

    ddata->keys = keys;
    ddata->num_keys = num_keys;

/* ---- 第3步:分配并初始化 input_dev ---- */
    input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;

    input->name = "gpio_keys_custom";
    input->phys = "gpio_keys_custom/input0";
    input->id.bustype = BUS_HOST;
    input->id.vendor  = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

/* 绑定 open/close 回调 */
    input->open  = gpio_keys_open;
    input->close = gpio_keys_close;

/* 保存私有数据,供 open/close 中取回 */
    input_set_drvdata(input, ddata);

/* 声明支持自动重复事件 */
if (of_property_read_bool(np, "autorepeat"))
        __set_bit(EV_REP, input->evbit);

/* ---- 第4步:逐个解析设备树中的按键节点 ---- */
    i = 0;
    for_each_child_of_node(np, child) {
enum of_gpio_flags flags;

/* 读取GPIO编号和极性 */
        keys[i].gpio = of_get_gpio_flags(np, i, &flags);
if (!gpio_is_valid(keys[i].gpio)) {
            dev_err(dev, "invalid GPIO for key %d\n", i);
return -EINVAL;
        }
        keys[i].active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;

/* 读取键码 */
        ret = of_property_read_u32(child, "linux,code", &keys[i].keycode);
if (ret) {
            dev_err(dev, "missing linux,code for key %d\n", i);
return ret;
        }

/* 读取名称 */
        keys[i].label = "unknown";
        of_property_read_string(child, "label", &keys[i].label);

/* 关联 input_dev */
        keys[i].input = input;

/* 申请GPIO资源并设置为输入 */
        ret = devm_gpio_request_one(dev, keys[i].gpio,
                                     GPIOF_IN, keys[i].label);
if (ret) {
            dev_err(dev, "failed to request GPIO %d\n", keys[i].gpio);
return ret;
        }

/* 获取对应的中断号 */
        keys[i].irq = gpio_to_irq(keys[i].gpio);

/* 初始化软件去抖定时器 */
        setup_timer(&keys[i].debounce_timer,
                    gpio_key_debounce,
                    (unsignedlong)&keys[i]);

/* ===== 关键:声明设备能力位图 ===== */
        __set_bit(EV_KEY, input->evbit);          /* 支持按键事件 */
        __set_bit(keys[i].keycode, input->keybit); /* 支持该键码 */

        dev_info(dev, "key[%d]: gpio=%d irq=%d code=%u label=%s\n",
                 i, keys[i].gpio, keys[i].irq,
                 keys[i].keycode, keys[i].label);

        i++;
    }

/* ---- 第5步:注册 input_dev 到输入子系统 ---- */
/*
     * input_register_device() 内部会:
     * 1. 将 input_dev 加入全局 input_dev_list
     * 2. 遍历 input_handler_list,逐一匹配
     * 3. evdev 匹配成功后自动创建 /dev/input/eventX
     */

    ret = input_register_device(input);
if (ret) {
        dev_err(dev, "failed to register input device\n");
return ret;
    }

    platform_set_drvdata(pdev, ddata);

    dev_info(dev, "gpio_keys_custom: registered %d keys\n", num_keys);
return0;
}

/* ===== 平台驱动 remove 函数 ===== */
staticintgpio_keys_remove(struct platform_device *pdev)
{
structgpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
int i;

/* 注销 input_dev(内部会自动断开所有 handler 连接) */
    input_unregister_device(ddata->input);

/* 清理定时器 */
for (i = 0; i < ddata->num_keys; i++)
        del_timer_sync(&ddata->keys[i].debounce_timer);

    dev_info(&pdev->dev, "gpio_keys_custom: removed\n");
return0;
}

/* ===== 设备树匹配表 ===== */
staticconststructof_device_idgpio_keys_of_match[] = {
    { .compatible = "gpio-keys-custom", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

/* ===== 平台驱动结构体 ===== */
staticstructplatform_drivergpio_keys_driver = {
    .probe  = gpio_keys_probe,
    .remove = gpio_keys_remove,
    .driver = {
        .name = "gpio-keys-custom",
        .of_match_table = gpio_keys_of_match,
    },
};

module_platform_driver(gpio_keys_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Custom GPIO Keys Input Driver");

驱动层数据流总结

设备树                     驱动probe()                    输入子系统
──────                     ──────────                    ──────────
gpio-keys-custom  ──匹配──→ platform_driver.probe()
  ├─ key_enter              ├─ input_allocate_device()
  │  gpio=<&gpio1 50>       ├─ 解析每个子节点
  │  linux,code=<28>        │   ├─ gpio_request()
  │                         │   ├─ gpio_to_irq()
  │                         │   └─ __set_bit(KEY_ENTER, keybit)
  │                         ├─ input_register_device()
  │                         │   ├─ 加入 input_dev_list
  │                         │   └─ evdev匹配 → 创建 /dev/input/event0
  │                         │
  ├─ key_volumeup           │
  │  gpio=<&gpio1 51>       │
  │  linux,code=<115>       │
  │                         │
  └─ key_volumedown         │
     gpio=<&gpio1 52>       │
     linux,code=<114>       │
                            │
用户按下Enter键 ──→  GPIO中断 ──→ gpio_key_isr()
                                    │
                                    ├─ mod_timer(20ms去抖)
                                    │
                                    └─ gpio_key_debounce()
                                         ├─ input_report_key(input, KEY_ENTER, 1)
                                         └─ input_sync(input)
                                              │
                                              └─ 核心层分发到 evdev
                                                   └─ /dev/input/event0 可读

三、用户态应用层

读取并打印所有事件

/*
 * input_reader.c - 输入子系统事件读取工具
 *
 * 功能:打开 /dev/input/eventX,实时读取并打印所有输入事件
 * 编译:gcc -o input_reader input_reader.c
 * 运行:./input_reader /dev/input/event0
 */


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<linux/input.h>
#include<sys/select.h>

/* 事件类型名称映射表 */
staticconstchar *ev_type_name(unsignedint type)
{
switch (type) {
case EV_SYN:  return"SYN";
case EV_KEY:  return"KEY";
case EV_REL:  return"REL";
case EV_ABS:  return"ABS";
case EV_MSC:  return"MSC";
case EV_LED:  return"LED";
case EV_SND:  return"SND";
case EV_REP:  return"REP";
case EV_FF:   return"FF";
default:      return"???";
    }
}

/* 常见按键名称映射(部分) */
staticconstchar *key_name(unsignedint code)
{
switch (code) {
case KEY_ENTER:      return"ENTER";
case KEY_ESC:        return"ESC";
case KEY_SPACE:      return"SPACE";
case KEY_UP:         return"UP";
case KEY_DOWN:       return"DOWN";
case KEY_LEFT:       return"LEFT";
case KEY_RIGHT:      return"RIGHT";
case KEY_VOLUMEUP:   return"VOL_UP";
case KEY_VOLUMEDOWN: return"VOL_DOWN";
case KEY_POWER:      return"POWER";
case BTN_TOUCH:      return"TOUCH";
case BTN_LEFT:       return"BTN_LEFT";
case BTN_RIGHT:      return"BTN_RIGHT";
default:             returnNULL;
    }
}

intmain(int argc, char *argv[])
{
int fd;
structinput_eventev;
constchar *dev_path;

if (argc < 2) {
fprintf(stderr"用法: %s <设备节点>\n", argv[0]);
fprintf(stderr"示例: %s /dev/input/event0\n", argv[0]);
return1;
    }

    dev_path = argv[1];

/* 打开输入设备节点 */
    fd = open(dev_path, O_RDONLY);
if (fd < 0) {
        perror("open");
return1;
    }

printf("已打开设备: %s (fd=%d)\n", dev_path, fd);
printf("等待事件... (Ctrl+C 退出)\n");
printf("%-12s %-8s %-8s %-8s %s\n",
"时间""类型""编码""值""描述");
printf("----------------------------------------------------\n");

/* 循环读取事件 */
while (1) {
ssize_t n = read(fd, &ev, sizeof(ev));
if (n != sizeof(ev)) {
if (n < 0)
                perror("read");
break;
        }

/* 打印事件信息 */
constchar *name = key_name(ev.code);
printf("%ld.%06ld  %-8s %-8u %-8d %s%s%s\n",
               ev.time.tv_sec, ev.time.tv_usec,
               ev_type_name(ev.type),
               ev.code,
               ev.value,
               name ? name : "",
               (ev.type == EV_KEY && name) ?
                   (ev.value ? " [按下]" : " [释放]") : "",
               (ev.type == EV_SYN) ? " --- 帧结束 ---" : "");
    }

    close(fd);
return0;
}

另一种版本

/*
 * input_monitor.c - 生产级输入事件监控程序
 *
 * 功能:
 *   1. 独占模式打开设备(EVIOCGRAB),阻止其他进程接收事件
 *   2. 非阻塞 I/O + select 多路复用
 *   3. 信号处理(Ctrl+C 优雅退出并释放独占)
 *   4. 查询设备能力信息
 *
 * 编译:gcc -o input_monitor input_monitor.c
 * 运行:sudo ./input_monitor /dev/input/event0
 */


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<sys/select.h>
#include<sys/ioctl.h>
#include<linux/input.h>

staticvolatileint running = 1;
staticint g_fd = -1;

/* 信号处理函数 */
staticvoidsignal_handler(int sig)
{
    (void)sig;
    running = 0;
}

/* 查询并打印设备能力 */
staticvoidprint_device_caps(int fd)
{
char name[256] = "Unknown";
char phys[256] = "Unknown";
unsignedlong evbit[EV_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong keybit[KEY_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong absbit[ABS_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong relbit[REL_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
structinput_idid;
int i;

/* 获取设备基本信息 */
    ioctl(fd, EVIOCGNAME(sizeof(name)), name);
    ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys);
    ioctl(fd, EVIOCGID, &id);

printf("========== 设备信息 ==========\n");
printf("名称: %s\n", name);
printf("物理路径: %s\n", phys);
printf("总线类型: 0x%04x\n", id.bustype);
printf("厂商ID:   0x%04x\n", id.vendor);
printf("产品ID:   0x%04x\n", id.product);
printf("版本:     0x%04x\n", id.version);

/* 获取能力位图 */
    ioctl(fd, EVIOCGBIT(0sizeof(evbit)), evbit);
    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit);
    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit);
    ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit);

printf("\n支持的事件类型:");
for (i = 0; i <= EV_MAX; i++) {
if (evbit[i / (8 * sizeof(unsignedlong))] &
            (1UL << (i % (8 * sizeof(unsignedlong))))) {
printf(" 0x%02X", i);
        }
    }
printf("\n");

/* 打印支持的按键 */
printf("支持的按键:");
for (i = 0; i <= KEY_MAX; i++) {
if (keybit[i / (8 * sizeof(unsignedlong))] &
            (1UL << (i % (8 * sizeof(unsignedlong))))) {
printf(" %d", i);
        }
    }
printf("\n");

/* 打印绝对轴信息 */
for (i = 0; i <= ABS_MAX; i++) {
if (absbit[i / (8 * sizeof(unsignedlong))] &
            (1UL << (i % (8 * sizeof(unsignedlong))))) {
structinput_absinfoabsinfo;
            ioctl(fd, EVIOCGABS(i), &absinfo);
printf("绝对轴 %d: min=%d max=%d fuzz=%d flat=%d resolution=%d\n",
                   i, absinfo.minimum, absinfo.maximum,
                   absinfo.fuzz, absinfo.flat, absinfo.resolution);
        }
    }

printf("==============================\n\n");
}

intmain(int argc, char *argv[])
{
int fd;
structinput_eventev;
constchar *dev_path;
int grab = 0;   /* 是否启用独占模式 */
int opt;

/* 解析命令行参数 */
while ((opt = getopt(argc, argv, "g")) != -1) {
switch (opt) {
case'g':
            grab = 1;
break;
default:
fprintf(stderr"用法: %s [-g] <设备节点>\n", argv[0]);
fprintf(stderr"  -g  启用独占模式(EVIOCGRAB)\n");
return1;
        }
    }

if (optind >= argc) {
fprintf(stderr"错误: 请指定设备节点\n");
return1;
    }
    dev_path = argv[optind];

/* 以非阻塞模式打开设备 */
    fd = open(dev_path, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
        perror("open");
return1;
    }
    g_fd = fd;

/* 注册信号处理 */
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

/* 打印设备能力 */
    print_device_caps(fd);

/* 独占模式:阻止其他进程接收该设备的事件 */
if (grab) {
if (ioctl(fd, EVIOCGRAB, (void *)1) < 0) {
            perror("EVIOCGRAB");
fprintf(stderr"提示: 独占模式需要 root 权限\n");
            close(fd);
return1;
        }
printf("[独占模式] 已独占设备,其他进程将无法接收事件\n");
    }

printf("开始监控事件... (Ctrl+C 退出)\n\n");

/* 主循环:使用 select 实现非阻塞等待 */
while (running) {
        fd_set readfds;
structtimevaltimeout = { .tv_sec = 1, .tv_usec = 0 };

        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

int ret = select(fd + 1, &readfds, NULLNULL, &timeout);

if (ret < 0) {
if (errno == EINTR)
continue;  /* 被信号中断,检查 running 标志 */
            perror("select");
break;
        }

if (ret == 0)
continue;  /* 超时,继续循环 */

/* 有数据可读,读取所有可用事件 */
while (1) {
ssize_t n = read(fd, &ev, sizeof(ev));
if (n != sizeof(ev)) {
if (n < 0 && errno != EAGAIN)
                    perror("read");
break;  /* 没有更多数据 */
            }

/* 过滤同步事件(仅用于标记帧边界,不打印) */
if (ev.type == EV_SYN)
continue;

printf("[%ld.%06ld] type=0x%02X code=0x%03X value=%d\n",
                   ev.time.tv_sec, ev.time.tv_usec,
                   ev.type, ev.code, ev.value);

/* 按键事件特殊处理 */
if (ev.type == EV_KEY) {
printf("  → 按键 %d %s\n", ev.code,
                       ev.value ? "按下" : (ev.value == 0 ? "释放" : "长按重复"));
            }

/* 绝对坐标事件 */
if (ev.type == EV_ABS) {
printf("  → 轴 %d = %d\n", ev.code, ev.value);
            }
        }
    }

/* 释放独占 */
if (grab) {
        ioctl(fd, EVIOCGRAB, (void *)0);
printf("\n[独占模式] 已释放设备\n");
    }

    close(fd);
printf("程序退出\n");
return0;
}

流程

┌──────────────────────────────────────────────────────────────┐
│                        物理硬件层                             │
│                                                              │
│   [用户手指按下按键]                                          │
│        │                                                     │
│        ▼                                                     │
│   GPIO1_50 引脚电平: HIGH → LOW(低电平有效)                 │
│        │                                                     │
│        ▼                                                     │
│   GPIO控制器检测到边沿变化,触发硬件中断                       │
└──────────────────────────────────────────────────────────────┘
        │
        ▼ IRQ #138(假设)
┌──────────────────────────────────────────────────────────────┐
│                     内核驱动层                                │
│                                                              │
│   gpio_key_isr()                                             │
│     │                                                        │
│     ├─ mod_timer(20ms)  ← 启动去抖定时器                     │
│     └─ return IRQ_HANDLED                                    │
│                                                              │
│   ... 20ms后 ...                                             │
│                                                              │
│   gpio_key_debounce()                                        │
│     │                                                        │
│     ├─ gpio_get_value(50) → 0(低电平 = 按下)               │
│     ├─ pressed = !0 = 1                                      │
│     │                                                        │
│     ├─ input_report_key(input, KEY_ENTER, 1)                 │
│     │    └─ input_event(dev, EV_KEY, 28, 1)                  │
│     │         └─ 核心层:更新 dev->key 状态位图               │
│     │              暂存到 dev->vals 缓冲区                   │
│     │                                                        │
│     └─ input_sync(input)                                     │
│          └─ input_event(dev, EV_SYN, SYN_REPORT, 0)          │
│               └─ 触发 input_pass_values()                    │
│                    │                                         │
│                    ├─ 遍历 dev->h_list                       │
│                    │                                         │
│                    ├─ handle#1 (evdev): open=1 ✓             │
│                    │    └─ evdev_events()                    │
│                    │         └─ 写入 client 环形缓冲区       │
│                    │         └─ wake_up_interruptible()      │
│                    │                                         │
│                    └─ handle#2 (kbd): open=0 ✗ 跳过          │
└──────────────────────────────────────────────────────────────┘
        │
        ▼ 进程被唤醒
┌──────────────────────────────────────────────────────────────┐
│                      用户态应用层                             │
│                                                              │
│   input_monitor 程序中的 select() 返回可读                    │
│        │                                                     │
│        ▼                                                     │
│   read(fd, &ev, sizeof(ev))                                  │
│        │                                                     │
│        ├─ 第1次读取:                                         │
│        │   ev = { time=1629.123456,                           │
│        │          type=EV_KEY(0x01),                          │
│        │          code=KEY_ENTER(28),                         │
│        │          value=1 }                                   │
│        │                                                     │
│        └─ 第2次读取:                                         │
│            ev = { time=1629.123456,                           │
│                   type=EV_SYN(0x00),                          │
│                   code=SYN_REPORT(0),                         │
│                   value=0 }                                   │
│                                                              │
│   输出:                                                      │
│   [1629.123456] type=0x01 code=0x01C value=1                 │
│     → 按键 28 按下                                           │
└──────────────────────────────────────────────────────────────┘
现象
可能原因
排查方法
insmod
后没有/dev/input/eventX
evdev模块未加载
modprobe evdev
设备注册成功但按键无响应
GPIO中断未触发
cat /proc/interrupts
 查看中断计数
事件上报但应用读不到
权限不足
chmod 666 /dev/input/event0
 或用sudo
按键抖动产生多次事件
去抖时间不够
增大debounce-interval或定时器延时
长按只触发一次
未启用EV_REP
设备树加autorepeat或驱动中__set_bit(EV_REP, evbit)

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-24 02:35:35 HTTP/2.0 GET : https://f.mffb.com.cn/a/512033.html
  2. 运行时间 : 0.210518s [ 吞吐率:4.75req/s ] 内存消耗:4,702.29kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=07f833dcd30e90c68bd86558d794b19d
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001156s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001481s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000757s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000741s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001642s ]
  6. SELECT * FROM `set` [ RunTime:0.000597s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001838s ]
  8. SELECT * FROM `article` WHERE `id` = 512033 LIMIT 1 [ RunTime:0.002641s ]
  9. UPDATE `article` SET `lasttime` = 1787510135 WHERE `id` = 512033 [ RunTime:0.004524s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000742s ]
  11. SELECT * FROM `article` WHERE `id` < 512033 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001265s ]
  12. SELECT * FROM `article` WHERE `id` > 512033 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.007530s ]
  13. SELECT * FROM `article` WHERE `id` < 512033 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.004070s ]
  14. SELECT * FROM `article` WHERE `id` < 512033 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.004194s ]
  15. SELECT * FROM `article` WHERE `id` < 512033 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.010385s ]
0.214315s