设备驱动中的并发控制-自旋锁
????????在linux中提供了一些鎖機(jī)制來避免競爭,引入鎖的機(jī)制是因?yàn)閱为?dú)的原子操作不能滿足復(fù)雜的內(nèi)核設(shè)計(jì)需求。Linux中一般可以認(rèn)為有兩種鎖,一種是自旋鎖,另一種是信號量。這兩種鎖是為了解決內(nèi)核中遇到的不同問題開發(fā)的。其實(shí)現(xiàn)機(jī)制和應(yīng)用場合有所不同。
自旋鎖是一種簡單的并發(fā)控制機(jī)制,其是實(shí)現(xiàn)信號量和完成量的基礎(chǔ)。自旋鎖對資源有很好的保護(hù)作用。
????????自旋鎖的使用
在linux中,自旋鎖的類型為struct spinlock_t。內(nèi)核提供了一系列的函數(shù)對struct spinlock_t進(jìn)行操作。
1. 定義和初始化自旋鎖
????????spinlock_t lock; /* 定義自旋鎖 */
????????自旋鎖必須初始化才能被使用,對自旋鎖的初始化可以在編譯階段通過宏來實(shí)現(xiàn)。初始化自旋鎖可以使用宏SPIN_LOCK_UNLOCKED,這個(gè)宏表示一個(gè)沒有鎖定的自旋鎖。
????????spinlock_t lock = SPIN_LOCK_UNLOCKED; /* 初始化一個(gè)未使用的自旋鎖 */
????????在運(yùn)行階段,可以使用spin_lock_init()函數(shù)動(dòng)態(tài)的初始化一個(gè)自旋鎖:
????????void spin_lock_init(spinlock_t lock);
2. 鎖定自旋鎖
????????在進(jìn)入臨界區(qū)前,需要使用spin_lock宏來獲得自旋鎖。spin_lock宏定義如下:
????????#define spin_lock(lock) ??_spin_lock(lock)
????????這個(gè)宏用來獲得lock自旋鎖,如果能夠立即獲得自旋鎖,則宏立刻返回;否則,這個(gè)鎖會(huì)一直自旋在那里,直到該鎖被其他線程釋放為止。
3. 釋放自旋鎖
????????當(dāng)不在使用臨界區(qū)時(shí),需要使用spin_unlock宏釋放自旋鎖。spin_unlock宏定義如下:
????????#define spin_unlock(lock) ??_spin_unlock(lock)
????????這個(gè)宏用來釋放lock自旋鎖,當(dāng)調(diào)用該宏之后,鎖立刻被釋放。
4. 使用自旋鎖
????????最基本的自旋鎖 API 函數(shù)如下所示:
4.1 定義并初始化一個(gè)自旋鎖變量。
??????DEFINE_SPINLOCK(spinlock_t lock);
4.2 初始化自旋鎖:
????????int spin_lock_init(spinlock_t *lock);
4.3 獲取指定的自旋鎖,也叫做加鎖。
????????void spin_lock(spinlock_t *lock);
4.4釋放指定的自旋鎖。
????????void spin_unlock(spinlock_t *lock);
4.5 嘗試獲取指定的自旋鎖,如果沒有獲取到就返回 0
????????int spin_trylock(spinlock_t *lock);
4.6 檢查指定的自旋鎖是否被獲取,如果沒有被獲取就返回非 0,否則返回 0。
?????????int spin_is_locked(spinlock_t *lock);
????????自旋鎖API函數(shù)適用于SMP或支持搶占的單CPU下線程之間的并發(fā)訪問,也就是用于線程與線程之間。
????????使用自旋鎖的方法:
????????spinlock_t lock; ???????/* 定義自旋鎖 */
????????spin_lock_init(&lock); ??/* 初始化自旋鎖 */
????????spin_lock(&lock); ??????/* 獲得自旋鎖 */
????????/* 臨界資源 */
????????spin_unlock(&lock); ????/* 釋放自旋鎖 */
????????在驅(qū)動(dòng)程序中,有些設(shè)備只允許打開一次,那么就需要一個(gè)自旋鎖保護(hù)表示設(shè)備的打開或關(guān)閉狀態(tài)的變量count。此處count屬于臨界資源,如果不對count進(jìn)行保護(hù),當(dāng)設(shè)備頻繁打開時(shí),可能會(huì)出現(xiàn)錯(cuò)誤得count計(jì)數(shù)。使用自旋鎖包含count的代碼如下:
int count=0; spinlock_t lock; int xxx_int(void) {...spin_lock_init(&lock);... } /* 文件打開函數(shù) */ int xxx_open(struct inode *inode, struct file *filp) {...spin_lock(&lock);if(count){spin_unlock(&lock);return -RBUSY;}count++;spin_unlock(&lock);... } /* 文件釋放函數(shù) */ int xxx_release(struct inode *inode, struct file *filp) {... spin_lock(&lock);count--;spin_unlock(&lock);... }5. 死鎖
????????兩種死鎖場景:
5.1 由睡眠引起的死鎖
????????被自旋鎖保護(hù)的臨界區(qū)一定不能調(diào)用任何能夠引起睡眠和阻塞的API函數(shù),否則的話會(huì)可能會(huì)導(dǎo)致死鎖現(xiàn)象的發(fā)生。自旋鎖會(huì)自動(dòng)禁止搶占,也就說當(dāng)線程A得到鎖以后會(huì)暫時(shí)禁止內(nèi)核搶占。如果線程 A 在持有鎖期間進(jìn)入了休眠狀態(tài),那么線程 A 會(huì)自動(dòng)放棄 CPU 使用權(quán)。線程 B 開始運(yùn)行,線程 B 也想要獲取鎖,但是此時(shí)鎖被 A 線程持有,而且內(nèi)核搶占還被禁止了。線程 B 無法被調(diào)度出去,那么線程 A 就無法運(yùn)行,鎖也就無法釋放,死鎖發(fā)生了。
5.2 由中斷引起的死鎖
????????如果線程 A 時(shí)運(yùn)行,中斷也想訪問共享資源,那該怎么辦呢?首先可以肯定的是,中斷里面可以使用自旋鎖,但是在中斷里面使用自旋鎖的時(shí)候,在獲取鎖之前一定要先禁止本地中斷(也就是本 CPU 中斷,對于多核 SOC來說會(huì)有多個(gè) CPU 核),否則可能導(dǎo)致鎖死現(xiàn)象的發(fā)生,如圖下所示:
?????????在圖中,線程 A 先運(yùn)行,并且獲取到了 lock 這個(gè)鎖,當(dāng)線程 A 運(yùn)行 functionA 函數(shù)的時(shí)候中斷發(fā)生了,中斷搶走了 CPU 使用權(quán)。右邊的中斷服務(wù)函數(shù)也要獲取 lock 這個(gè)鎖,但是這個(gè)鎖被線程 A 占有著,中斷就會(huì)一直自旋,等待鎖有效。但是在中斷服務(wù)函數(shù)執(zhí)行完之前,線程 A 是不可能執(zhí)行的,場面就這么僵持著,死鎖發(fā)生。
5.3 解決死鎖問題
????????最好的解決方法就是獲取鎖之前關(guān)閉本地中斷,Linux 內(nèi)核提供了相應(yīng)的 API 函數(shù),如下所示:
5.3.1 禁止本地中斷,并獲取自旋鎖。
????????void spin_lock_irq(spinlock_t *lock);
5.3.2 激活本地中斷,并釋放自旋鎖。
????????void spin_unlock_irq(spinlock_t *lock);
5.3.3 保存中斷狀態(tài),禁止本地中斷,并獲取自旋鎖。
????????void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)
5.3.4 將中斷狀態(tài)恢復(fù)到以前的狀態(tài),并且激活本地中斷,釋放自旋鎖。
????????void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
????????使用 spin_lock_irq/spin_unlock_irq 的時(shí)候需要用戶能夠確定加鎖之前的中斷狀態(tài),但實(shí)際上內(nèi)核很龐大,運(yùn)行也是“千變?nèi)f化”,我們是很難確定某個(gè)時(shí)刻的中斷狀態(tài),因此不推薦使用
spin_lock_irq/spin_unlock_irq。建議使用 spin_lock_irqsave/spin_unlock_irqrestore,因?yàn)檫@一組函數(shù)會(huì)保存中斷狀態(tài),在釋放鎖的時(shí)候會(huì)恢復(fù)中斷狀態(tài)。一般在線程中使用 spin_lock_irqsave/
spin_unlock_irqrestore,在中斷中使用 spin_lock/spin_unlock,示例代碼如下所示:
/* 線程 A */ void functionA (){unsigned long flags; /* 中斷狀態(tài) */spin_lock_irqsave(&lock, flags) /* 獲取鎖 *//* 臨界區(qū) */spin_unlock_irqrestore(&lock, flags) /* 釋放鎖 */ }/* 中斷服務(wù)函數(shù) */ void irq() {spin_lock(&lock) /* 獲取鎖 *//* 臨界區(qū) */spin_unlock(&lock) /* 釋放鎖 */ }6. 自旋鎖的使用注意事項(xiàng)
????????在使用自旋鎖時(shí),需要注意以下幾項(xiàng):
6.1 自旋鎖是一種忙等待。Linux中,自旋鎖當(dāng)條件不滿足時(shí),會(huì)一直不斷地循環(huán)條件是否被滿足。如果滿足就解鎖,繼續(xù)運(yùn)行下面的代碼。這種忙等待機(jī)制對系統(tǒng)的性能是有影響的。所以應(yīng)該注意使用自旋鎖時(shí),自旋鎖不應(yīng)該長時(shí)間的持有,它是一種適合短時(shí)間鎖定的輕量級的加鎖機(jī)制。
6.2 自旋鎖不能遞歸使用。這是因?yàn)樽孕i被設(shè)計(jì)成在不同線程或函數(shù)之間同步。如果一個(gè)線程在已經(jīng)持有自旋鎖時(shí),其處于忙等待狀態(tài),則已經(jīng)沒有機(jī)會(huì)釋放自己持有的鎖了。如果這時(shí)在調(diào)用自身,則自旋鎖永遠(yuǎn)沒有執(zhí)行的機(jī)會(huì)了。所以類似下面的遞歸形式不能使用自旋鎖:
void A() {鎖定自旋鎖;A();鎖定自旋鎖; }6.3 自旋鎖保護(hù)的臨界區(qū)內(nèi)不能調(diào)用任何可能導(dǎo)致線程休眠的 API 函數(shù),否則的話可能導(dǎo)致死鎖。
補(bǔ)充:
臨界資源:系統(tǒng)中某些資源一次只允許一個(gè)進(jìn)程使用,稱這樣的資源為臨界資源或互斥資源或共享變量。
臨界區(qū):指的是一個(gè)訪問臨界資源的程序片段。
還有其他類型的鎖,如順序鎖,待學(xué)習(xí)。
總結(jié)
以上是生活随笔為你收集整理的设备驱动中的并发控制-自旋锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 链表分解单双数c语言代码,编写一程序,将
- 下一篇: 《佛密诸事》第十一章:解读大悲咒