在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    一文讀懂Kernel同步機制的底層實現(xiàn)(超詳細~)

    一文讀懂Kernel同步機制的底層實現(xiàn)(超詳細~)

    原子操作

    通常我們代碼中的a = a + 1這樣的一行語句,翻譯成匯編后蘊含著3條指令:

    ldr x0, &aadd x0,x0,#1str x0,&a

    即 (1)從內(nèi)存中讀取a變量到X0寄存器 (2)X0寄存器加1 (3)將X0寫入到內(nèi)存a中

    既然是3條指令,那么就有可能并發(fā),也就意味著返回的結(jié)果可能不是預(yù)期的。

    然后在linux kernel的操作系統(tǒng)中,提供訪問原子變量的函數(shù),用來解決上述問題。其中部分原子操作的API如下:

    atomic_read atomic_add_return(i,v) atomic_add(i,v) atomic_inc(v) atomic_add_unless(v,a,u) atomic_inc_not_zero(v) atomic_sub_return(i,v) atomic_sub_and_test(i,v) atomic_sub(i,v) atomic_dec(v) atomic_cmpxchg(v,舊,新)

    那么操作系統(tǒng)(僅僅是軟件而已)是如何保證原子操作的呢?(還是得靠硬件),硬件原理是什么呢?

    以上的那些API函數(shù),在底層調(diào)用的其實都是如下__lse_atomic_add_return##name宏的封裝,這段代碼中最核心也就是ldadd指令了,這是armv8.1增加的LSE(Large System Extension)feature。

    (linux/arch/arm64/include/asm/atomic_lse.h)static inline int __lse_atomic_add_return##name(int i, atomic_t *v) { u32 tmp; asm volatile( __LSE_PREAMBLE ” ldadd” #mb ” %w[i], %w[tmp], %[v]” ” add %w[i], %w[i], %w[tmp]” : [i] “+r” (i), [v] “+Q” (v->counter), [tmp] “=&r” (tmp) : “r” (v) : cl); return i; }

    那么系統(tǒng)如果沒有LSE擴展呢,即armv8.0,其實現(xiàn)的原型如下所示,這段代碼中最核心的也就是ldxr、stxr指令了。

    (linux/arch/arm64/include/asm/atomic_ll_sc.h)static inline void __ll_sc_atomic_##op(int i, atomic_t *v){ unsigned long tmp; int result; asm volatile(“// atomic_” #op “” __LL_SC_FALLBACK( ” prfm pstl1strm, %2″ “1: ldxr %w0, %2″ ” ” #asm_op ” %w0, %w0, %w3″ ” stxr %w1, %w0, %2″ ” cbnz %w1, 1b”) : “=&r” (result), “=&r” (tmp), “+Q” (v->counter) : __stringify(constraint) “r” (i)); }

    那么在armv8.0之前呢,如armv7是怎樣實現(xiàn)的?如下所示, 這段代碼中最核心的也就是ldrex、strex指令了。

    (linux/arch/arm/include/asm/atomic.h)static inline void atomic_##op(int i, atomic_t *v) { unsigned long tmp; int result; prefetchw(&v->counter); __asm__ __volatile__(“@ atomic_” #op “” “1: ldrex %0, [%3]” ” ” #asm_op ” %0, %0, %4″ ” strex %1, %0, [%3]” ” teq %1, #0″ ” bne 1b” : “=&r” (result), “=&r” (tmp), “+Qo” (v->counter) : “r” (&v->counter), “Ir” (i) : “cc”); }

    總結(jié):

    在很早期,使用arm的exclusive機制來實現(xiàn)的原子操作,exclusive相關(guān)的指令也就是ldrex、strex了,但在armv8后,exclusive機制的指令發(fā)生了變化變成了ldxr、stxr。但是又由于在一個大系統(tǒng)中,處理器是非常多的,競爭也激烈,使用獨占的存儲和加載指令可能要多次嘗試才能成功,性能也就變得很差,在armv8.1為了解決該問題,增加了ldadd等相關(guān)的原子操作指令。

    更多l(xiāng)inux內(nèi)核視頻教程文檔資料免費領(lǐng)取后臺私信【內(nèi)核】自行獲取.

    Linux內(nèi)核源碼/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進程管理/設(shè)備驅(qū)動/網(wǎng)絡(luò)協(xié)議棧-學(xué)習(xí)視頻教程-騰訊課堂

    自旋鎖

    早期旋鎖的設(shè)計

    早期的spinlock的設(shè)計是鎖的擁有者加鎖時將鎖的值設(shè)置為1,釋放鎖時將鎖的值設(shè)置為0,這樣做的缺點是會出現(xiàn) 先來搶占鎖的進程一直搶占不到鎖,而后來的進程可能一來 就能獲取到鎖。導(dǎo)致這個原因的是先搶占的進程和后搶占的進程在搶占鎖時并沒有一個先后關(guān)系,最終就是離鎖所在的內(nèi)存最近的cpu節(jié)點就有更多的機會搶占鎖,離鎖所在內(nèi)存遠的節(jié)點可能一直搶占不到。

    新版旋鎖設(shè)計

    為了解決這個spinlock的不公平問題,linux 2.6.25內(nèi)核以后,spinlock采用了一種“FIFO ticket-based”算法的spinlock機制,可以很好的實現(xiàn)先來先搶占的思想。具體的做法如下:

  • spinlock的核心字段有owner和next,在初始時,owner=next=0
  • 當?shù)谝粋€進程搶占spinlock時,會在進程函數(shù)本地保存下next的值,也就是next=0,并將spinlock的next字段加1;
  • 當獲取spinlock的進程的本地next和spinlock的owner相等時,該進程就獲取到spinlock;
  • 由于第一個進程本地的next=0,并且spinlock的owner為0,所以第一個CPU獲取到spinlock;
  • 接著當?shù)诙€進程搶占spinlock,此時spinlock的next值為1,保存到本地,然后將spinlock的next字段加1。而spinlock的owner字段依然為0,第二個進程的本地next 不等于spinlock的owner,所以一直自旋等待spinlock;
  • 第三個進程搶占spinlock,得到本地next值為2,然后將spinlock的next字段加1。此時spinlock的owner字段還是為0,所以第三個進程自旋等待。
  • 當?shù)谝粋€進程處理完臨界區(qū)以后,就釋放spinlock,執(zhí)行的操作是將spinlock的owner字段加1;
  • 由于第二個進程和第三個進程都還在等待spinlock,他們會不停第獲取spinlock的owner字段,并和自己本地的next值進行比較。當?shù)诙€進程發(fā)現(xiàn)自己的next值和spinlock的owner字段相等時(此時next == owner == 2),第二個進程就獲取到spinlock。第三個進程的本地next值是3,和spinlock的owner字段不相等,所以繼續(xù)等待;
  • 只有在第二個進程釋放了spinlock,就會將spinlock的owner字段加1,第三個進程才有機會獲取spinlock。
  • 我在舉個例子,如下:

    T1 : 進程1調(diào)用spin_lock,此時next=0, owner=0獲得該鎖,在arch_spin_lock()底層實現(xiàn)中,會next++ T2 : 進程2調(diào)用spin_lock,此時next=1, owner=0沒有獲得該鎖,while(1)中調(diào)用wfe指令standby在那里,等待owner==next成立. T3 : 進程3調(diào)用spin_lock,此時next=2, owner=0沒有獲得該鎖,while(1)中調(diào)用wfe指令standby在那里,等待owner==next成立. T4&T5 : 進程1調(diào)用spin_unlock,此時owner++,即owner=1,接著調(diào)用sev指令,讓進程2和進程3退出standby狀態(tài),走while(1)流程,重新檢查owner==next條件。此時進程2條件成立,進程3繼續(xù)等待。進程2獲得該鎖,進程3繼續(xù)等待。

    Linux Kernel中的SpinLock的實現(xiàn)

    (linux/include/linux/spinlock.h)static __always_inline void spin_unlock(spinlock_t *lock){ raw_spin_unlock(&lock->rlock);}static __always_inline void spin_lock(spinlock_t *lock){ raw_spin_lock(&lock->rlock);}(linux/include/linux/spinlock.h)#define raw_spin_lock_irq(lock) _raw_spin_lock_irq(lock)#define raw_spin_lock_bh(lock) _raw_spin_lock_bh(lock)#define raw_spin_unlock(lock) _raw_spin_unlock(lock)#define raw_spin_unlock_irq(lock) _raw_spin_unlock_irq(lock)#define raw_spin_lock(lock) _raw_spin_lock(lock)(linux/kernel/locking/spinlock.c)#ifdef CONFIG_UNINLINE_SPIN_UNLOCKvoid __lockfunc _raw_spin_unlock(raw_spinlock_t *lock){ __raw_spin_unlock(lock);}EXPORT_SYMBOL(_raw_spin_unlock);#endif#ifndef CONFIG_INLINE_SPIN_LOCKvoid __lockfunc _raw_spin_lock(raw_spinlock_t *lock){ __raw_spin_lock(lock);}EXPORT_SYMBOL(_raw_spin_lock);#endif(linux/include/linux/spinlock_api_smp.h)static inline void __raw_spin_unlock(raw_spinlock_t *lock){ spin_release(&lock->dep_map, _RET_IP_); do_raw_spin_unlock(lock); preempt_enable();}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);}(linux/include/linux/spinlock.h)static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock){ mmiowb_spin_unlock(); arch_spin_unlock(&lock->raw_lock); __release(lock);}static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock){ __acquire(lock); arch_spin_lock(&lock->raw_lock); mmiowb_spin_lock();}

    對于arch_spin_lock()、arch_spin_unlock()的底層實現(xiàn),不同的kernel版本也一直在變化。

    對于kernel4.4這個版本,還是比較好理解的,最核心的也就是ldaxr、ldaxr獨占指令 ,以及stlrh release指令

    (linux/arch/arm64/include/asm/spinlock.h)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″”1: ldaxr %w0, %3″” add %w1, %w0, %w5″” stxr %w2, %w1, %3″” cbnz %w2, 1b”, /* LSE atomics */” mov %w2, %w5″” ldadda %w2, %w0, %3″” nop”” nop”” nop” ) /* Did we get the lock? */” eor %w1, %w0, %w0, ror #16″” cbz %w1, 3f” /* * No: spin on the owner. Send a local event to avoid missing an * unlock before the exclusive load. */” sevl””2: wfe”” ldaxrh %w2, %4″” eor %w1, %w2, %w0, lsr #16″” cbnz %w1, 2b” /* We got the lock. Critical section starts here. */”3:” : “=&r” (lockval), “=&r” (newval), “=&r” (tmp), “+Q” (*lock) : “Q” (lock->owner), “I” (1 <owner), "=&r" (tmp) : : "memory");}

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年6月20日 06:17
    下一篇 2022年6月20日 06:18

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息