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 驅動中,多進程並發存取同一裝置時,等待隊列 是實現「阻塞等待條件滿足」的核心基礎設施。本文系統整理 wait_event 族群 API、睡眠模式差異、喚醒函式語意,並給出典型「裝置忙碌等待」與「非同步通知」兩大實戰模式,助你寫出正確、高效的同步程式碼。

一、等待隊列基本資料結構

DECLARE_WAIT_QUEUE_HEAD(my_queue);  // 靜態定義並初始化
// 或動態
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

隊列掛載 wait_queue_t 節點,每個節點代表一個等待的進程上下文。

二、睡眠/等待 API 完整對照表

函式可中斷性超時支援喚醒條件典型用途
wait_event(queue, condition)不可中斷 (TASK_UNINTERRUPTIBLE)無condition 為真極短關鍵區、不可被訊號打斷
wait_event_interruptible(queue, condition)可中斷 (TASK_INTERRUPTIBLE)無condition 為真 或 收到訊號一般裝置等待,允許 Ctrl+C 取消
wait_event_timeout(queue, condition, timeout)不可中斷有 (jiffies)condition 為真 或 超時硬體輪詢、看門狗
wait_event_interruptible_timeout(queue, condition, timeout)可中斷有condition 為真 或 超時 或 訊號最通用:網路/裝置 I/O 等待

關鍵點:condition 為 C 表達式,每次排程器喚醒該進程時會重新求值;務必確保 condition 受鎖保護,否則會有競態風險。

三、喚醒函式

void wake_up(wait_queue_head_t *queue);                 // 喚醒隊列中所有 TASK_UNINTERRUPTIBLE 與 TASK_INTERRUPTIBLE 進程
void wake_up_interruptible(wait_queue_head_t *queue);   // 僅喚醒 TASK_INTERRUPTIBLE 進程
void wake_up_all(wait_queue_head_t *queue);             // 同 wake_up,語意更明確

中斷處理常見模式:

irqreturn_t my_isr(int irq, void *dev_id) {
    struct my_dev *dev = dev_id;
    dev->data_ready = 1;                // 更新條件變數 (需鎖保護)
    wake_up_interruptible(&dev->wq);    // 喚醒等待隊列
    return IRQ_HANDLED;
}

四、實戰模式 1:阻塞式 read() 等待資料就緒

ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
    struct my_dev *dev = filp->private_data;
    int ret;

    // 1. 等待資料就緒 (可中斷 + 超時 5 秒)
    ret = wait_event_interruptible_timeout(dev->wq, dev->data_ready, 5 * HZ);
    if (ret == 0) return -ETIMEDOUT;      // 超時
    if (ret < 0) return ret;              // 被訊號中斷 (-ERESTARTSYS)

    // 2. 複製資料到用戶空間 (略)
    dev->data_ready = 0;                  // 重置條件
    return bytes_copied;
}

五、實戰模式 2:非同步通知 (FASYNC / SIGIO)

若用戶程式不想阻塞,可啟用 FASYNC,驅動在資料就緒時發送 SIGIO:

// file_operations
int my_fasync(int fd, struct file *filp, int on) {
    return fasync_helper(fd, filp, on, &dev->async_queue);
}

// 中斷/資料到達時
if (dev->async_queue)
    kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

用戶程式 fcntl(fd, F_SETOWN, getpid()); fcntl(fd, F_SETFL, FASYNC); 並安裝 SIGIO 處理函式即可收到通知。

六、常見陷阱與最佳實踐

  1. condition 必須在鎖保護下修改與檢查,否則可能發生「條件變真但進程尚未入隊」導致永久睡眠。
  2. 優先使用 _interruptible 版本,避免進程陷入 D 狀態無法殺掉。
  3. 超時值用 msecs_to_jiffies(ms) 換算,避免手算 HZ 導致移植錯誤。
  4. 喚醒後 condition 可能再次為假 (多進程搶佔),wait_event 迴圈會自動重新評估,安全。
  5. 避免在持有自旋鎖時睡眠,等待隊列 API 內部會排程,需用互斥鎖 mutex 或信號量。

結語

等待隊列是 Linux 核心「排程與同步」的基石。熟練掌握四大 wait_event 變體、喚醒語意與非同步通知機制,即能編寫出既高效又健壯的字元/區塊裝置驅動。進階主題可探討 completion (一次性事件)、mutex/semaphore (互斥)、rcu (讀多寫少) 等更高層同步原語。

Copyright © Windows教程網 All Rights Reserved