spinlock在上一篇文章有提到:http://www.cnblogs.com/charlesblc/p/6254437.html 通過鎖數據總線來實現。
而看了這篇文章說明:mutex內部也用到了spinlock http://blog.chinaunix.net/uid-21918657-id-2683763.html
獲取互斥鎖。
實際上是先給count做自減操作,然后使用本身的自旋鎖進入臨界區操作。首先取得count的值,在將count置為-1,判斷如果原來count的置為1,也即互斥鎖可以獲得,則直接獲取,跳出。否則進入循環反復測試互斥鎖的狀態。在循環中,也是先取得互斥鎖原來的狀態,在將其之為-1,判斷如果可以獲取(等於1),則退出循環,否則設置當前進程的狀態為不可中斷狀態,解鎖自身的自旋鎖,進入睡眠狀態,待被在調度喚醒時,再獲得自身的自旋鎖,進入新一次的查詢其自身狀態(該互斥鎖的狀態)的循環。
那么,SpinLock是不是用的CAS操作呢。看起來是的。
http://blog.csdn.net/goondrift/article/details/19044361
為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換,由於只有一條指令,保證了原子性,即使是多處理器平台,訪問內存的總線周期也有先后,一個處理器上的交換指令執行時另一個處理器的交換指令只能等待總線周期。
unlock中的釋放鎖操作同樣只用一條指令實現,以保證它的原子性。
也許還有讀者好奇,“掛起等待”和“喚醒等待線程”的操作如何實現?每個Mutex有一個等待隊列,一個線程要在Mutex上掛起等待,首先把自己加入等待隊列中,然后置線程狀態為睡眠,接着(系統)調用調度器函數切換到別的線程。一個線程要喚醒等待隊列中的其它線程,只需從等待隊列中取出一項,把它的狀態從睡眠改為就緒,加入就緒隊列,那么下次調度器函數執行時就有可能切換到被喚醒的線程。
一般情況下,如果同一個線程先后兩次調用lock,在第二次調用時,由於鎖已經被占用,該線程會掛起等待別的線程釋放鎖,然而鎖正是被自己占用着的,該線程又被掛起而沒有機會釋放鎖,因此就永遠處於掛起等待狀態了,這叫做死鎖(Deadlock)。另一種典型的死鎖情形是這樣:線程A獲得了鎖1,線程B獲得了鎖2,這時線程A調用lock試圖獲得鎖2,結果是需要掛起自己等待線程B釋放鎖2,而這時線程B也調用lock試圖獲得鎖1,結果是需要掛起自己等待線程A釋放鎖1,於是線程A和B都永遠處於掛起狀態了。不難想象,如果涉及到更多的線程和更多的鎖,有沒有可能死鎖的問題將會變得復雜和難以判斷。
避免死鎖的方法:
1. 不要遞歸調用鎖(有些鎖沒有允許遞歸調用的選項)
2. 不同線程加鎖的順序要一致,都要A,B,C
3. 盡量使用pthread_mutex_trylock調用代替pthread_mutex_lock調用
mutex和spinlock各自的問題:
mutex的問題是,它一旦上鎖失敗就會進入sleep,讓其他thread運行,這 就需要內核將thread切換到sleep狀態,如果mutex又在很短的時間內被釋放掉了,那么又需要將此thread再次喚醒,這需要消耗許多CPU 指令和時間,這種消耗還不如讓thread去輪訊。也就是說,其他thread解鎖時間很短的話會導致CPU的資源浪費。
spinlock的問題是,和上面正好相反,如果其他thread解鎖的時間很長的話,這種spinlock進行輪訊的方式將會浪費很多CPU資源。
解決方案:
對於single-core/single-CPU,spinlock將一直浪費 CPU資源,如果采用mutex,反而可以立刻讓其他的thread運行,可能去釋放mutex lock。
對於multi-core/mutil-CPU,會存在很多短時間被占用的lock,如果總是去讓thread sleep,緊接着去wake up,這樣會浪費很多CPU資源,從而降低了系統性能,所以應該盡量使用spinlock。
大多數現代操作系統已經使用了混合 mutex(hybrid mutex)和混合spinlock(hybrid spinlock)。說白了就是將兩者的特點相結合。
hydrid mutex:在一個multi-core系統上,hybrid mutex首先像一個spinlock一樣,當thread加鎖失敗的時候不會立即被設置成sleep,但是,當過了一定的時間(或則其他的策略)還沒有 獲得lock,就會被設置成sleep,之后再被wake up。而在一個single-core系統上,hybrid mutex就不會表現出spinlock的特性,而是如果加鎖失敗就直接被設置成sleep。
hybrid spinlock:和hybrid mutex相似,只不過,thread加鎖失敗后在spinlock一段很短的時間后,會被stop而不是被設置成sleep,stop是正常的進程調 度,應該會比先讓thread sleep然后再wake up的開銷小一些。
總結:
寫程序的時候,如果對mutex和spinlock有任何疑惑,請選擇使用mutex。
http://blog.chinaunix.net/uid-24227137-id-3563249.html
和spinlock 相關的文件主要有兩個,一個是include/linux/spinlock.h,主要是提供關於和硬件無關的spinlock的幾個對外主函數,一個是 include/asm-XXX/spinlock.h,用來提供和硬件相關的功能函數。另外,在2.6的內核中,又多了一個文件, include/linux/preempt.h,為新增加的搶占式多任務功能提供一些服務。
spinlock 函數的使用前提:首先,spinklock函數只能使用在內核中,或者說只能使用在內核狀態下,在2.6以前的內核是不可搶占的,也就是說,當運行於內核狀態下時,是不容許切換到其他進程的。而在2.6以后的內核中,編譯內核的時候多了一個選項,可以配置內核是否可以被搶占,這也就是為什么在2.6的內核中多了一個preempt.h的原因。
需要澄清的是,互斥手段的選擇,不是根據臨界區的大小,而是根據臨界區的性質,以及
有哪些部分的代碼,即哪些內核執行路徑來爭奪。
從嚴格意義上說,semaphore和spinlock_XXX屬於不同層次的互斥手段,前者的
實現有賴於后者,這有點象HTTP和TCP的關系,都是協議,但層次是不同的。
先說semaphore,它是進程級的,用於多個進程之間對資源的互斥,雖然也是在
內核中,但是該內核執行路徑是以進程的身份,代表進程來爭奪資源的。如果
競爭不上,會有context switch,進程可以去sleep,但CPU不會停,會接着運行
其他的執行路徑。從概念上說,這和單CPU或多CPU沒有直接的關系,只是在
semaphore本身的實現上,為了保證semaphore結構存取的原子性,在多CPU中需要
spinlock來互斥。
在內核中,更多的是要保持內核各個執行路徑之間的數據訪問互斥,這是最基本的
互斥問題,即保持數據修改的原子性。semaphore的實現,也要依賴這個。在單CPU
中,主要是中斷和bottom_half的問題,因此,開關中斷就可以了。在多CPU中,
又加上了其他CPU的干擾,因此需要spinlock來幫助。這兩個部分結合起來,
就形成了spinlock_XXX。它的特點是,一旦CPU進入了spinlock_XXX,它就不會
干別的,而是一直空轉,直到鎖定成功為止。因此,這就決定了被
spinlock_XXX鎖住的臨界區不能停,更不能context switch,要存取完數據后趕快
出來,以便其他的在空轉的執行路徑能夠獲得spinlock。這也是spinlock的原則
所在。如果當前執行路徑一定要進行context switch,那就要在schedule()之前
釋放spinlock,否則,容易死鎖。因為在中斷和bh中,沒有context,無法進行
context switch,只能空轉等待spinlock,你context switch走了,誰知道猴年
馬月才能回來。
那么,在什么情況下具體用哪個呢?這要看是在什么內核執行路徑中,以及要與哪些內核
執行路徑相互斥。我們知道,內核中的執行路徑主要有:
1 用戶進程的內核態,此時有進程context,主要是代表進程在執行系統調用 等。 2 中斷或者異常或者自陷等,從概念上說,此時沒有進程context,不能進行 context switch。 3 bottom_half,從概念上說,此時也沒有進程context。 4 同時,相同的執行路徑還可能在其他的CPU上運行。
如果只要和其他CPU
互斥,就要用spin_lock/spin_unlock,如果要和irq及其他CPU互斥,就要用
spin_lock_irq/spin_unlock_irq。。。
自旋鎖的使用
http://www.linuxidc.com/Linux/2012-02/54313.htm
int OpenCloseStatus; spinlock_t spinlock; int xxxx_init(void) { ............ spin_lock_init(&spinlock); ............ } int xxxx_open(struct inode *inode, struct file *filp) { ............ spin_lock(&spinlock); if(OpenCloseStatus) { spin_unlock(&spinlock); return -EBUSY; } OpenCloseStatus ++; spin_unlock(&spinlock); ........... } int xxxx_release(struct inode *inode, struct file *filp) { ............ spin_lock(&spinlock); OpenCloseStatus --; spin_unlock(&spinlock); ........... }
5:自旋鎖使用注意事項
1):自旋鎖一種忙等待,當條件不滿足時,會一直不斷的循環判斷條件是否滿足,如果滿足就解鎖,運行之后的代碼。因此會對linux的系統的性能有些影響。所以在實際編程時,需要注意自旋鎖不應該長時間的持有。它適合於短時間的的輕量級的加鎖機制。
2):自旋鎖不能遞歸使用,這是因為自旋鎖,在設計之初就被設計成在不同進程或者函數之間同步。所以不能用於遞歸使用。
還不錯,講的比較具體
http://www.wowotech.net/kernel_synchronization/spinlock.html
對於spin lock,其保護的資源可能來自多個CPU CORE上的進程上下文和中斷上下文的中的訪問,其中,進程上下文包括:用戶進程通過系統調用訪問,內核線程直接訪問,來自workqueue中work function的訪問(本質上也是內核線程)。中斷上下文包括:HW interrupt context(中斷handler)、軟中斷上下文(soft irq,當然由於各種原因,該softirq被推遲到softirqd的內核線程中執行的時候就不屬於這個場景了,屬於進程上下文那個分類了)、timer的callback函數(本質上也是softirq)、tasklet(本質上也是softirq)。
先看最簡單的單CPU上的進程上下文的訪問。如果一個全局的資源被多個進程上下文訪問,這時候,內核如何交錯執行呢?對於那些沒有打開preemptive選項(Linux2.6石達開的,也就是可搶占的)的內核,所有的系統調用都是串行化執行的,因此不存在資源爭搶的問題。如果內核線程也訪問這個全局資源呢?本質上內核線程也是進程,類似普通進程,只不過普通進程時而在用戶態運行、時而通過系統調用陷入內核執行,而內核線程永遠都是在內核態運行,但是,結果是一樣的,對於non-preemptive的linux kernel,只要在內核態,就不會發生進程調度,因此,這種場景下,共享數據根本不需要保護(沒有並發,談何保護呢)。如果時間停留在這里該多么好,單純而美好,在繼續前進之前,讓我們先享受這一刻。
(注:所以說SpinLock主要保護的是系統調用,而mutex更多是應用級別)
當打開premptive選項后,事情變得復雜了,我們考慮下面的場景:
(1)進程A在某個系統調用過程中訪問了共享資源R
(2)進程B在某個系統調用過程中也訪問了共享資源R
OK,我們加上spin lock看看如何:A在進入臨界區之前獲取了spin lock,同樣的,在A訪問共享資源R的過程中發生了中斷,中斷喚醒了沉睡中的,優先級更高的B,B在訪問臨界區之前仍然會試圖獲取spin lock,這時候由於A進程持有spin lock而導致B進程進入了永久的spin……怎么破?
linux的kernel很簡單,在A進程獲取spin lock的時候,禁止本CPU上的搶占(上面的永久spin的場合僅僅在本CPU的進程搶占本CPU的當前進程這樣的場景中發生)。(不同的CPU的話,反正總會執行回來的。除非另一個鎖鎖住了,那就是多個鎖的死鎖了,另外的問題了)。
如果A和B運行在不同的CPU上,那么情況會簡單一些:A進程雖然持有spin lock而導致B進程進入spin狀態,不過由於運行在不同的CPU上,A進程會持續執行並會很快釋放spin lock,解除B進程的spin狀態。
多CPU core的場景和單核CPU打開preemptive選項的效果是一樣的,這里不再贅述。
我們繼續向前分析,現在要加入中斷上下文這個因素。
訪問共享資源的thread包括:
(1)運行在CPU0上的進程A在某個系統調用過程中訪問了共享資源R
(2)運行在CPU1上的進程B在某個系統調用過程中也訪問了共享資源R
(3)外設P的中斷handler中也會訪問共享資源R
在這樣的場景下,使用spin lock可以保護訪問共享資源R的臨界區嗎?
我們假設CPU0上的進程A持有spin lock進入臨界區,這時候,外設P發生了中斷事件,並且調度到了CPU1上執行,看起來沒有什么問題,執行在CPU1上的handler會稍微等待一會CPU0上的進程A,等它立刻臨界區就會釋放spin lock的,但是,如果外設P的中斷事件被調度到了CPU0上執行會怎么樣?CPU0上的進程A在持有spin lock的狀態下被中斷上下文搶占,而搶占它的CPU0上的handler在進入臨界區之前仍然會試圖獲取spin lock,悲劇發生了,CPU0上的P外設的中斷handler永遠的進入spin狀態,這時候,CPU1上的進程B也不可避免在試圖持有spin lock的時候失敗而導致進入spin狀態。
為了解決這樣的問題,linux kernel采用了這樣的辦法:如果涉及到中斷上下文的訪問,spin lock需要和禁止本CPU上的中斷聯合使用。也就是不准handler里面調用SpinLock吧。
下面這句不太懂:
linux kernel中提供了豐富的bottom half的機制,雖然同屬中斷上下文,不過還是稍有不同。我們可以把上面的場景簡單修改一下:外設P不是中斷handler中訪問共享資源R,而是在的bottom half中訪問。使用spin lock+禁止本地中斷當然是可以達到保護共享資源的效果,但是使用牛刀來殺雞似乎有點小題大做,這時候disable bottom half就OK了。
最后,我們討論一下中斷上下文之間的競爭。同一種中斷handler之間在uni core和multi core上都不會並行執行,這是linux kernel的特性。如果不同中斷handler需要使用spin lock保護共享資源,對於新的內核(不區分fast handler和slow handler),所有handler都是關閉中斷的,因此使用spin lock不需要關閉中斷的配合。
bottom half又分成softirq和tasklet,同一種softirq會在不同的CPU上並發執行,因此如果某個驅動中的sofirq的handler中會訪問某個全局變量,對該全局變量是需要使用spin lock保護的,不用配合disable CPU中斷或者bottom half。tasklet更簡單,因為同一種tasklet不會多個CPU上並發,具體我就不分析了,大家自行思考吧。
Linux內核同步機制之(五):Read/Write spin lock
http://www.wowotech.net/kernel_synchronization/rw-spinlock.html
我們首先看看加鎖的邏輯:
(1)假設臨界區內沒有任何的thread,這時候任何read thread或者write thread可以進入,但是只能是其一。
(2)假設臨界區內有一個read thread,這時候新來的read thread可以任意進入,但是write thread不可以進入
(3)假設臨界區內有一個write thread,這時候任何的read thread或者write thread都不可以進入
(4)假設臨界區內有一個或者多個read thread,write thread當然不可以進入臨界區,但是該write thread也無法阻止后續read thread的進入,他要一直等到臨界區一個read thread也沒有的時候,才可以進入,多么可憐的write thread。
unlock的邏輯如下:
(1)在write thread離開臨界區的時候,由於write thread是排他的,因此臨界區有且只有一個write thread,這時候,如果write thread執行unlock操作,釋放掉鎖,那些處於spin的各個thread(read或者write)可以競爭上崗。
(2)在read thread離開臨界區的時候,需要根據情況來決定是否讓其他處於spin的write thread們參與競爭。如果臨界區仍然有read thread,那么write thread還是需要spin(注意:這時候read thread可以進入臨界區,聽起來也是不公平的)直到所有的read thread釋放鎖(離開臨界區),這時候write thread們可以參與到臨界區的競爭中,如果獲取到鎖,那么該write thread可以進入。