Linux Input 子系統為鍵盤、鼠標、觸摸屏、加速度計等輸入設備提供統一框架。核心在於三大數據結構——input_dev(物理設備)、input_handler(事件消費者)、input_handle(連接雙方)形成的動態網狀關聯。本文基於內核源碼(drivers/input/input.c)深度解析註冊流程、匹配機制、事件分發路徑。
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);
}
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 節點
};
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
};
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);
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);
input_match_device()struct input_device_id 定義匹配維度:
| 字段 | 掩碼 | 說明 | |
|---|---|---|---|
| bustype | MATCH_BUS | 總線類型(USB、I2C、SPI、平台) | |
| vendor | MATCH_VENDOR | 廠商 ID | |
| product | MATCH_PRODUCT | 產品 ID | |
| version | MATCH_VERSION | 版本號 | |
| evbit[] | - | 必須支持的事件類型位圖 | |
| keybit[] | - | 必須支持的按鍵碼位圖 | |
| relbit/absbit/... | - | 其他事件碼位圖 |
| Handler | 功能 | 設備節點 | 匹配關鍵 |
|---|---|---|---|
| evdev | 通用事件接口,用戶態 read() 獲取原始事件 | /dev/input/eventX | id_table 為空(匹配所有) |
| mousedev | PS/2 鼠標兼容接口 | /dev/input/mice, /dev/input/mouseX | EV_REL + REL_X/Y |
| keybdev | AT 鍵盤兼容接口 | /dev/input/keyboardX | EV_KEY + 按鍵碼 |
| joydev | 手柄/搖桿接口 | /dev/input/jsX | EV_ABS + ABS_X/Y |
| mtdev | 多點觸控協議轉換 | /dev/input/mtdevX | EV_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 可過濾
一台筆記本:內建鍵盤、外接 USB 鍵盤、觸摸板、USB 鼠標。
input_dev。evdev handler 與所有 input_dev 建立 handle → 用戶態可打開任意 /dev/input/eventX 讀取對應設備事件。mousedev 僅與鼠標/觸摸板建立 handle → 合併輸出 /dev/input/mice。event3(觸摸板)時,evdev->open() 增加 handle->open 計數,驅動開始上報。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。