linux kernel的spin_lock的详细介绍(以arm64为例)
1、spin_lock的調用流程:
static __always_inline void spin_lock(spinlock_t *lock) {raw_spin_lock(&lock->rlock); } #define raw_spin_lock(lock) _raw_spin_lock(lock) void __lockfunc _raw_spin_lock(raw_spinlock_t *lock) __acquires(lock); void __lockfunc _raw_spin_lock(raw_spinlock_t *lock) {__raw_spin_lock(lock); } static inline void __raw_spin_lock(raw_spinlock_t *lock) {preempt_disable(); //&&&&&& 這里是禁止搶占&&&&spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); } static inline int do_raw_spin_trylock(raw_spinlock_t *lock) {return arch_spin_trylock(&(lock)->raw_lock); }static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock) {arch_spin_unlock(&lock->raw_lock); //調用到arch體系相關代碼__release(lock); } static inline void arch_spin_lock(arch_spinlock_t *lock) {unsigned int tmp;arch_spinlock_t lockval, newval;asm volatile(/* Atomically increment the next ticket. */ARM64_LSE_ATOMIC_INSN(/* LL/SC */ " prfm pstl1strm, %3\n" //cache相關指令 "1: ldaxr %w0, %3\n" " add %w1, %w0, %w5\n" " stxr %w2, %w1, %3\n" " cbnz %w2, 1b\n",/* LSE atomics */ " mov %w2, %w5\n" " ldadda %w2, %w0, %3\n" " nop\n" " nop\n" " nop\n")/* Did we get the lock? */ " eor %w1, %w0, %w0, ror #16\n" " cbz %w1, 3f\n"/** No: spin on the owner. Send a local event to avoid missing an* unlock before the exclusive load.*/ " sevl\n" "2: wfe\n" //&&&& 讓core進入low-power state " ldaxrh %w2, %4\n" " eor %w1, %w2, %w0, lsr #16\n" " cbnz %w1, 2b\n" //&&&& 這一段是一個循環,也就是自旋等待/* We got the lock. Critical section starts here. */ "3:": "=&r" (lockval), "=&r" (newval), "=&r" (tmp), "+Q" (*lock): "Q" (lock->owner), "I" (1 << TICKET_SHIFT): "memory"); }2、使用場景:
spin_lock:當線程A拿了鎖,線程B再去拿鎖,就會失敗(拿不到),線程B就會自旋在哪里,等待鎖釋放.
mutex:當線程A拿了鎖,線程B再去拿鎖,就會失敗(拿不到),會陷入sleep, 等到線程A釋放了鎖,線程B才會wakeup,獲得該鎖;
如果鎖住的“事務”很簡單,占用很少的時間,就應該使用spinlock,這個時候spinlock的代價比mutex會小很多。”事務”很快執行完畢,自旋的消耗遠遠小于陷入sleep和wake的消耗
3、問與答:
(1)、spin_lock中,會什么要禁止搶占(preempt_disable)?
(以單核為例)
P1 holds the lock and after a time is scheduled out.
Now P2 starts executing and let’s say requests the same spinlock. Since P1 has not released the spinlock, P2 must spin and do nothing. So the execution time of P2 is wasted. It makes more sense to let P1 release the lock and then allow it to be preempted.
Also since there is no other processor, simply by disallowing preemption we know that there will never be another process that runs in parallel on another processor and accesses the same critical section.
也就是說, process1正在持有該鎖,此時發生了schedule后process2又去試圖拿該鎖,process2就會自旋在那里,時間就浪費了. 合理的做法應該是,讓Process1(執行完臨界區)釋放該鎖
(2)、在這樣的場景下,使用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上的中斷聯合使用
spin lock和禁止本CPU上的中斷聯合使用:
(3)、假設只有一個cpu,如果把spin_lock中的preempt_disable注釋掉, 即允許搶占。 那么使用spin_lock會產生死鎖嗎?
不會。threadA在執行時臨界區時,被schedule出去了,thread B試圖獲取該鎖,threadB會自旋那里(比較浪費cpu資源),等到再次被調度到threadA并且釋放了該鎖后,threadB才可以繼續往下跑。
(4)、spinlock的臨界區為什么不允許sleep(使用schedule類函數)?
Thread A調用spin_lock進去臨界區,此時該cpu已經禁止搶占了(preempt_disable),如果此時調用sleep主動schedule出去后,該cpu就永遠回不來了因為禁止搶占了。這樣的話,如果threadB再試圖獲取該鎖時,就會發生死鎖。
4、 wfe/sev的使用
(1)、WFI,執行WFI指令后,ARM core會立即進入low-power standby state,直到有WFI Wakeup events發生。
(2)、WFE,執行WFE指令后,根據Event Register(一個單bit的寄存器,每個PE一個)的狀態,有兩種情況:
a. 如果Event Register為1,該指令會把它清零,然后執行完成(不會standby);
b. 如果Event Register為0,和WFI類似,進入low-power standby state,直到有WFE Wakeup events發生。
(3)、SEV指令,就是一個用來改變Event Register的指令,有兩個:SEV會修改所有PE上的寄存器;SEVL,只修改本PE的寄存器值
WFE應用于arch_spin_lock場景:
a)資源空閑 b)Core1訪問資源,acquire lock,獲得資源 c)Core2訪問資源,此時資源不空閑,執行WFE指令,讓core進入low-power state d)Core1釋放資源,release lock,釋放資源,同時會喚醒Core2(unlock中的staddlh會喚醒WFE) e)Core2獲得資源我們也剖析下現有的代碼:
arch_spin_lock進來后,先執行sevl (Event Register變成1),再執行wfe Event Register變成0),執行cbnz如果沒有拿到鎖,再跳轉到2處,又執行wfe了,此時cpu進入low-power standby state
等到其它的cpu進程釋放了該鎖(發送sev信號后),當前cpu退出low-power standby state繼續往下執行,執行cbnz獲取到該鎖,繼續向下執行.
問題 : 那么“其它的cpu進程釋放了該鎖(發送sev信號后)”,其它cpu在哪里發送的sev信號呢???
回答 : 等待自旋鎖的時候,使用指令ldaxrh(帶有獲取語義的獨占加載,h表示halfword,即2字節)讀取服務號,獨占加載操作會設置處理器的獨占監視器,記錄鎖的物理地址。
釋放鎖的時候,使用stlrh指令修改鎖的值,stlrh指令會清除所有監視鎖的物理地址的處理器的獨占監視器,清除獨占監視器的時候會生成一個喚醒事件
(可以查看armv8文檔中的 “WFE wake-up events in AArch64 state” 章節,看看都是哪些事件或命令可以喚醒WFE,其中里面提到:An event caused by the clearing of the global monitor for the PE)
執行staddlh會產生一個WFE的喚醒時間
static inline void arch_spin_unlock(arch_spinlock_t *lock) {unsigned long tmp;asm volatile(ARM64_LSE_ATOMIC_INSN(/* LL/SC */" ldrh %w1, %0\n"" add %w1, %w1, #1\n"" stlrh %w1, %0",/* LSE atomics */" mov %w1, #1\n"" nop\n"" staddlh %w1, %0"): "=Q" (lock->owner), "=&r" (tmp):: "memory"); }總結
以上是生活随笔為你收集整理的linux kernel的spin_lock的详细介绍(以arm64为例)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [crypto]-30-The Armv
- 下一篇: linux kernel进程切换(寄存器