天天看點

轉:自旋鎖(spinlock)

自旋鎖與互斥鎖有點類似,隻是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被别的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是是以而得名。

  由于自旋鎖使用者一般保持鎖時間非常短,是以選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。

  信号量和讀寫信号量适合于保持時間較長的情況,它們會導緻調用者睡眠,是以隻能在程序上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖适合于保持時間非常短的情況,它可以在任何上下文使用。

  如果被保護的共享資源隻在程序上下文通路,使用信号量保護該共享資源非常合适,如果對共巷資源的通路時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文通路(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。

  自旋鎖保持期間是搶占失效的,而信号量和讀寫信号量保持期間是可以被搶占的。自旋鎖隻有在核心可搶占或SMP的情況下才真正需要,在單CPU且不可搶占的核心下,自旋鎖的所有操作都是空操作。

  跟互斥鎖一樣,一個執行單元要想通路被自旋鎖保護的共享資源,必須先得到鎖,在通路完共享資源後,必須釋放鎖。如果在擷取自旋鎖時,沒有任何執行單元保持該鎖,那麼将立即得到鎖;如果在擷取自旋鎖時鎖已經有保持者,那麼擷取鎖操作将自旋在那裡,直到該自旋鎖的保持者釋放了鎖。

  無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。

  自旋鎖的API有:

spin_lock_init(x)

  該宏用于初始化自旋鎖x。自旋鎖在真正使用前必須先初始化。該宏用于動态初始化。

DEFINE_SPINLOCK(x)

  該宏聲明一個自旋鎖x并初始化它。該宏在2.6.11中第一次被定義,在先前的核心中并沒有該宏。

SPIN_LOCK_UNLOCKED

  該宏用于靜态初始化一個自旋鎖。

DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKED spin_is_locked(x)

  該宏用于判斷自旋鎖x是否已經被某執行單元保持(即被鎖),如果是,傳回真,否則傳回假。

spin_unlock_wait(x)

  該宏用于等待自旋鎖x變得沒有被任何執行單元保持,如果沒有任何執行單元保持該自旋鎖,該宏立即傳回,否則将循環在那裡,直到該自旋鎖被保持者釋放。

spin_trylock(lock)

  該宏盡力獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖并傳回真,否則不能立即獲得鎖,立即傳回假。它不會自旋等待lock被釋放。

spin_lock(lock)

  該宏用于獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上傳回,否則,它将自旋在那裡,直到該自旋鎖的保持者釋放,這時,它獲得鎖并傳回。總之,隻有它獲得鎖才傳回。

spin_lock_irqsave(lock, flags)

  該宏獲得自旋鎖的同時把标志寄存器的值儲存到變量flags中并失效本地中斷。

spin_lock_irq(lock)

  該宏類似于spin_lock_irqsave,隻是該宏不儲存标志寄存器的值。

spin_lock_bh(lock)

  該宏在得到自旋鎖的同時失效本地軟中斷。

spin_unlock(lock)

  該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用。如果spin_trylock傳回假,表明沒有獲得自旋鎖,是以不必使用spin_unlock釋放。

spin_unlock_irqrestore(lock, flags)

  該宏釋放自旋鎖lock的同時,也恢複标志寄存器的值為變量flags儲存的值。它與spin_lock_irqsave配對使用。

spin_unlock_irq(lock)

  該宏釋放自旋鎖lock的同時,也使能本地中斷。它與spin_lock_irq配對應用。

spin_unlock_bh(lock)

  該宏釋放自旋鎖lock的同時,也使能本地的軟中斷。它與spin_lock_bh配對使用。

spin_trylock_irqsave(lock, flags)

  該宏如果獲得自旋鎖lock,它也将儲存标志寄存器的值到變量flags中,并且失效本地中斷,如果沒有獲得鎖,它什麼也不做。

  是以如果能夠立即獲得鎖,它等同于spin_lock_irqsave,如果不能獲得鎖,它等同于spin_trylock。如果該宏獲得自旋鎖lock,那需要使用spin_unlock_irqrestore來釋放。

spin_trylock_irq(lock)

  該宏類似于spin_trylock_irqsave,隻是該宏不儲存标志寄存器。如果該宏獲得自旋鎖lock,需要使用spin_unlock_irq來釋放。

spin_trylock_bh(lock)

  該宏如果獲得了自旋鎖,它也将失效本地軟中斷。如果得不到鎖,它什麼也不做。是以,如果得到了鎖,它等同于spin_lock_bh,如果得不到鎖,它等同于spin_trylock。如果該宏得到了自旋鎖,需要使用spin_unlock_bh來釋放。

spin_can_lock(lock)

  該宏用于判斷自旋鎖lock是否能夠被鎖,它實際是spin_is_locked取反。如果lock沒有被鎖,它傳回真,否則,傳回假。該宏在2.6.11中第一次被定義,在先前的核心中并沒有該宏。

  獲得自旋鎖和釋放自旋鎖有好幾個版本,是以讓讀者知道在什麼樣的情況下使用什麼版本的獲得和釋放鎖的宏是非常必要的。

  如果被保護的共享資源隻在程序上下文通路和軟中斷上下文通路,那麼當在程序上下文通路共享資源時,可能被軟中斷打斷,進而可能進入軟中斷上下文來對被保護的共享資源通路,是以對于這種情況,對共享資源的通路必須使用spin_lock_bh和spin_unlock_bh來保護。

  當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隐式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當的,它比其他兩個快。

  如果被保護的共享資源隻在程序上下文和tasklet或timer上下文通路,那麼應該使用與上面情況相同的獲得和釋放鎖的宏,因為tasklet和timer是用軟中斷實作的。

  如果被保護的共享資源隻在一個tasklet或timer上下文通路,那麼不需要任何自旋鎖保護,因為同一個tasklet或timer隻能在一個CPU上運作,即使是在SMP環境下也是如此。實際上tasklet在調用tasklet_schedule标記其需要被排程時已經把該tasklet綁定到目前CPU,是以同一個tasklet決不可能同時在其他CPU上運作。

  timer也是在其被使用add_timer添加到timer隊列中時已經被幫定到目前CPU,是以同一個timer絕不可能運作在其他CPU上。當然同一個tasklet有兩個執行個體同時運作在同一個CPU就更不可能了。

  如果被保護的共享資源隻在兩個或多個tasklet或timer上下文通路,那麼對共享資源的通路僅需要用spin_lock和spin_unlock來保護,不必使用_bh版本,因為當tasklet或timer運作時,不可能有其他tasklet或timer在目前CPU上運作。

 如果被保護的共享資源隻在一個軟中斷(tasklet和timer除外)上下文通路,那麼這個共享資源需要用spin_lock和spin_unlock來保護,因為同樣的軟中斷可以同時在不同的CPU上運作。

  如果被保護的共享資源在兩個或多個軟中斷上下文通路,那麼這個共享資源當然更需要用spin_lock和spin_unlock來保護,不同的軟中斷能夠同時在不同的CPU上運作。

  如果被保護的共享資源在軟中斷(包括tasklet和timer)或程序上下文和硬中斷上下文通路,那麼在軟中斷或程序上下文通路期間,可能被硬中斷打斷,進而進入硬中斷上下文對共享資源進行通路,是以,在程序或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護對共享資源的通路。

  而在中斷處理句柄中使用什麼版本,需依情況而定,如果隻有一個中斷處理句柄通路該共享資源,那麼在中斷處理句柄中僅需要spin_lock和spin_unlock來保護對共享資源的通路就可以了。

  因為在執行中斷處理句柄期間,不可能被同一CPU上的軟中斷或程序打斷。但是如果有不同的中斷處理句柄通路該共享資源,那麼需要在中斷處理句柄中使用spin_lock_irq和spin_unlock_irq來保護對共享資源的通路。

  在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應該使用哪一個也需要依情況而定,如果可以确信在對共享資源通路前中斷是使能的,那麼使用spin_lock_irq更好一些。

下一篇: CAS+自旋鎖

繼續閱讀