W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
對于互斥, 旗標是一個有用的工具, 但是它們不是內(nèi)核提供的唯一這樣的工具. 相反, 大部分加鎖是由一種稱為自旋鎖的機制來實現(xiàn). 不象旗標, 自旋鎖可用在不能睡眠的代碼中, 例如中斷處理. 當正確地使用了, 通常自旋鎖提供了比旗標更高的性能. 然而, 它們確實帶來對它們用法的一套不同的限制.
自旋鎖概念上簡單. 一個自旋鎖是一個互斥設(shè)備, 只能有 2 個值:"上鎖"和"解鎖". 它常常實現(xiàn)為一個整數(shù)值中的一個單個位. 想獲取一個特殊鎖的代碼測試相關(guān)的位. 如果鎖是可用的, 這個"上鎖"位被置位并且代碼繼續(xù)進入臨界區(qū). 相反, 如果這個鎖已經(jīng)被別人獲得, 代碼進入一個緊湊的循環(huán)中反復(fù)檢查這個鎖, 直到它變?yōu)榭捎? 這個循環(huán)就是自旋鎖的"自旋"部分.
當然, 一個自旋鎖的真實實現(xiàn)比上面描述的復(fù)雜一點. 這個"測試并置位"操作必須以原子方式進行, 以便只有一個線程能夠獲得鎖, 就算如果有多個進程在任何給定時間自旋. 必須小心以避免在超線程處理器上死鎖 -- 實現(xiàn)多個虛擬 CPU 以共享一個單個處理器核心和緩存的芯片. 因此實際的自旋鎖實現(xiàn)在每個 Linux 支持的體系上都不同. 核心的概念在所有系統(tǒng)上相同, 然而, 當有對自旋鎖的競爭, 等待的處理器在一個緊湊循環(huán)中執(zhí)行并且不作有用的工作.
它們的特性上, 自旋鎖是打算用在多處理器系統(tǒng)上, 盡管一個運行一個搶占式內(nèi)核的單處理器工作站的行為如同 SMP, 如果只考慮到并發(fā). 如果一個非搶占的單處理器系統(tǒng)進入一個鎖上的自旋, 它將永遠自旋; 沒有其他的線程再能夠獲得 CPU 來釋放這個鎖. 因此, 自旋鎖在沒有打開搶占的單處理器系統(tǒng)上的操作被優(yōu)化為什么不作, 除了改變 IRQ 屏蔽狀態(tài)的那些. 由于搶占, 甚至如果你從不希望你的代碼在一個 SMP 系統(tǒng)上運行, 你仍然需要實現(xiàn)正確的加鎖.
自旋鎖原語要求的包含文件是 <linux/spinlock.h>. 一個實際的鎖有類型 spinlock_t. 象任何其他數(shù)據(jù)結(jié)構(gòu), 一個 自旋鎖必須初始化. 這個初始化可以在編譯時完成, 如下:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
或者在運行時使用:
void spin_lock_init(spinlock_t *lock);
在進入一個臨界區(qū)前, 你的代碼必須獲得需要的 lock , 用:
void spin_lock(spinlock_t *lock);
注意所有的自旋鎖等待是, 由于它們的特性, 不可中斷的. 一旦你調(diào)用 spin_lock, 你將自旋直到鎖變?yōu)榭捎?
為釋放一個你已獲得的鎖, 傳遞它給:
void spin_unlock(spinlock_t *lock);
有很多其他的自旋鎖函數(shù), 我們將很快都看到. 但是沒有一個背離上面列出的函數(shù)所展示的核心概念. 除了加鎖和釋放, 沒有什么可對一個鎖所作的. 但是, 有幾個規(guī)則關(guān)于你必須如何使用自旋鎖. 我們將用一點時間來看這些, 在進入完整的自旋鎖接口之前.
想象一會兒你的驅(qū)動請求一個自旋鎖并且在它的臨界區(qū)里做它的事情. 在中間某處, 你的驅(qū)動失去了處理器. 或許它已調(diào)用了一個函數(shù)( copy_from_user, 假設(shè)) 使進程進入睡眠. 或者, 也許, 內(nèi)核搶占發(fā)威, 一個更高優(yōu)先級的進程將你的代碼推到一邊. 你的代碼現(xiàn)在持有一個鎖, 在可見的將來的如何時間不會釋放這個鎖. 如果某個別的線程想獲得同一個鎖, 它會, 在最好的情況下, 等待( 在處理器中自旋 )很長時間. 最壞的情況, 系統(tǒng)可能完全死鎖.
大部分讀者會同意這個場景最好是避免. 因此, 應(yīng)用到自旋鎖的核心規(guī)則是任何代碼必須, 在持有自旋鎖時, 是原子性的. 它不能睡眠; 事實上, 它不能因為任何原因放棄處理器, 除了服務(wù)中斷(并且有時即便此時也不行)
內(nèi)核搶占的情況由自旋鎖代碼自己處理. 內(nèi)核代碼持有一個自旋鎖的任何時間, 搶占在相關(guān)處理器上被禁止. 即便單處理器系統(tǒng)必須以這種方式禁止搶占以避免競爭情況. 這就是為什么需要正確的加鎖, 即便你從不期望你的代碼在多處理器機器上運行.
在持有一個鎖時避免睡眠是更加困難; 很多內(nèi)核函數(shù)可能睡眠, 并且這個行為不是都被明確記錄了. 拷貝數(shù)據(jù)到或從用戶空間是一個明顯的例子: 請求的用戶空間頁可能需要在拷貝進行前從磁盤上換入, 這個操作顯然需要一個睡眠. 必須分配內(nèi)存的任何操作都可能睡眠. kmalloc 能夠決定放棄處理器, 并且等待更多內(nèi)存可用除非它被明確告知不這樣做. 睡眠可能發(fā)生在令人驚訝的地方; 編寫會在自旋鎖下執(zhí)行的代碼需要注意你調(diào)用的每個函數(shù).
這有另一個場景: 你的驅(qū)動在執(zhí)行并且已經(jīng)獲取了一個鎖來控制對它的設(shè)備的存取. 當持有這個鎖時, 設(shè)備發(fā)出一個中斷, 使得你的中斷處理運行. 中斷處理, 在存取設(shè)備之前, 必須獲得鎖. 在一個中斷處理中獲取一個自旋鎖是一個要做的合法的事情; 這是自旋鎖操作不能睡眠的其中一個理由. 但是如果中斷處理和起初獲得鎖的代碼在同一個處理器上會發(fā)生什么? 當中斷處理在自旋, 非中斷代碼不能運行來釋放鎖. 這個處理器將永遠自旋.
避免這個陷阱需要在持有自旋鎖時禁止中斷( 只在本地 CPU ). 有各種自旋鎖函數(shù)會為你禁止中斷( 我們將在下一節(jié)見到它們 ). 但是, 一個完整的中斷討論必須等到第 10 章了.
關(guān)于自旋鎖使用的最后一個重要規(guī)則是自旋鎖必須一直是盡可能短時間的持有. 你持有一個鎖越長, 另一個進程可能不得不自旋等待你釋放它的時間越長, 它不得不完全自旋的機會越大. 長時間持有鎖也阻止了當前處理器調(diào)度, 意味著高優(yōu)先級進程 -- 真正應(yīng)當能獲得 CPU 的 -- 可能不得不等待. 內(nèi)核開發(fā)者盡了很大努力來減少內(nèi)核反應(yīng)時間( 一個進程可能不得不等待調(diào)度的時間 )在 2.5 開發(fā)系列. 一個寫的很差的驅(qū)動會摧毀所有的進程, 僅僅通過持有一個鎖太長時間. 為避免產(chǎn)生這類問題, 重視使你的鎖持有時間短.
我們已經(jīng)看到 2 個函數(shù), spin_lock 和 spin_unlock, 可以操作自旋鎖. 有其他幾個函數(shù), 然而, 有類似的名子和用途. 我們現(xiàn)在會展示全套. 這個討論將帶我們到一個我們無法在幾章內(nèi)適當涵蓋的地方; 自旋鎖 API 的完整理解需要對中斷處理和相關(guān)概念的理解.
實際上有 4 個函數(shù)可以加鎖一個自旋鎖:
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock)
我們已經(jīng)看到自旋鎖如何工作. spin_loc_irqsave 禁止中斷(只在本地處理器)在獲得自旋鎖之前; 之前的中斷狀態(tài)保存在 flags 里. 如果你絕對確定在你的處理器上沒有禁止中斷的(或者, 換句話說, 你確信你應(yīng)當在你釋放你的自旋鎖時打開中斷), 你可以使用 spin_lock_irq 代替, 并且不必保持跟蹤 flags. 最后, spin_lock_bh 在獲取鎖之前禁止軟件中斷, 但是硬件中斷留作打開的.
如果你有一個可能被在(硬件或軟件)中斷上下文運行的代碼獲得的自旋鎖, 你必須使用一種 spin_lock 形式來禁止中斷. 其他做法可能死鎖系統(tǒng), 遲早. 如果你不在硬件中斷處理里存取你的鎖, 但是你通過軟件中斷(例如, 在一個 tasklet 運行的代碼, 在第 7 章涉及的主題 ), 你可以使用 spin_lock_bh 來安全地避免死鎖, 而仍然允許硬件中斷被服務(wù).
也有 4 個方法來釋放一個自旋鎖; 你用的那個必須對應(yīng)你用來獲取鎖的函數(shù).
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
每個 spin_unlock 變體恢復(fù)由對應(yīng)的 spin_lock 函數(shù)鎖做的工作. 傳遞給 spin_unlock_irqrestore 的 flags 參數(shù)必須是傳遞給 spin_lock_irqsave 的同一個變量. 你必須也調(diào)用 spin_lock_irqsave 和 spin_unlock_irqrestore 在同一個函數(shù)里. 否則, 你的代碼可能破壞某些體系.
還有一套非阻塞的自旋鎖操作:
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
這些函數(shù)成功時返回非零( 獲得了鎖 ), 否則 0. 沒有"try"版本來禁止中斷.
內(nèi)核提供了一個自旋鎖的讀者/寫者形式, 直接模仿我們在本章前面見到的讀者/寫者旗標. 這些鎖允許任何數(shù)目的讀者同時進入臨界區(qū), 但是寫者必須是排他的存取. 讀者寫者鎖有一個類型 rwlock_t, 在 <linux/spinlokc.h> 中定義. 它們可以以 2 種方式被聲明和被初始化:
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Dynamic way */
可用函數(shù)的列表現(xiàn)在應(yīng)當看來相當類似. 對于讀者, 下列函數(shù)是可用的:
void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
有趣地, 沒有 read_trylock. 對于寫存取的函數(shù)是類似的:
void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
讀者/寫者鎖能夠餓壞讀者, 就像 rwsem 一樣. 這個行為很少是一個問題; 然而, 如果有足夠的鎖競爭來引起饑餓, 性能無論如何都不行.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: