linux kernel的spin_lock的详细介绍(以arm64为例)
1、spin_lock的調(diào)用流程:
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); //調(diào)用到arch體系相關(guān)代碼__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相關(guān)指令 "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進(jìn)入low-power state " ldaxrh %w2, %4\n" " eor %w1, %w2, %w0, lsr #16\n" " cbnz %w1, 2b\n" //&&&& 這一段是一個(gè)循環(huá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:當(dāng)線程A拿了鎖,線程B再去拿鎖,就會失敗(拿不到),線程B就會自旋在哪里,等待鎖釋放.
mutex:當(dāng)線程A拿了鎖,線程B再去拿鎖,就會失敗(拿不到),會陷入sleep, 等到線程A釋放了鎖,線程B才會wakeup,獲得該鎖;
如果鎖住的“事務(wù)”很簡單,占用很少的時(shí)間,就應(yīng)該使用spinlock,這個(gè)時(shí)候spinlock的代價(jià)比mutex會小很多。”事務(wù)”很快執(zhí)行完畢,自旋的消耗遠(yuǎn)遠(yuǎn)小于陷入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正在持有該鎖,此時(shí)發(fā)生了schedule后process2又去試圖拿該鎖,process2就會自旋在那里,時(shí)間就浪費(fèi)了. 合理的做法應(yīng)該是,讓Process1(執(zhí)行完臨界區(qū))釋放該鎖
(2)、在這樣的場景下,使用spin lock可以保護(hù)訪問共享資源R的臨界區(qū)嗎?
我們假設(shè)CPU0上的進(jìn)程A持有spin lock進(jìn)入臨界區(qū),這時(shí)候,外設(shè)P發(fā)生了中斷事件,并且調(diào)度到了CPU1上執(zhí)行,看起來沒有什么問題,執(zhí)行在CPU1上的handler會稍微等待一會CPU0上的進(jìn)程A,等它立刻臨界區(qū)就會釋放spin lock的,但是,如果外設(shè)P的中斷事件被調(diào)度到了CPU0上執(zhí)行會怎么樣?CPU0上的進(jìn)程A在持有spin lock的狀態(tài)下被中斷上下文搶占,而搶占它的CPU0上的handler在進(jìn)入臨界區(qū)之前仍然會試圖獲取spin lock,悲劇發(fā)生了,CPU0上的P外設(shè)的中斷handler永遠(yuǎn)的進(jìn)入spin狀態(tài),這時(shí)候,CPU1上的進(jìn)程B也不可避免在試圖持有spin lock的時(shí)候失敗而導(dǎo)致進(jìn)入spin狀態(tài)。為了解決這樣的問題,linux kernel采用了這樣的辦法:如果涉及到中斷上下文的訪問,spin lock需要和禁止本CPU上的中斷聯(lián)合使用
spin lock和禁止本CPU上的中斷聯(lián)合使用:
(3)、假設(shè)只有一個(gè)cpu,如果把spin_lock中的preempt_disable注釋掉, 即允許搶占。 那么使用spin_lock會產(chǎn)生死鎖嗎?
不會。threadA在執(zhí)行時(shí)臨界區(qū)時(shí),被schedule出去了,thread B試圖獲取該鎖,threadB會自旋那里(比較浪費(fèi)cpu資源),等到再次被調(diào)度到threadA并且釋放了該鎖后,threadB才可以繼續(xù)往下跑。
(4)、spinlock的臨界區(qū)為什么不允許sleep(使用schedule類函數(shù))?
Thread A調(diào)用spin_lock進(jìn)去臨界區(qū),此時(shí)該cpu已經(jīng)禁止搶占了(preempt_disable),如果此時(shí)調(diào)用sleep主動(dòng)schedule出去后,該cpu就永遠(yuǎn)回不來了因?yàn)榻箵屨剂恕_@樣的話,如果threadB再試圖獲取該鎖時(shí),就會發(fā)生死鎖。
4、 wfe/sev的使用
(1)、WFI,執(zhí)行WFI指令后,ARM core會立即進(jìn)入low-power standby state,直到有WFI Wakeup events發(fā)生。
(2)、WFE,執(zhí)行WFE指令后,根據(jù)Event Register(一個(gè)單bit的寄存器,每個(gè)PE一個(gè))的狀態(tài),有兩種情況:
a. 如果Event Register為1,該指令會把它清零,然后執(zhí)行完成(不會standby);
b. 如果Event Register為0,和WFI類似,進(jìn)入low-power standby state,直到有WFE Wakeup events發(fā)生。
(3)、SEV指令,就是一個(gè)用來改變Event Register的指令,有兩個(gè):SEV會修改所有PE上的寄存器;SEVL,只修改本PE的寄存器值
WFE應(yīng)用于arch_spin_lock場景:
a)資源空閑 b)Core1訪問資源,acquire lock,獲得資源 c)Core2訪問資源,此時(shí)資源不空閑,執(zhí)行WFE指令,讓core進(jìn)入low-power state d)Core1釋放資源,release lock,釋放資源,同時(shí)會喚醒Core2(unlock中的staddlh會喚醒WFE) e)Core2獲得資源我們也剖析下現(xiàn)有的代碼:
arch_spin_lock進(jìn)來后,先執(zhí)行sevl (Event Register變成1),再執(zhí)行wfe Event Register變成0),執(zhí)行cbnz如果沒有拿到鎖,再跳轉(zhuǎn)到2處,又執(zhí)行wfe了,此時(shí)cpu進(jìn)入low-power standby state
等到其它的cpu進(jìn)程釋放了該鎖(發(fā)送sev信號后),當(dāng)前cpu退出low-power standby state繼續(xù)往下執(zhí)行,執(zhí)行cbnz獲取到該鎖,繼續(xù)向下執(zhí)行.
問題 : 那么“其它的cpu進(jìn)程釋放了該鎖(發(fā)送sev信號后)”,其它c(diǎn)pu在哪里發(fā)送的sev信號呢???
回答 : 等待自旋鎖的時(shí)候,使用指令ldaxrh(帶有獲取語義的獨(dú)占加載,h表示halfword,即2字節(jié))讀取服務(wù)號,獨(dú)占加載操作會設(shè)置處理器的獨(dú)占監(jiān)視器,記錄鎖的物理地址。
釋放鎖的時(shí)候,使用stlrh指令修改鎖的值,stlrh指令會清除所有監(jiān)視鎖的物理地址的處理器的獨(dú)占監(jiān)視器,清除獨(dú)占監(jiān)視器的時(shí)候會生成一個(gè)喚醒事件
(可以查看armv8文檔中的 “WFE wake-up events in AArch64 state” 章節(jié),看看都是哪些事件或命令可以喚醒WFE,其中里面提到:An event caused by the clearing of the global monitor for the PE)
執(zhí)行staddlh會產(chǎn)生一個(gè)WFE的喚醒時(shí)間
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"); }總結(jié)
以上是生活随笔為你收集整理的linux kernel的spin_lock的详细介绍(以arm64为例)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [crypto]-30-The Armv
- 下一篇: linux kernel进程切换(寄存器