在 Linux 設備驅動開發中,阻塞機制
是處理資源暫時不可用(如設備未準備好數據、緩沖區滿等)的核心手段。驅動程序可以將被阻塞的進程設置成休眠狀態,然后,在資源可用后,再將該進程喚醒。
在 Linux 驅動開發中,休眠(等待)機制分為 簡單休眠
(使用 wait_event系列宏)和 高級休眠
(手動使用 prepare_to_wait/finish_wait),它們的核心區別在于封裝程度和靈活性。
其中,"簡單休眠"由"高級休眠"封裝而來,可以當做"高級休眠"的語法糖。
本質區別對比:
如何選擇?
- 優先使用簡單休眠,比如:只需要等待單一條件、不需要在等待過程中執行額外操作;
- 當需要實現非標準的喚醒條件(如同時等待多個事件)、需要更精細的性能優化時必須使用高級休眠;
- 現代內核更推薦使用 wait_event_interruptible 等封裝宏。
簡單休眠
最佳框架:
ssize_t scullpipe_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{struct scull_pipe *dev = filp->private_data;int ret;// 檢查參數if (count == 0)return 0;if (mutex_lock_interruptible(&dev->lock))return -ERESTARTSYS;// 如果緩沖區為空,進入休眠等待while (read_buffer_empty(dev)) {mutex_unlock(&dev->lock); // 必須釋放鎖再休眠if (filp->f_flags & O_NONBLOCK)return -EAGAIN;ret = wait_event_interruptible(dev->inq, !read_buffer_empty(dev));if (ret == -ERESTARTSYS) // 被信號打斷return -ERESTARTSYS;// 被喚醒后再次獲取鎖再判斷if (mutex_lock_interruptible(&dev->lock))return -ERESTARTSYS;}// 此時緩沖區有數據,可以讀取ret = copy_data_to_user(dev, buf, count);mutex_unlock(&dev->lock);return ret;
}
高級休眠
最佳框架:
ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{struct my_device *dev = filp->private_data;DEFINE_WAIT_FUNC(wait, default_wake_function);ssize_t ret = 0;if (mutex_lock_interruptible(&dev->lock))return -ERESTARTSYS;while (!data_available(dev)){mutex_unlock(&dev->lock);/* 非阻塞模式 */if (filp->f_flags & O_NONBLOCK) return -EAGAIN;if (prepare_to_wait_event(&dev->inq, &wait, TASK_INTERRUPTIBLE))return -ERESTARTSYS; // 信號/* 在當前進程掛入等待隊列后,再次確認條件*/if (!data_available(dev))schedule();if (signal_pending(current)) {ret = -ERESTARTSYS; // 信號break;}if (mutex_lock_interruptible(&dev->lock)){ret = -ERESTARTSYS;break;}}finish_wait(&dev->inq, &wait);if(ret)return ret;// 現在有鎖,并且數據有效,下面進行讀操作count = min(count, xxx);if (copy_to_user(buf, dev->rp, count) ){mutex_unlock(&dev->lock);return -EFAULT;}mutex_unlock(&dev->lock);return count;
}
DEFINE_WAIT_FUNC(wait, default_wake_function)
:定義一個等待隊列項
(wait_queue_entry),并初始化其喚醒回調函數。
為當前進程創建一個“代表自己”的等待隊列項 (wait
),并指定當條件滿足(通常是資源可用)時如何喚醒自己(通過default_wake_function
函數)。// 源碼 (include/linux/wait.h): #define DEFINE_WAIT_FUNC(name, function) \wait_queue_entry_t name = { \.private = current, \.func = function, \.entry = LIST_HEAD_INIT((name).entry), \}
signal_pending(current)
:檢查當前進程 (current) 是否有待處理的信號。實現可中斷的睡眠 (TASK_INTERRUPTIBLE)。避免進程在需要響應信號(如 Ctrl+C)時被無謂地阻塞。- 返回非零值(
true
)表示有掛起的信號需要處理。此時,進程不應繼續睡眠,而應退出等待循環并返回 -ERESTARTSYS 錯誤碼給用戶空間(讓用戶空間有機會處理信號)。 - 通常在調用 schedule() 讓出 CPU 之前檢查,避免信號丟失導致多余阻塞。這也符合現代 Linux 關于"信號優先響應"的設計原則。
- 返回非零值(
prepare_to_wait_event(&dev->inq, &wait, state)
:準備當前進程進入睡眠狀態。核心任務是將等待項 (&wait) 添加到設備的等待隊列頭 (&dev->inq),并設置當前進程的狀態 (根據參數state
,通常是TASK_INTERRUPTIBLE
, 表示可被信號喚醒的睡眠)。
正式將當前進程“注冊”到設備的等待隊列 (參數dev->inq
),并標記為睡眠狀態,為安全讓出 CPU 做準備。- 它將等待項 (
wait
) 安全地(加鎖)添加到等待隊列 ()。
// 簡化邏輯,kernel/sched/wait.c void prepare_to_wait_event(wait_queue_head_t *wq_head, wait_queue_entry_t *wait, int state) {unsigned long flags;wait->flags &= ~WQ_FLAG_EXCLUSIVE; // 通常是非獨占等待spin_lock_irqsave(&wq_head->lock, flags); // 獲取隊列鎖if (list_empty(&wait->entry)) // 防止重復添加__add_wait_queue(wq_head, wait); // 將等待項加入隊列尾部set_current_state(state); // 設置當前進程狀態 (TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)spin_unlock_irqrestore(&wq_head->lock, flags); // 釋放隊列鎖 }
- 它將等待項 (
schedule()
:主動讓出 CPU,觸發進程調度。當前進程進入睡眠狀態,內核選擇另一個就緒進程運行。- 調用
schedule()
時,進程狀態必須已經是TASK_INTERRUPTIBLE
或TASK_UNINTERRUPTIBLE
(由prepare_to_wait_event
設置)。調度器根據此狀態將進程移出運行隊列。 - 進程在此處“睡著”。
- 當進程被喚醒時(通常是因為等待的條件滿足或有信號到達),執行會從
schedule()
調用之后的下一條語句繼續。
- 調用
finish_wait(&dev->inq, &wait)
:清理等待狀態。將等待項從等待隊列中移除,并將當前進程狀態設置回 TASK_RUNNING。// 簡化邏輯,kernel/sched/wait.c void finish_wait(wait_queue_head_t *wq_head, wait_queue_entry_t *wait) {unsigned long flags;set_current_state(TASK_RUNNING); // 重要:恢復為可運行狀態if (!list_empty_careful(&wait->entry)) { // 如果還在隊列里spin_lock_irqsave(&wq_head->lock, flags);list_del_init(&wait->entry); // 從等待隊列中移除spin_unlock_irqrestore(&wq_head->lock, flags);} }