Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux Input 子系統核心架構解析:input_dev、input_handler、input_handle 三角關係

編輯:Linux 教學

Linux Input 子系統為鍵盤、鼠標、觸摸屏、加速度計等輸入設備提供統一框架。核心在於三大數據結構——input_dev(物理設備)、input_handler(事件消費者)、input_handle(連接雙方)形成的動態網狀關聯。本文基於內核源碼(drivers/input/input.c)深度解析註冊流程、匹配機制、事件分發路徑。

一、 三大核心結構體

1. input_dev:物理輸入設備

struct input_dev {
    const char *name;           // 設備名稱
    struct device dev;          // 設備模型節點(class "input")
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];   // 支持的事件類型位圖
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 支持的按鍵碼位圖
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 相對事件(鼠標移動)
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 絕對事件(觸摸座標)
    // ... mscbit, ledbit, sndbit, ffbit, swbit 等
    struct input_handle *grab;  // 獨佔處理器
    struct list_head h_list;    // 掛接的 handle 鏈表
    struct list_head node;      // 全局 input_dev_list 節點
    // 事件上報入口
    void (*event)(struct input_dev *, unsigned int type, unsigned int code, int value);
}

2. input_handler:事件處理器(上層消費者)

struct input_handler {
    void *private;              // 私有數據
    void (*event)(struct input_handle *, unsigned int type, unsigned int code, int value);
    void (*events)(struct input_handle *, struct input_value *, unsigned int count);
    bool (*filter)(struct input_handle *, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *, struct input_dev *);  // 自定義匹配
    int (*connect)(struct input_handler *, struct input_dev *, const struct input_device_id *);
    void (*disconnect)(struct input_handle *);
    void (*start)(struct input_handle *);
    bool legacy_minors;         // 使用傳統次設備號
    int minor;                  // 次設備號基值
    const struct input_device_id *id_table;   // 匹配表(白名單)
    const struct input_device_id *blacklist;  // 黑名單
    struct list_head h_list;    // 掛接的 handle 鏈表
    struct list_head node;      // 全局 input_handler_list 節點
};

3. input_handle:連接句柄

struct input_handle {
    void *private;
    int open;                   // 打開計數
    const char *name;           // 如 "input0"
    struct input_dev *dev;      // 關聯設備
    struct input_handler *handler; // 關聯處理器
    struct list_head d_node;    // 掛在 dev->h_list
    struct list_head h_node;    // 掛在 handler->h_list
};

二、 註冊與匹配流程

1. 設備註冊 input_register_device()


__set_bit(EV_SYN, dev->evbit);  // 強制支持同步事件
init_timer(&dev->timer);        // 按鍵重複定時器
device_add(&dev->dev);          // 創建 /dev/input/eventX
list_add_tail(&dev->node, &input_dev_list);

// 遍歷所有已註冊 handler,嘗試連接
list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);

2. 連接嘗試 input_attach_handler()


if (handler->blacklist && input_match_device(handler->blacklist, dev))
    return;  // 黑名單過濾

if (!handler->id_table || !input_match_device(handler->id_table, dev))
    return;  // 白名單不匹配

// 創建 handle 並雙向掛接
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
handle->dev = dev;
handle->handler = handler;
handler->connect(handler, dev, id);
list_add_tail(&handle->d_node, &dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);

3. 匹配規則 input_match_device()

struct input_device_id 定義匹配維度:

邏輯:handler 的 id_table 中任意一項匹配即可(OR 關系),但該項內部各字段為 AND 關系。blacklist 匹配則強制拒絕。

三、 經典 Handler 實現

字段掩碼說明
bustypeMATCH_BUS總線類型(USB、I2C、SPI、平台)
vendorMATCH_VENDOR廠商 ID
productMATCH_PRODUCT產品 ID
versionMATCH_VERSION版本號
evbit[]-必須支持的事件類型位圖
keybit[]-必須支持的按鍵碼位圖
relbit/absbit/...-其他事件碼位圖
Handler功能設備節點匹配關鍵
evdev通用事件接口,用戶態 read() 獲取原始事件/dev/input/eventXid_table 為空(匹配所有)
mousedevPS/2 鼠標兼容接口/dev/input/mice, /dev/input/mouseXEV_REL + REL_X/Y
keybdevAT 鍵盤兼容接口/dev/input/keyboardXEV_KEY + 按鍵碼
joydev手柄/搖桿接口/dev/input/jsXEV_ABS + ABS_X/Y
mtdev多點觸控協議轉換/dev/input/mtdevXEV_ABS + MT 槽位事件

四、 事件分發路徑


// 驅動上報事件(中斷上下文或內核線程)
input_event(dev, EV_KEY, KEY_A, 1);  // 按下
input_sync(dev);                     // 發送 EV_SYN

// 內核分發
input_handle_event(dev, type, code, value);  // 遍歷 dev->h_list
    → handler->event(handle, type, code, value)  // 單事件
    → handler->events(handle, vals, count)       // 批量事件
    → handler->filter() 返回 true 可過濾

五、 多設備多 Handler 共存示例

一台筆記本:內建鍵盤、外接 USB 鍵盤、觸摸板、USB 鼠標。

  • 每個物理設備註冊一個 input_dev。
  • evdev handler 與所有 input_dev 建立 handle → 用戶態可打開任意 /dev/input/eventX 讀取對應設備事件。
  • mousedev 僅與鼠標/觸摸板建立 handle → 合併輸出 /dev/input/mice。
  • 應用程序打開 event3(觸摸板)時,evdev->open() 增加 handle->open 計數,驅動開始上報。

六、 進階特性

  • Grab 獨佔模式:EVIOCGRAB ioctl 設置 dev->grab,事件僅分發給該 handle,其他 handler 忽略(如密碼輸入、遊戲獨佔鼠標)。
  • 按鍵重複:dev->rep[REP_DELAY/PERIOD] 配置首次延遲/重複間隔,定時器回調 input_repeat_key 自動上報重複事件。
  • 力反饋:EV_FF 事件類型,handler 實現 upload_effect、playback 等回調。
  • 設備樹綁定:compatible、interrupt-parent、linux,input-properties 自動填充 evbit/keybit。
Copyright © Windows教程網 All Rights Reserved