當兩個進程同時打開同一設備進行寫操作時,若無同步機制,會導致對共享數據結構(如設備鏈表)的並發修改,造成數據覆蓋錯誤,這就是競態條件。在單處理器系統上,內核代碼非搶佔,故不會發生;但在多處理器系統中極易出現。
Linux 提供兩種主要同步手段:
#include <linux/semaphore.h>
struct semaphore sem;
sema_init(&sem, 1); // 初始化為 1,表示可用
if (down_interruptible(&sem)) return -ERESTARTSYS; // 獲取失敗返回
// 臨界區操作...
up(&sem); // 釋放並喚醒等待進程
注意:信號量會導致進程睡眠,故不能在中斷處理程序中使用;且必須確保獲取後一定釋放,否則會造成死鎖。
#include <linux/spinlock.h>
spinlock_t lock = SPIN_LOCK_UNLOCKED; // 或 spin_lock_init(&lock)
spin_lock(&lock); // 獲取鎖,若被佔用則自旋等待
// 臨界區操作(不可睡眠!)
spin_unlock(&lock); // 釋放鎖
自旋鎖持有期間絕對不能睡眠,適用於持鎖時間極短、且不涉及阻塞操作的場景。
當設備無數據可讀時,阻塞型 I/O 會讓進程睡眠(移出運行隊列),直到數據就緒被喚醒;非阻塞則直接返回錯誤。Linux 驅動中通過等待隊列實現阻塞:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
// 或靜態初始化:DECLARE_WAIT_QUEUE_HEAD(my_queue);
wait_event_interruptible(wq, condition):可被信號中斷的睡眠,條件滿足則返回 0,被信號中斷返回 -ERESTARTSYS。wake_up_interruptible(&wq):喚醒隊列上所有可中斷睡眠的進程。interruptible_sleep_on() 的競態缺陷舊式 interruptible_sleep_on() 在設置進程狀態與調用 schedule() 之間存在窗口,可能導致喚醒信號丟失。推薦使用 wait_event_interruptible() 宏,其內部實現了條件檢查循環,原子性地完成「檢查條件→睡眠→被喚醒後重新檢查」流程,彻底消除該競態。
可重入函數不使用全局/靜態變量,所有數據存放在棧上或由調用者提供,確保多線程/中斷並發調用時數據隔離。驅動開發中應盡量編寫可重入代碼,減少鎖的使用,提高並發性能。