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 鍵盤驅動程式開發全流程解析:從模組註冊到非同步通知

編輯:Linux 教學

鍵盤驅動雖簡單,卻涵蓋 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);

註冊字元裝置、初始化等待佇列、定時器。

二、file_operations 介面定義


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 系統呼叫實作、非同步通知機制完整流程,並提供完整應用程式範例驗證驅動功能。

Copyright © Windows教程網 All Rights Reserved