鎖定課程¶
課程 1:自旋鎖¶
最基本的鎖定原語是自旋鎖
static DEFINE_SPINLOCK(xxx_lock);
unsigned long flags;
spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);
以上總是安全的。它將_本地_停用中斷,但是自旋鎖本身會保證全域性鎖,因此它將保證在由該鎖保護的區域中只有一個控制執行緒。即使在 UP 下也能很好地工作,因此程式碼_無需_擔心 UP 與 SMP 問題:自旋鎖在兩者下都能正確工作。
注意! 自旋鎖對記憶體的影響在以下文件中進一步描述:
Documentation/memory-barriers.txt
獲取操作。
釋放操作。
以上通常非常簡單(通常對於大多數事情,您只需要一個自旋鎖 - 使用多個自旋鎖會使事情變得更加複雜甚至更慢,並且通常只有在您**知道**需要拆分序列時才值得:如果不確定,請不惜一切代價避免)。
這實際上是關於自旋鎖的唯一真正困難的部分:一旦您開始使用自旋鎖,它們往往會擴充套件到您可能之前沒有注意到的區域,因為您必須確保自旋鎖正確地保護共享資料結構**在它們被使用的每個地方**。 自旋鎖最容易新增到與其他程式碼完全獨立的地方(例如,從未有人接觸過的內部驅動程式資料結構)。
注意! 僅當您**還**使用鎖本身跨 CPU 進行鎖定時,自旋鎖才是安全的,這意味著訪問共享變數的**所有**內容都必須就它們想要使用的自旋鎖達成一致。
課程 2:讀者-寫者自旋鎖。¶
如果您的資料訪問具有非常自然的模式,您通常傾向於主要從共享變數中讀取資料,則自旋鎖的讀者-寫者鎖(rw_lock)版本有時很有用。 它們允許多個讀者同時位於同一臨界區中,但是如果有人想要更改變數,則必須獲得獨佔寫鎖。
注意! 讀者-寫者鎖比簡單的自旋鎖需要更多的原子記憶體操作。 除非讀者臨界區很長,否則最好只使用自旋鎖。
這些例程看起來與上面相同
rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);
unsigned long flags;
read_lock_irqsave(&xxx_lock, flags);
.. critical section that only reads the info ...
read_unlock_irqrestore(&xxx_lock, flags);
write_lock_irqsave(&xxx_lock, flags);
.. read and write exclusive access to the info ...
write_unlock_irqrestore(&xxx_lock, flags);
以上型別的鎖可能對複雜的資料結構(如連結串列)很有用,尤其是搜尋條目而無需更改列表本身。 讀鎖允許多個併發讀者。 任何**更改**列表的內容都必須獲得寫鎖。
注意! RCU 更適合列表遍歷,但需要仔細注意設計細節(請參閱 使用 RCU 保護主要用於讀取的連結串列)。
另外,您不能將讀鎖“升級”為寫鎖,因此,如果在_任何_時候需要進行任何更改(即使您不是每次都這樣做),則必須在一開始就獲得寫鎖。
注意! 我們正在努力在大多數情況下刪除讀者-寫者自旋鎖,因此請不要在沒有達成共識的情況下新增新的自旋鎖。 (相反,請參閱 RCU 概念 以獲取完整資訊。)
課程 3:重新審視自旋鎖。¶
上面的單個自旋鎖原語絕不是唯一的。 它們是最安全的,並且是在所有情況下都適用的,但是在某種程度上**因為**它們是安全的,所以它們也相當慢。 它們比需要的慢,因為它們確實必須停用中斷(這只是 x86 上的單個指令,但是這是一個昂貴的指令 - 並且在其他體系結構上可能會更糟)。
如果您需要在多個 CPU 之間保護資料結構,並且想要使用自旋鎖,則可能會使用更便宜的自旋鎖版本。 僅當您知道自旋鎖永遠不會在中斷處理程式中使用時,才能使用非 irq 版本
spin_lock(&lock);
...
spin_unlock(&lock);
(當然,還有等效的讀寫版本)。 自旋鎖將保證相同的獨佔訪問,並且速度會快得多。 如果您知道所討論的資料僅從“程序上下文”進行操作,即不涉及中斷,則這很有用。
如果您有中斷來使用自旋鎖,則不得使用這些版本的原因是您可能會遇到死鎖
spin_lock(&lock);
...
<- interrupt comes in:
spin_lock(&lock);
其中一箇中斷嘗試鎖定一個已經鎖定的變數。 如果另一箇中斷髮生在另一個 CPU 上,這是可以的,但是如果中斷髮生在已經持有鎖的同一 CPU 上,則這是_不可以_的,因為鎖顯然永遠不會被釋放(因為中斷正在等待鎖,並且持鎖者被中斷中斷,並且在中斷被處理之前不會繼續)。
(這也是自旋鎖的 irq 版本只需要停用_本地_中斷的原因 - 在其他 CPU 上的中斷中使用自旋鎖是可以的,因為另一個 CPU 上的中斷不會中斷持有鎖的 CPU,因此持鎖者可以繼續並最終釋放鎖)。
Linus
參考資訊:¶
對於動態初始化,請根據需要使用 spin_lock_init() 或 rwlock_init()
spinlock_t xxx_lock;
rwlock_t xxx_rw_lock;
static int __init xxx_init(void)
{
spin_lock_init(&xxx_lock);
rwlock_init(&xxx_rw_lock);
...
}
module_init(xxx_init);
對於靜態初始化,請根據需要使用 DEFINE_SPINLOCK() / DEFINE_RWLOCK() 或 __SPIN_LOCK_UNLOCKED() / __RW_LOCK_UNLOCKED()。