驅動程式專屬鎖定(driver-specific locking)
驅動程式的資料可能同時被兩個截然不同的世界碰觸。一個是行程情境:你的 read 或 write 處理常式,代表某個呼叫進來的應用程式執行,且可以睡眠。另一個是中斷情境:你的 IRQ 處理常式,在硬體發出信號的瞬間非同步觸發,可能正落在你 read 處理常式的中途。若兩者碰同一個緩衝區或計數器,你就有了競爭。驅動程式專屬鎖定就是跨越這個「行程對中斷」邊界保護共享驅動程式狀態的紀律。
經典陷阱是這樣:你的行程情境程式碼取一個普通自旋鎖來守護一個共享佇列。當它持有鎖時,同一個 CPU 上來了一個中斷,你的 IRQ 處理常式執行並試圖取同一個鎖。中斷處理常式無法繼續(鎖被持有)也不能睡著去等,而持鎖的程式碼也無法繼續(中斷搶占了它)——你瞬間對自己造成死結。修法是:在持有一個中斷處理常式也會取的鎖時,停用該 CPU 上的中斷:在 Linux 中你在行程情境用 spin_lock_irqsave(它儲存中斷狀態並停用中斷),在處理常式中用普通的 spin_lock,使兩者無法在一個 CPU 上交錯。你也絕不可在中斷情境用會睡眠的鎖(互斥鎖),因為中斷處理常式不能睡眠。
這之所以重要,是因為裝置驅動程式本質上是並行的——硬體不會等你的處理常式做完——而這些競爭與時序相關,所以它們通過每一項測試,然後在現場破壞記憶體。誠實的總結:鎖的通論在別處;屬於驅動程式專屬的,是「鑑於中斷隨時可能來襲,該用哪種鎖變體」的紀律。規則很具體:一個與中斷處理常式共享的鎖,必須在非中斷路徑停用中斷,而中斷情境絕不可睡眠,所以與處理常式共享的資料要用自旋鎖(絕不用互斥鎖)守護。
// 行程情境:持鎖時停用 IRQ unsigned long flags; spin_lock_irqsave(&dev->lock, flags); ... 碰共享佇列 ... spin_unlock_irqrestore(&dev->lock, flags); // 中斷處理常式:同一個鎖,用普通 spin_lock spin_lock(&dev->lock); ... 碰共享佇列 ... spin_unlock(&dev->lock); // 在此用互斥鎖是非法的
與中斷處理常式共享的資料在行程情境需要 spin_lock_irqsave,這樣處理常式才不會對被持有的鎖造成死結。
與中斷處理常式共享的鎖,必須在非中斷路徑停用中斷(spin_lock_irqsave),而中斷情境絕不可睡眠——所以絕不在那裡取互斥鎖。用普通自旋鎖卻不停用 IRQ,會招致對自己的死結。