鍵盤驅動雖簡單,卻涵蓋 Linux 字元裝置驅動的完整骨架。本文以查詢式鍵盤驅動為例,循序漸進講解模組註冊/註銷、file_operations 介面、開啟/關閉/讀取操作、環形緩衝區、核心定時器、等待佇列(阻塞 I/O)、poll 機制、非同步通知(fasync)等核心技術。
static int head, tail;
static int __init Keypad_init(void) {
int result;
result = register_chrdev(KEY_LED_MAJOR, KEY_LED_NAME, &Keypad_fops);
Keypad_clear();
init_waitqueue_head(&queue);
printk("%s %s initialized.\n", KEY_LED_NAME, KEY_LED_VERSION);
return 0;
}
module_init(Keypad_init);
static void __exit Keypad_cleanup(void) {
del_timer(&timer);
unregister_chrdev(KEY_LED_MAJOR, KEY_LED_NAME);
printk("Keypad driver removed\n");
}
module_exit(Keypad_cleanup);
註冊字元裝置、初始化等待佇列、定時器。
static struct file_operations Keypad_fops = {
.open = Keypad_open,
.read = Keypad_read,
.poll = Keypad_poll,
.fasync = Keypad_fasync,
.release = Keypad_release,
};
static int Keypad_open(struct inode *inode, struct file *filp) {
read_xy();
try_module_get(THIS_MODULE); // 2.6 核心模組計數器+1
return 0;
}
static void read_xy(void) {
new_data(); // 讀取鍵值
keypad_starttimer(); // 啟動核心定時器,定期掃描
}
typedef struct {
ulong status; // 按鍵值
ulong click; // 1=有按下,0=無
} KEY_EVENT;
static KEY_EVENT cur_data, buf[BUFSIZE];
static void new_data(void) {
if ((KEY_CS & 0xff) != 0xff) { // 低電平有效
switch (KEY_CS & 0xff) {
case ~KEY0 & 0xff: cur_data.status = 1; break;
case ~KEY1 & 0xff: cur_data.status = 2; break;
// 其他鍵同理
}
cur_data.click = 1;
} else {
cur_data.click = 0;
cur_data.status = 0;
}
// 環形緩衝區寫入
if (head != tail) {
int last = head--;
if (last < 0) last = BUFSIZE - 1;
}
buf[head] = cur_data;
if (++head == BUFSIZE) head = 0;
if (head == tail && tail++ == BUFSIZE) tail = 0;
if (fasync)
kill_fasync(&fasync, SIGIO, POLL_IN);
wake_up_interruptible(&queue);
}
static int Keypad_release(struct inode *inode, struct file *filp) {
Keypad_fasync(-1, filp, 0); // 移除非同步通知
module_put(THIS_MODULE); // 模組計數器-1
del_timer(&timer); // 刪除定時器
return 0;
}
static ssize_t Keypad_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {
DECLARE_WAITQUEUE(wait, current);
KEY_EVENT t;
ulong out_buf[2];
if (head == tail) { // 緩衝區空
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
add_wait_queue(&queue, &wait);
current->state = TASK_INTERRUPTIBLE;
while (head == tail && !signal_pending(current)) {
schedule();
current->state = TASK_INTERRUPTIBLE;
}
current->state = TASK_RUNNING;
remove_wait_queue(&queue, &wait);
if (head == tail) return count;
}
t = get_data();
out_buf[0] = t.status;
out_buf[1] = t.click;
copy_to_user(buf, &out_buf, sizeof(out_buf));
return count;
}
static KEY_EVENT get_data(void) {
int last = tail;
if (++tail == BUFSIZE) tail = 0;
return buf[last];
}
展示標準阻塞讀取流程:等待佇列、進程狀態切換、排程器呼叫、資料拷貝至用戶空間。
下篇將詳細講解 poll 系統呼叫實作、非同步通知機制完整流程,並提供完整應用程式範例驗證驅動功能。