Linux 驅動中,多進程並發存取同一裝置時,等待隊列 是實現「阻塞等待條件滿足」的核心基礎設施。本文系統整理 wait_event 族群 API、睡眠模式差異、喚醒函式語意,並給出典型「裝置忙碌等待」與「非同步通知」兩大實戰模式,助你寫出正確、高效的同步程式碼。
DECLARE_WAIT_QUEUE_HEAD(my_queue); // 靜態定義並初始化 // 或動態 wait_queue_head_t my_queue; init_waitqueue_head(&my_queue);
隊列掛載 wait_queue_t 節點,每個節點代表一個等待的進程上下文。
| 函式 | 可中斷性 | 超時支援 | 喚醒條件 | 典型用途 |
|---|---|---|---|---|
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;
}
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;
}
若用戶程式不想阻塞,可啟用 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 處理函式即可收到通知。
_interruptible 版本,避免進程陷入 D 狀態無法殺掉。msecs_to_jiffies(ms) 換算,避免手算 HZ 導致移植錯誤。wait_event 迴圈會自動重新評估,安全。mutex 或信號量。等待隊列是 Linux 核心「排程與同步」的基石。熟練掌握四大 wait_event 變體、喚醒語意與非同步通知機制,即能編寫出既高效又健壯的字元/區塊裝置驅動。進階主題可探討 completion (一次性事件)、mutex/semaphore (互斥)、rcu (讀多寫少) 等更高層同步原語。