You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何我编写的Linux内核模块无法模拟按键输入?

问题:Linux内核模块模拟按键无效果的排查与修复

我正在尝试编写一个用于模拟按键输入的Linux内核模块,但该模块无法实现按键功能。模块编译运行均无错误,但没有产生任何按键效果,仅出现以下日志:

[ 1275.986167] input: Unspecified device as /devices/virtual/input/input19
[ 1275.986622] Device Driverz Insert...Done!!!

我的模块代码如下:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/device.h>
#include<linux/timer.h>
#include<linux/kdev_t.h>
#include<linux/jiffies.h>
#include<linux/input.h>
#include<asm/io.h>

static struct input_dev *key_dev;

static int hellopress_init(void)
{
    key_dev = input_allocate_device();

    set_bit(EV_KEY,key_dev->evbit);

    set_bit(KEY_A,key_dev->keybit);
    set_bit(KEY_B,key_dev->keybit);
    set_bit(KEY_C,key_dev->keybit);
    set_bit(KEY_D,key_dev->keybit);
    set_bit(KEY_E,key_dev->keybit);
    set_bit(KEY_F,key_dev->keybit);
    set_bit(KEY_G,key_dev->keybit);
    set_bit(KEY_H,key_dev->keybit);
    set_bit(KEY_I,key_dev->keybit);
    set_bit(KEY_J,key_dev->keybit);
    set_bit(KEY_K,key_dev->keybit);
    set_bit(KEY_L,key_dev->keybit);
    set_bit(KEY_M,key_dev->keybit);
    set_bit(KEY_N,key_dev->keybit);
    set_bit(KEY_O,key_dev->keybit);
    set_bit(KEY_P,key_dev->keybit);
    set_bit(KEY_Q,key_dev->keybit);
    set_bit(KEY_R,key_dev->keybit);
    set_bit(KEY_S,key_dev->keybit);
    set_bit(KEY_T,key_dev->keybit);
    set_bit(KEY_U,key_dev->keybit);
    set_bit(KEY_V,key_dev->keybit);
    set_bit(KEY_W,key_dev->keybit);
    set_bit(KEY_X,key_dev->keybit);
    set_bit(KEY_Y,key_dev->keybit);
    set_bit(KEY_Z,key_dev->keybit);

    input_register_device(key_dev);

    input_report_key(key_dev, KEY_A, 1);
    input_report_key(key_dev, KEY_A, 0);
    input_report_key(key_dev, KEY_B, 1);
    input_report_key(key_dev, KEY_B, 0);
    input_report_key(key_dev, KEY_C, 1);
    input_report_key(key_dev, KEY_C, 0);
    input_report_key(key_dev, KEY_D, 1);
    input_report_key(key_dev, KEY_D, 0);
    input_report_key(key_dev, KEY_E, 1);
    input_report_key(key_dev, KEY_E, 0);
    input_report_key(key_dev, KEY_F, 1);
    input_report_key(key_dev, KEY_F, 0);
    input_report_key(key_dev, KEY_G, 1);
    input_report_key(key_dev, KEY_G, 0);
    input_report_key(key_dev, KEY_H, 1);
    input_report_key(key_dev, KEY_H, 0);
    input_report_key(key_dev, KEY_I, 1);
    input_report_key(key_dev, KEY_I, 0);
    input_report_key(key_dev, KEY_J, 1);
    input_report_key(key_dev, KEY_J, 0);
    input_report_key(key_dev, KEY_K, 1);
    input_report_key(key_dev, KEY_K, 0);
    input_report_key(key_dev, KEY_L, 1);
    input_report_key(key_dev, KEY_L, 0);
    input_report_key(key_dev, KEY_M, 1);
    input_report_key(key_dev, KEY_M, 0);
    input_report_key(key_dev, KEY_N, 1);
    input_report_key(key_dev, KEY_N, 0);
    input_report_key(key_dev, KEY_O, 1);
    input_report_key(key_dev, KEY_O, 0);
    input_report_key(key_dev, KEY_P, 1);
    input_report_key(key_dev, KEY_P, 0);
    input_report_key(key_dev, KEY_Q, 1);
    input_report_key(key_dev, KEY_Q, 0);
    input_report_key(key_dev, KEY_R, 1);
    input_report_key(key_dev, KEY_R, 0);
    input_report_key(key_dev, KEY_S, 1);
    input_report_key(key_dev, KEY_S, 0);
    input_report_key(key_dev, KEY_T, 1);
    input_report_key(key_dev, KEY_T, 0);
    input_report_key(key_dev, KEY_U, 1);
    input_report_key(key_dev, KEY_U, 0);
    input_report_key(key_dev, KEY_V, 1);
    input_report_key(key_dev, KEY_V, 0);
    input_report_key(key_dev, KEY_W, 1);
    input_report_key(key_dev, KEY_W, 0);
    input_report_key(key_dev, KEY_X, 1);
    input_report_key(key_dev, KEY_X, 0);
    input_report_key(key_dev, KEY_Y, 1);
    input_report_key(key_dev, KEY_Y, 0);
    input_report_key(key_dev, KEY_Z, 1);
    input_report_key(key_dev, KEY_Z, 0);

    input_report_key(key_dev, 30, 1);
    input_report_key(key_dev, 30, 0);
    input_report_key(key_dev, 76, 1);
    input_report_key(key_dev, 76, 0);


    input_sync(key_dev);

    printk(KERN_INFO "Device Driverz Insert...Done!!!\n");

    return 0;
}

static void hellopress_exit(void)
{
    input_unregister_device(key_dev);

    printk(KERN_INFO "Device Driver unloaded...Done!!!\n");
}


module_init(hellopress_init);
module_exit(hellopress_exit);

MODULE_AUTHOR("Vic");
MODULE_VERSION("0.1.2");
MODULE_LICENSE("GPL");

错误原因与修复方案

1. 未设置输入设备的基本标识信息

日志中的"Unspecified device"是核心线索——你没有给input_dev设置name、phys等必要标识字段,部分桌面环境或输入子系统会忽略无标识的虚拟输入设备。

在input_allocate_device()之后添加以下代码:

key_dev->name = "virtual-keyboard-emulator";
key_dev->phys = "virtual-keyboard/input0";
key_dev->id.bustype = BUS_VIRTUAL;
key_dev->id.vendor  = 0x0001;
key_dev->id.product = 0x0001;
key_dev->id.version = 0x0100;

2. 按键事件发送时机与方式错误

你在input_register_device()后立刻批量发送所有按键事件,存在两个问题:

  • 设备未完成输入子系统的初始化,事件直接被丢弃
  • 无延迟的批量发送不符合真实键盘的输入逻辑,输入子系统无法正确识别

必须用定时器模拟真实按键的按下/释放间隔,示例实现如下:
首先定义全局变量:

static struct timer_list key_timer;
static int current_key_idx = 0;
static const unsigned int target_keys[] = {KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z};

在hellopress_init()中初始化定时器:

// 初始化定时器,绑定回调函数
setup_timer(&key_timer, send_key_event, 0);
// 100ms后触发第一个按键事件
mod_timer(&key_timer, jiffies + msecs_to_jiffies(100));

实现定时器回调函数(处理按键按下/释放):

static void send_key_event(unsigned long data) {
    if (current_key_idx < ARRAY_SIZE(target_keys)) {
        // 发送按键按下事件
        input_report_key(key_dev, target_keys[current_key_idx], 1);
        input_sync(key_dev); // 标记单次按下事件完成
        // 50ms后发送释放事件(模拟真实按键按下时长)
        mod_timer(&key_timer, jiffies + msecs_to_jiffies(50));
        current_key_idx++;
    } else if (current_key_idx == ARRAY_SIZE(target_keys)) {
        // 释放最后一个按键
        input_report_key(key_dev, target_keys[current_key_idx - 1], 0);
        input_sync(key_dev);
        current_key_idx++;
        // 若需要循环发送,可重置current_key_idx并再次启动定时器
    }
}

在hellopress_exit()中清理定时器:

del_timer_sync(&key_timer);

3. input_sync()调用不规范

每个完整的按键动作(按下+释放)都需要对应一次input_sync(),告知输入子系统这是一组完整的事件。你之前仅在所有事件末尾调用一次,导致输入子系统无法解析单个按键的动作。

4. 禁止在init函数中阻塞

内核模块的init函数属于初始化路径,不能直接调用msleep()这类阻塞函数,必须用定时器处理延迟逻辑,否则会触发内核警告甚至导致模块加载失败。


完整修复后的核心流程

  1. 分配input_dev并设置基本标识信息
  2. 配置设备支持的事件类型(EV_KEY)和按键列表
  3. 注册输入设备到内核输入子系统
  4. 通过定时器分批发送按键的按下/释放事件,每次事件后调用input_sync()
  5. 模块卸载时注销设备并清理定时器

内容的提问来源于stack exchange,提问作者JustOneMan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 22:45:53