目錄
與其他鎖一樣,自旋鎖也用於保護臨界區,但是自旋鎖主要是用於在SMP上保護臨界區。在SMP上,自旋鎖最多只能被一個可執行線程持有,如果一個線程嘗試獲得一個被爭用的自旋鎖,該線程將一直旋轉(while循環)直到鎖可用;如果鎖未被爭用,請求鎖的執行線程將立刻爭用它,並繼續執行。
LINUX下自旋鎖的基本使用方法:
聲明鎖:
spinlock_t lock;
初始化:
lock = SPIN_LOCK_UNLOCKED;
或者
spin_lock_init(&lock);
加鎖有4個接口,3個會阻塞,1個不阻塞
spin_lock(&lock);//獲取自旋鎖 spin_lock_irq(&lock);//關中斷,獲取自旋鎖,不建議使用 spin_lock_irqsave(&lock, flags);//關中斷,保存中斷狀態,獲取自旋鎖 spin_trylock(&lock);//與spin_lock一樣,但是獲取不到的時候不阻塞,返回非0
對應的解鎖接口有3個
spin_unlock(&lock);//spin_lock和spin_trylock都用該接口解鎖 spin_unlock_irq(&lock);//不建議使用 spin_unlock_irqrestore(&lock, flags);
另外還提供了一個獲取鎖狀態的接口:
spin_is_locked(&lock);//如果指定的鎖被獲取,返回非0,否則,返回0
對於SMP,自旋鎖將在禁止搶占后,while循環自旋直到鎖可用;
對於UP,自旋鎖的行為有點不一樣,具體表現為:
- 內核不支持搶占,則spin_lock是個空函數,spin_unlock也是空函數;
- 內核支持搶占,則spin_lock關搶占,spin_unlock開搶占。
要分析SMP和UP的區別,還得先理解UP下內核支持搶占與否的區分。
在不支持搶占的時候,內核調度時機為:
- 內核代碼一直要執行到完成(返回用戶空間);
- 阻塞或主動調用schedule();
支持內核搶占的內核,則在以下情況會發生搶占:
- 從中斷處理程序回到內核空間且內核具有搶占性時;
- 當內核代碼再一次具有可搶占性時;(如時鍾中斷,發現進程時間片已經用完,則發生進程搶占)
- 內核顯式調用schedule();
- 內核中的任務阻塞(同樣導致schedule());
而對於支持搶占的內核, spin_lock/spin_unlock自然只需要關閉/恢復搶占功能即可。
由於自旋鎖不睡眠,既可以用於進程上下文,也可用於非進程上下文,這是它與信號量相比的一個優勢。
正常來講,如果自旋鎖都是在進程上下文中使用,則建議使用spin_lock/spin_unlock。
若在中斷處理例程中也要使用自旋鎖,則所有爭用同一個鎖的地方應使用spin_lock_irqsave/spin_unlock_irqrestore。
若沒有在中斷而在下半部使用自旋鎖,則所有急用同一個鎖的地方可以使用spin_lock_bh/spin_unlock_bh,這對函數平時用得比較少。
注:自旋鎖用在中斷例程中,若是進程上下文中的內核代碼使用spin_lock,則在臨界區,可能發生中斷 ,要么原子性被破壞(UP),要么造成死鎖(SMP).
在UP下,正如前面所說,原子性得不到保證。
而在SMP下,則可能發生死鎖:
假設有3個進程A,B,C,2個CPU稱CPU0,CPU1.
CPU0上A進程獲取自旋鎖進入睡眠,調度了B進程,B進程將自旋;
CPU1上調度了C進程也將自旋。
B等着A釋放鎖,A等着B釋放CPU,自旋后的CPU不能發生調度,CPU0和CPU1將一直自旋下去,形成了死鎖。
使用鎖的時候一定要對症下葯,明確保護的是數據而不是代碼,這是使用鎖的正確思考方式。經常看別人代碼的人應該會有所體會,針對代碼加鎖常常使代碼難以理解,特別是復雜的程序,往往加鎖沒做好,引起競爭條件。
不防在使用鎖的時候先思考一下自己要保護什么,然后給對應的鎖取跟數據相關的名稱。比如"int foo;spinlock_t foo_lock;"表示foo由foo_lock加鎖;
《Linux內核設計與實現》