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