linux 自旋鎖和信號量【轉】
轉自:http://blog.csdn.net/xu_guo/article/details/6072823
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
自旋鎖最多只能被一個可執行線程持有(讀寫自旋鎖除外)。自旋鎖不會引起調用者睡眠,如果一個執行線程試圖獲得一個已經被持有的自旋鎖,那麽線程就會一直進行忙循環,一直等待下去(一直占用 CPU ),在那裏看是否該自旋鎖的保持者已經釋放了鎖, " 自旋 " 一詞就是因此而得名。
由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。
信號量和讀寫信號量適合於保持時間較長的情況,它們會導致調用者睡眠,因此只能在進程上下文使用(因為中斷的上下文不允許休眠)( _trylock 的變種能夠在中斷上下文使用);而自旋鎖 適合於保持時間非常短的情況,因為一個被爭用的自旋鎖使得請求它的線程在等待重新可用時自旋,特別浪費處理時間,這是自旋鎖的要害之處,所以自旋鎖不應該 被長時間持有。在實際應用中自旋鎖代碼只有幾行,而持有自旋鎖的時間也一般不會超過兩次上下方切換,因線程一旦要進行切換,就至少花費切出切入兩次,自旋 鎖的占用時間如果遠遠長於兩次上下文切換,我們就可以讓線程睡眠,這就失去了設計自旋鎖的意義。
如果被保護的共享資源只在進程上下文訪問,使用信號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。
自旋鎖保持期間是搶占失效的(內核不允許被搶占) ,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內核可搶占或 SMP 的情況下才真正需要,在單 CPU 且不可搶占的內核下,自旋鎖的所有操作都是空操作。
(問題:在單 CPU 可搶占內核中,如果一個進程請求自旋鎖,那麽他一直占用 CPU 循環等待自旋鎖可用,那麽原來占用自旋鎖的進程得不到使用 CPU
回答:這種情況應該是在實際中不會發生的。因為考慮最開始的情況,一個進程需要自旋鎖,而且這個時候自旋鎖沒有被占用,所以他加鎖,進入臨界區。因為這個時候內核已經被自旋鎖設置成了非搶占式,所以正在運行的進程不會被調用處 CPU ,因此也不會再有其他的進程來請求這個已經被占用的自旋鎖,當臨界區代碼處理完成後,釋放了自旋鎖,此時內核又被自旋鎖設置成了可搶占模式,這個時候其他的進程可以被調度,因此可以再請求自旋鎖等操作。因此問題中的情況應該在實際中不會發生。
註意,如果在一個已經對某個自旋鎖加鎖的進程的臨界區中又申請對這個自旋鎖加鎖,則會導致進程自旋在那裏,引起死機。)
一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麽將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麽獲取鎖操作將自旋在那裏,直到該自旋鎖的保持者釋放了鎖。
無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執行單元獲得鎖(讀寫鎖除外)。自旋鎖的實現和體系結構密切相關,代碼一般通過匯編實現,定義在文件 , 實際用到的接口定義在文件夾中。
因為自旋鎖在同一時刻至多被一個執行線程持有,所以一個時刻只能有一個線程位於臨界區,這就為多處理器提供了防止並發訪問所需的保護機制,但是在單處理器上,編譯的時候不會加入自旋鎖。它僅僅被當作一個設置內核搶占機制是否被啟用的開關( gx 自己的理解:在單內核可搶占式內核中,對自旋鎖加鎖導致禁止搶占,對自旋鎖解鎖導致恢復搶占模式。)。 註意, Linux 內核實現的自旋鎖是不可遞歸的,這一點不同於自旋鎖 在其他操作系統中的實現,如果你想得到一個你正持有的鎖,你必須自旋,等待你自己釋放這個鎖,但是你處於自旋忙等待中,所以永遠沒有機會釋放鎖,於是你就 被自己鎖死了,一定要註意!
自旋鎖可以用在中斷處理程序中,但是在使用時一定要在獲取鎖之前,首先禁止本地中斷(當前處理器上的中 斷),否則中斷處理程序就可能打斷正持有鎖的內核代碼,有可能會試圖爭用這個已經被持有的自旋鎖。這樣一來,中斷處理程序就會自旋,等待該鎖重新可用, 但是鎖的持有者在這個中斷處理程序執行完畢之前不可能運行,這就會造成雙重請求死鎖。
自旋鎖與下半部:由於下半部(中斷程序下半部)可以搶占進程上下文中的代碼,所以當下半部和進程上下文共享數據時,必須對進程上下文中的共享數據進行保護,所以需要加鎖的同時還要禁止下半部執行。同樣, 由於中斷處理程序可以搶占下半部,所以如果中斷處理程序和下半部共享數據,那麽就必須在獲取恰當的鎖的同時還要禁止中斷。對於軟中斷,無論是否同種類型, 如果數據被軟中斷共享,那麽它必須得到鎖的保護,因為同種類型的兩個軟中斷也可以同進運行在一個系統的多個處理器上。但是,同一個處理器上的一個軟中斷絕 不會搶占另一個軟中斷( gx :因為在中斷處理代碼運行上下文中禁止響應同類型的中斷?) ,因此,根本不需要禁止下半部。
自旋鎖在內核中主要用來防止多處理器中並發訪問臨界區,防止內核搶占造成的競爭。 另外自旋鎖不允許任務睡眠 ( 持有自旋鎖的任務睡眠會造成自死鎖 —— 因為睡眠有可能造成持有鎖的內核任務被重新調度,而再次申請自己已持有的鎖 ) ,它能夠在中斷上下文中使用 。
Lnux 中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的信號量時,信號量會將其推入等待隊列,然後讓其睡眠。這時處理器獲得自由去執行其它代碼。當持有信號量的進程將信號量釋放後,在等待隊列中的一個任務將被喚醒,從而便可以獲得這個信號量。
信號量的睡眠特性,使得信號量適用於鎖會被長時間持有的情況;只能在進程上下文中使用,因為中斷上下文中是不能被調度的;另外當代碼持有信號量時,不可以再持有自旋鎖。
雖然聽起來兩者之間的使用條件復雜,其實在實際使用中信號量和自旋鎖並不易混淆。註意以下原則 :
如果代碼需要睡眠 —— 這往往是發生在和用戶空間同步時 —— 使用信號量是唯一的選擇。由於不受睡眠的限制,使用信號量通常來說更加簡單一些。如果需要在自旋鎖和信號量中作選擇,應該取決於鎖被持有的時間長短。理想情況是所有的鎖都應該盡可能短的被持有,但是如果鎖的持有時間較長的話,使用信號量是更好的選擇。另外,信號量不同於自旋鎖,它不會關閉內核搶占 ,所以持有信號量的代碼可以被搶占。這意味者信號量不會對影響調度反應時間帶來負面影響。
-------- 自旋鎖對信號量 ------------------------------------------------------
需求 建議的加鎖方法
低開銷加鎖 優先使用自旋鎖
短期鎖定 優先使用自旋鎖
長期加鎖 優先使用信號量
中斷上下文中加鎖 使用自旋鎖
持有鎖是需要睡眠、調度 使用信號量
原子操作、信號量、讀寫信號量和自旋鎖:
http://kom118.blog.163.com/blog/static/476673182010312113630768/
自旋鎖和信號量:
http://canlynet.blog.163.com/blog/static/2550136520091025069172/
自旋鎖:一種當獲取不到鎖時,采用循環 “ 測試並設置 ” 的方式等待獲取鎖的一種互斥鎖。
獲取方法:采用 test-and-set 原子操作測試並設置某個內存變量。 —— 實現互斥的方法。
不被打斷:通過禁止搶占實現。
使用場合:可在進程上下文和中斷上下文使用。
特點:
1 、這種鎖在獲取不到時系統開銷大,所以獲取鎖和釋放鎖的區間代碼執行時間應該盡可能短。
2 、在單 CPU 內核可搶占系統中,自旋鎖持有期間內核搶占被禁止。但在單 CPU 內核不支持搶占的系統,自旋鎖退化為空操作。 —— 也就是說,如果持有鎖的區間代碼執行時間過長,會出現其它操作的不響應(假死機)現象。
3 、因為搶占被禁止,自旋鎖可保證臨界區不受 “ 別的 CPU 和本 CPU 內 ” 的搶占進程進程打擾。(但是可響應中斷,如果連中斷都不響應,需要特定的加鎖函數)
4 、可能受到中斷和底半部( BH) 的影響,為此,與開、關中斷配合,為此有:
spin_lock_irq(),spin_unlock_irq()
spin_lock_irqsave(),spin_unlock_irqstore()
spin_lock_bh(),spin_unlock_bh()
具體含義請參考教材或網絡搜索。
自旋鎖的擴展: 1 、讀寫自旋鎖; 2 、順序鎖; 3 、讀 - 拷貝 - 更新( RCU)
******************************************************************
信號量:一種當獲取不到鎖時,采用睡眠以等待喚醒的方式的一種同步機制。
不被打斷:通過何種方式實現沒找到參考,但可推斷為同自旋鎖。
獲取方法:同自旋鎖。
適用場合:由於會導致睡眠,只能在進程上下文中使用。但是 int down_trylock(struct semphore *sem) 可以。
特點:
獲取不到鎖時,進入睡眠狀態,系統開銷為上下文切換的時間 Tsw 。
自旋鎖和信號量的對比:
1 、當鎖不能獲取時,信號量開銷為 Tsw (上下文切換時間),自旋鎖開銷為等待獲取時間 Tcs ,這兩個時間對比權衡使用哪種機制。
2 、信號量可用於保護包含可能引起阻塞的代碼 ( 即保護的代碼中有可引起睡眠的函數如 copy_to_user(),copy_from_user()) ,自旋鎖不能。自旋鎖如果也使用這樣的代碼,當睡眠時另一個程進也要獲取這把鎖時,會 進入循環,而睡眠時間一般相對較長,系統資源開銷大的時間過長時,資源耗盡的情況會發生,直到這個睡眠的進程被喚醒,代碼執行完畢,資源才得以釋放。至於 自旋鎖區間睡眠引起死鎖的情況我實在想不出來。但教材中都這麽說。
3 、中斷上下文中只能使用自旋鎖,不可使用信號量。因為中斷上下文中是不能被調度的,但睡眠後會發生上下文切換,需要調度,在中斷上下文中睡眠只能永久睡眠 —— 死機!
如果一個函數需要一個鎖,並且接著調用另外一個函數也試圖請求這個鎖,那麽會導致代碼死鎖。
獲得多個鎖可能是危險的,然而,如果你有 2 個鎖,稱為 lock1 和 lock2 ,代碼需要同時獲取,你有一個潛在的死鎖。這個問題的解決方法常常是簡單的:當多個鎖必須獲得時,它們應當一直以同樣的順序獲得,只要遵照這個慣例,像上面描述的簡單的死鎖能夠避免。
如果你懷疑鎖競爭在損壞性能,你可能發現 lockmeter 工具有用,這個補丁裝備內核來測量在鎖等待花費的時間,通過看這個報告你能夠很快知道是否鎖競爭真的是問題。
對於 2.6.10 ,有一個通用的環形緩存實現在內核中可用,如何使用它的信息看 <linux/kfifo.h> 。
加鎖方式的幾種選擇:
1、 有時候一個共享資源是一個簡單的整數值,對於這樣的情況,內核提供了一個原子整數稱為 atomic_t ,定義在 <asm/atomic.h> 中, atomic_t 的操作非常快,因為他們在認識可能時編譯成一條機器指令。有一系列函數支持 atomic_t 操作。
Atomic_t 數據項必須通過這些函數存取,如果你傳遞一個原子項給一個期望一個整數參數的函數,你會得到一個編譯錯誤。
2、 內核提供了一套函數來原子的修改或測試單個位,因為整個操作在單步內發生,沒有中斷能幹擾。參見 <asm/bitops.h> ,他們保證是原子的,及時在 SMP 上。
可以通過為操作來模擬鎖,但是此中情況最好使用自旋鎖,自旋鎖很好的調試過,而且很好的處理了中斷和內核搶占。
一段模擬鎖的為操作代碼:
/*try to set lock*/
While(test_and_set_bit(nr,addr) ! = 0)
Wait_for_a_while();
/*do your work*/
/*release lock,and check… */
If(test_and_clear_bit(nr,addr) == 0)
Something_went_wrong(); /*already release:error*/
為操作例子:
Static int main_init(void)
{
Unsigned long v = 0x00;
Unsigned log ret = test_and_rest_bit(0,&v);
Printk(KERN_INFO “%X,%X/n”,ret,v);
Return 0;
}
返回結果為 0,1 。
3、 信號量
4、 自旋鎖
5、 Seqlock 機制,參見 <linux/seqlock.h>
6、 RCU( 讀取 - 拷貝 - 更新 ) ,參見 <linux/rcupdate.h> 。在真實世界中使用 RCU 的例子如下: <1> 、網絡路由表; <2> 、無線 IP 驅動。
Rcu_read_lock() 速度很快且禁止內核搶占,因此是原子的。
7、 Cpmpletion 完成機制。
原子操作:一個原子上下文只是一個狀態,這裏多個步驟必須在沒有任何類型的並發存取的情況下進行
linux 自旋鎖和信號量【轉】