




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
Linux內(nèi)核同步機制簡介 1介紹由于現(xiàn)代Linux操作系統(tǒng)是多任務、SMP、搶占式以及中斷是異步執(zhí)行的,導致共享資源容易被并發(fā)訪問,從而使得訪問共享資源的各線程之間互相覆蓋共享數(shù)據(jù),造成被訪問數(shù)據(jù)處于不一致狀態(tài),因此Linux提供了同步機制來防止并發(fā)訪問。常用的同步機制(如自旋鎖)用來保護共享數(shù)據(jù)使用起來簡單有效,但由于CPU的處理速度與訪問內(nèi)存的速度差距越來越大,導致獲取鎖的開銷相對于CPU的速度在不斷的增加。因為這種鎖使用了原子操作指令,需要原子地訪問內(nèi)存,即獲取鎖的開銷與訪問內(nèi)存的速度相關(guān)。Linux內(nèi)核根據(jù)對不同共享資源的特性,提供多種同步機制:原子操作、自旋鎖、讀-寫自旋鎖、信號量、讀-寫信號量、完成變量、順序鎖、禁止搶占、內(nèi)存屏障及RCU,本文將對其分別進行簡要介紹。2原子操作(atomic)2.1基本原理所謂原子操作,就是該操作絕不會在執(zhí)行完畢前被任何其它任務或事件打斷,它是最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位。原子操作通常是內(nèi)聯(lián)函數(shù),通過內(nèi)聯(lián)匯編指令來實現(xiàn)。原子操作需要硬件的支持,因此不同的體系結(jié)構(gòu)的實現(xiàn)方式不同。內(nèi)核提供了兩組原子操作接口:整數(shù)操作和位操作。2.1.2原子整數(shù)操作原子操作主要用于實現(xiàn)資源計數(shù),很多引用計數(shù)就是通過原子操作實現(xiàn)的。原子類型定義如下:(參看RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/types.h)typedefstruct{volatileint\o"Multipleusedin2324places."counter}\o"Multiplereferedfrom2781places."atomic_t;針對整數(shù)的原子操作只能對atomic_t類型的數(shù)據(jù)進行處理,原因如下:讓原子函數(shù)只接受atomic_t類型的操作數(shù),可以確保原子操作只與這種特殊類型一起使用。使用atomic_t類型確保編譯器不對相應的值進行優(yōu)化,使得原子操作最終接收到正確的內(nèi)存地址??梢云帘尾煌w系結(jié)構(gòu)上實現(xiàn)原子操作的差異。2.1.2原子位操作位操作函數(shù)是對普通的內(nèi)存地址進行操作的,對所操作的數(shù)據(jù)類型沒有要求。位操作函數(shù)有兩個參數(shù):一個是位號,一個是指針。第0位表示給定地址的最低有效位,第31位表示給定地址的最高有效位,第32位表示下一個字的最低有效位(32位系統(tǒng))。2.2相關(guān)函數(shù)2.2.1原子整數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/arch/x86/include/asm/atomic_32.h函數(shù)描述ATOMIC_INIT(inti)原子地初始化變量為iintatomic_read(atomic_t*v)原子地讀取變量vvoidatomic_set(atomic_t*v,inti);原子地設置變量v的值為ivoidatomic_add(inti,atomic_t*v);原子地給變量v加ivoidatomic_sub(inti,atomic_t*v);原子地給變量v減iintatomic_sub_and_test(inti,atomic_t*v);原子地從v減i,等于0返回真,否則返回假voidatomic_inc(atomic_t*v);原子地給變量v加1voidatomic_dec(atomic_t*v);原子地給變量v減1intatomic_dec_and_test(atomic_t*v);原子地從v減1,等于0返回真,否則返回假intatomic_inc_and_test(atomic_t*v);原子地從v加1,等于0返回真,否則返回假intatomic_add_negative(inti,atomic_t*v);原子地從v加i,等于負數(shù)返回真,否則返回假2.2.2原子位參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/asm-generic/bitops/atomic.h函數(shù)描述void\o"Multiplereferedfrom3316places."set_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地置位void\o"Multiplereferedfrom2571places."clear_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地清除void\o"Multiplereferedfrom13places."change_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地翻轉(zhuǎn)int\o"Multiplereferedfrom1190places."test_and_set_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地置位并返回該位原來的值
int\o"Multiplereferedfrom1160places."test_and_clear_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地清除并返回該位原來的值
int\o"Multiplereferedfrom11places."test_and_change_bit(int\o"Multipleusedin8217places."nr,volatileunsignedlong*\o"Multipleusedin8412places."addr)原子地翻轉(zhuǎn)并返回該位原來的值
3自旋鎖(spinlock)3.1基本原理自旋鎖最多只能被一個可執(zhí)行線程持有,這樣就可以防止多個執(zhí)行線程同時進入臨界區(qū)。如果一個執(zhí)行線程試圖獲取一個已經(jīng)被別的線程持有的自旋鎖,那么該線程就會一直進行忙循環(huán)—自旋等待鎖重新可用,因此自旋鎖不應該被長時間持有。每當使用自旋鎖的時候,會使preempt_count計數(shù)器加1,釋放自旋鎖的時候,會使其值減1。只有當preemp_count為0時,內(nèi)核才可以進行搶占。即加鎖和解鎖分別表示禁止和允許內(nèi)核搶占。自旋鎖不會導致睡眠,因此可以在中斷處理程序中使用。在中斷處理程序中使用自旋鎖時,一定要在索取鎖之前,首先禁止本地中斷(當前處理器上的中斷請求),否則中斷處理程序就會打斷正持有鎖的內(nèi)核代碼,有可能會試圖去爭用這個已經(jīng)被持有自旋鎖。這樣一來,中斷處理程序就會自旋,等待該鎖重新可用,但是鎖的持有者在這個中斷處理程序執(zhí)行完畢之前都不可能運行,這時候就會發(fā)生死鎖。注:需要關(guān)閉的只是當前處理器上的中斷。如果中斷發(fā)生在別的處理器上,即使中斷處理程序在同一鎖上自旋,也不會妨礙鎖的持有者最終釋放鎖。自旋鎖是不可遞歸的。一個執(zhí)行線程試圖得到一個已持有的自旋鎖,那么它必須自旋等待自己釋放這個鎖,但是由于處于自旋等待中永遠沒有機會釋放鎖,這樣死鎖發(fā)生了。使用鎖的時候要有針對性,鎖保護的是數(shù)據(jù)不是代碼。3.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/spinlock.h函數(shù)描述spin_lock()獲取指定的自旋鎖spin_lock_irq()禁止本地中斷,并獲取指定的自旋鎖spin_lock_irqsave()保存本地中斷的當前狀態(tài),禁止本地中斷,并獲取指定的自旋鎖spin_lock()釋放指定的鎖spin_unlock_irq()釋放指定的鎖,并激活本地中斷spin_unlock_irqstore()釋放指定的鎖,并讓本地中斷恢復到以前狀態(tài)spin_lock_init()動態(tài)初始化指定的spinlock_tspin_trylock()試圖獲取指定的鎖,如果獲取失敗,則立刻返回,不自旋等待spin_is_locked如果當前指定的鎖已經(jīng)被持有,則返回真,否則返回假4讀-寫自旋鎖(rwlock)4.1基本原理當鎖的用途可以明確地分為讀取和寫入時,如對一個鏈表可能既要進行更新又要進行檢索,這個時候使用讀-寫自旋鎖將非常有效。讀-寫自旋鎖為讀和寫分別提供了不同的鎖:讀鎖可以同時被多個執(zhí)行線程持有寫鎖只能被一個執(zhí)行線程持有,而且此時不能有并發(fā)的讀操作3)不能把一個讀鎖“升級”為寫鎖,否則會導致死鎖發(fā)生。如下所示:read_lock(&my_rwlock);write_lock(&my_rwlock);因為寫鎖會不斷自旋,等待所有的讀者釋放鎖,而自己所持有的讀鎖永遠沒有機會釋放。一個線程遞歸地獲得同一讀鎖也是安全的。讀-寫自旋鎖傾向于讀操作:當讀鎖被持有時,寫操作為了互斥訪問只能等待,而讀操作可以繼續(xù)成長地獲取鎖,等待的寫操作在所有讀操作釋放鎖之前是無法獲的到鎖的。因此,大量的讀操作會使寫操作處于饑餓狀態(tài)。4.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/spinlock.h函數(shù)描述read_lock()獲得指定的讀鎖read_lock_irq()禁止本地中斷,并獲得指定的讀鎖read_lock_irqsave()保存本地中斷的當前狀態(tài),禁止本地中斷,并獲得指定的讀鎖read_unlock()釋放指定的讀鎖read_unlock_irq()釋放指定的讀鎖,并激活本地中斷read_unlock_irqsave()釋放指定的讀鎖,并將本地中斷恢復到以前的狀態(tài)write_lock()獲得指定的寫鎖wirte_lock_irq()禁止本地中斷,并獲得指定的寫鎖write_lock_irqsave()保存本地中斷的當前狀態(tài),禁止本地中斷,并獲得指定的寫鎖write_unlock()釋放指定的寫鎖write_unlock_irq()釋放指定的寫鎖,并激活本地中斷write_unlock_iqrstore()釋放指定的寫鎖,并將本地中斷恢復到以前的狀態(tài)write_trylock()試圖獲得指定的寫鎖,如果獲取失敗,立刻返回,不自旋等待rw_lock_init()初始化指定的rwlock_trw_is_locked()如如果當前指定的鎖已經(jīng)被持有,則返回真,否則返回假5信號量(semaphore)5.1基本原理信號量是一種睡眠鎖。如果一個進程試圖獲得一個已經(jīng)被占用的信號量,信號量將會將其放置一個等待隊列,然后讓其睡眠。這時處理器能夠重新調(diào)度別的進程執(zhí)行,等持有信號量的進程將信號量釋放后,處于等待隊列中的那個進程將被喚醒,并獲得該信號量。由于進程在獲取信號量失敗時,不是進行自旋等待,而是進行睡眠,這樣就比自旋鎖提供了更好的處理器利用率。但是信號量比自旋鎖有更大的開銷,因為睡眠會引起進程上下文切換,從而導致產(chǎn)生開銷。信號量不同于自旋鎖,它不會禁止內(nèi)核搶占,所以持有信號量的代碼可以被搶占。信號量在創(chuàng)建時需要設置一個初始值,表示同時可以有多少個執(zhí)行線程獲取該信號量,若初始值為1該信號量就變成互斥鎖,即同時只有一個執(zhí)行線程可以獲取該信號量。根據(jù)信號量的睡眠特性可以得出如下一些信息:由于爭用信號量的進程在等待鎖重新變?yōu)榭捎脮r會睡眠,所以信號量適用于鎖會被長時間持有的情況。由于進程在鎖被爭用時會睡眠,所以只能在進程上下文中才能獲取信號量鎖,因為在中斷上下文中是不能進行睡眠的。進程在占用信號量的同時不能占用自旋鎖,因為在等待信號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。5.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/kernel/semaphore.c、/root/include/linux/semaphore.h函數(shù)描述void\o"Multiplereferedfrom49places."sema_init(struct\o"Multipleusedin46places."semaphore*\o"Multipleusedin228places."sem,int\o"Multipleusedin16546places."val)以指定的計數(shù)值初始化動態(tài)創(chuàng)建的信號量void\o"Multiplereferedfrom528places."down(struct\o"Multipleusedin46places."semaphore*\o"Multipleusedin228places."sem)試圖獲得指定的信號量,如果信號量已被別的進程持有,則進入不可中斷睡眠狀態(tài)int\o"Multiplereferedfrom68places."down_interruptible(struct\o"Multipleusedin46places."semaphore*\o"Multipleusedin228places."sem)試圖獲得指定的信號量,如果信號量已被別的進程持有,則進入可中斷睡眠狀態(tài)int\o"Multiplereferedfrom40places."down_trylock(struct\o"Multipleusedin46places."semaphore*\o"Multipleusedin228places."sem)以試圖獲得指定的信號量,如果信號量已被別的進程持有,則立刻返回非0值void\o"Multiplereferedfrom4444places."up(struct\o"Multipleusedin46places."semaphore*\o"Multipleusedin228places."sem)釋放指定的信號量,如果睡眠隊列不為空,則喚醒其中一個進程。6讀-寫信號量(rwsemaphore)6.1基本原理讀-寫信號量與讀-寫自旋鎖類似,對訪問者進行了劃分:讀者和寫者。所有的讀-寫信號量都是互斥信號量。讀-寫信號量可以同時被多個讀者持有,但只能同時被一個寫者持有且此時不能有并發(fā)的讀者。寫者在不需寫訪問的時候可以“降級”為讀者。6.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/kernel/rwsem.c、/root/include/linux/rwsem-spinlock.h函數(shù)描述init_rwsem(sem)對讀-寫信號量sem進行初始化void\o"Multiplereferedfrom505places."down_read(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)讀者獲取讀-寫信號量sem,獲取失敗進入睡眠狀態(tài)int\o"Multiplereferedfrom27places."down_read_trylock(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)讀者獲取讀-寫信號量sem,獲取失敗立刻返回0void\o"Multiplereferedfrom602places."down_write(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)寫者獲取讀-寫信號量sem,獲取失敗進入睡眠狀態(tài)int\o"Multiplereferedfrom10places."down_write_trylock(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)寫者獲取讀-寫信號量sem,獲取失敗立刻返回0void\o"Multiplereferedfrom703places."up_read(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)讀者釋放讀-寫信號量semvoid\o"Multiplereferedfrom754places."up_write(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)寫者釋放讀-寫信號量semvoid\o"Multiplereferedfrom8places."downgrade_write(struct\o"Multipleusedin173places."rw_semaphore*\o"Multipleusedin228places."sem)把寫者“降級”為讀者7完成變量(completion)7.1基本原理如果內(nèi)核中一個任務需要發(fā)出信號通知另一個任務發(fā)生了某個特定的事件,利用完成變量使得兩個任務同步。如vfork系統(tǒng)調(diào)用中,當子進程退出時,會使用完成變量喚醒父進程。完成變量僅僅提供了替代信號量的一個簡單解決方法。7.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/completion.h、/root/kernel/sched.c函數(shù)描述voidinit_completion(structcompletion*)初始化動態(tài)創(chuàng)建的完成變量voidcomplete(structcompletion*)發(fā)送信號喚醒等待的任務voidwait_for_completion(structcompletion*)等待指定的完成變量發(fā)送信號8順序鎖(sequencelock)8.1基本原理順序鎖是對讀-寫鎖的一種優(yōu)化,這種鎖主要依靠一個序列計數(shù)器實現(xiàn)。每當寫者獲取該鎖時,使序列計數(shù)器加1,釋放該鎖時,也會使序列計數(shù)器再加1。注:寫者獲取順序鎖的功能等同于“spin_lock同時使序列計數(shù)器加1”。讀者絕不會被寫者阻塞。讀者在讀取共享數(shù)據(jù)前后都會讀取序列計數(shù)器,如果序列計數(shù)器的值一樣,說明讀者在讀取共享數(shù)據(jù)的過程中沒有被寫者打斷。如果序列計數(shù)器的值發(fā)生了變化,說明讀者讀操作期間有寫者進行操作,那么讀者必須重新讀取數(shù)據(jù),以便確保該數(shù)據(jù)是完整的。讀操作如下所示:do{seq=read_seqbegin(&my_seq_lock);/*readsharedata*/}while(read_seqretry(&my_seq_lock,seq));注:讀者實際沒有任何獲取鎖和釋放鎖的開銷,只是獲取序列計數(shù)器。順序鎖中寫者不會阻塞讀者,讀者也不會阻塞寫者,但是寫者和寫者之間仍然是互斥的。順序鎖有一個限制,它要求被保護的共享資源不含有指。因為寫者可能使得指針失效,但讀者如果正要訪問該指針,那么將會導致系統(tǒng)崩潰。順序鎖是一種對寫者更有利的鎖。因為只要沒有其他寫者,寫鎖總是能夠被成功獲得,而寫者在操作期間,使得讀者必須不斷循環(huán)地進行讀操作,知道寫者釋放鎖。8.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/seqlock.h函數(shù)描述unsigned\o"Multiplereferedfrom35places."read_seqbegin(const\o"Definedat35ininclude/linux/seqlock.h."seqlock_t*)讀者在在訪問共享數(shù)據(jù)之前調(diào)用的函數(shù)int\o"Multiplereferedfrom35places."read_seqretry(const\o"Definedat35ininclude/linux/seqlock.h."seqlock_t*,unsigned)讀者在在訪問共享數(shù)據(jù)之后調(diào)用的函數(shù)void\o"Multiplereferedfrom29places."write_seqlock(\o"Definedat35ininclude/linux/seqlock.h."seqlock_t*\o"Multipleusedin110places.")寫者獲取順序鎖,如果獲取失敗,自旋等待void\o"Multiplereferedfrom29places."write_sequnlock(\o"Definedat35ininclude/linux/seqlock.h."seqlock_t*)寫者釋放順序鎖intwrite_tryseqlock(\o"Definedat35ininclude/linux/seqlock.h."seqlock_t*)寫者獲取順序鎖,如果獲取失敗,立刻返回9禁止搶占(disablepreempt)9.1基本原理使用自旋鎖可以禁止內(nèi)核搶占,但是若共享數(shù)據(jù)對每個處理器是唯一的,那么可以通過使用preempt_disable()函數(shù)來禁止內(nèi)核搶占,而不必使用自旋鎖。注:使用禁止內(nèi)核搶占比使用自旋鎖開銷小。preempt_disable()可以嵌套調(diào)用。preempt_disable()調(diào)用多少次就需要調(diào)用preemt_enabled()多少次才能使內(nèi)核搶占重新啟用。premmpt_disable()和premmpt_enable()會對搶占計數(shù)器preemp_count(低8位)進行操作。搶占計數(shù)器preempt_count分為4段:bit32-24(區(qū)域A)bit23-16(區(qū)域B)bit15-8(區(qū)域C)bit7-0(區(qū)域D)其它硬中斷計數(shù)軟中斷計數(shù)普通搶占只有當preempt_count=0,才表示內(nèi)核可以搶占。9.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/include/linux/preempt.h函數(shù)描述preempt_disable()增加搶占計數(shù)值,從而禁止內(nèi)核搶占preempt_enable()減少搶占計數(shù)值,若值為0表示內(nèi)核搶占重新啟用preempt_enable_no_resched()減少搶占計數(shù)值,若值為0表示內(nèi)核搶占重新啟用,但不檢查被掛起的需要調(diào)度的任務preempt_count()返回搶占計數(shù)值10內(nèi)存屏障(memorybarrier)10.1基本原理內(nèi)存屏障主要解決兩個問題:單處理器下的亂序問題和多處理器下的內(nèi)存同步問題。CPU亂序的由來:CPU采用流水線來執(zhí)行指令,一個指令的執(zhí)行被分成:取指、譯碼、訪存、執(zhí)行、寫回等多個階段。流水線是并行的。多條指令可以同時存在于流水線中,同時被執(zhí)行,只要CPU內(nèi)部相應的處理部件未被占滿即可,比如說CPU有一個加法器和一個除法器,那么一條加法指令和一條除法指令就可能同時處于“執(zhí)行”階段。在X86平臺下,CPU總是順序的從內(nèi)存里面取指令,然后將其順序的放入指令流水線,但是由于指令執(zhí)行時的各種條件,指令與指令之間的互相影響,可能導致順序放入流水線的指令,最終亂序執(zhí)行完成。比如,一條加法指令原本出現(xiàn)在一個除法指令的后面,但是由于除法指令的執(zhí)行時間很長,在它執(zhí)行完之前,加法指令可能已經(jīng)執(zhí)行完了。CPU亂序的特點:CPU的亂序并不是任意的亂序,而是以保證上下文因果關(guān)系為前提的。如下的代碼:b=c/d;a=1;有可能在b中存儲新值之前就在a中存儲1了。 如下的代碼:b=c/d;a=b+1;由于a=b+1這條指令的執(zhí)行結(jié)果依賴于前一條指令b=c/d的執(zhí)行結(jié)果,那么在b=c/d執(zhí)行完畢之前,a=b+1將被在“執(zhí)行”階段阻塞。編譯器的亂序:作為編譯優(yōu)化的一種手段,是真正對指令順序做了調(diào)整,但是編譯器的亂序也必須保證程序上下文的因果關(guān)系不發(fā)生改變。如下所示:b=c/d;a=f(b);;e++;由于a=f(b)這條指令必定要等待b=c/d這條指令的執(zhí)行結(jié)果,a=f(b)會阻塞在“執(zhí)行”階段,從而占用流水線資源。編譯器優(yōu)化可能對上述代碼進行如下排序:b=c/d;e++;a=f(b);這樣的話,由于指令b=c/d和指令a=f(b)間隔開了,很有可能a=f(b)指令在進入CPU的時候,b=c/d已經(jīng)執(zhí)行完畢了,這樣a=f(b)就不會在流水線中阻塞了。亂序的后果:有了“保證程序上下文因果關(guān)系”這一前提,一般情況下是不會有問題的,但是有些邏輯程序,單純從上下文中看不出它們的因果關(guān)系,這樣亂序后就會出現(xiàn)問題。如下的代碼:*addr=5;val=*data;從表面上看,addr和data沒有任何聯(lián)系,可以放心的亂序執(zhí)行。但是如果這是在某設備驅(qū)動中,這兩個變量可能對應到設備的地址端口和數(shù)據(jù)端口,并且這個設備規(guī)定讀寫設備上的某個寄存器時,先將寄存器編號設置到地址端口,然后就可以通過對數(shù)據(jù)端口的讀寫而操作對應的寄存器。這樣的話,一亂序執(zhí)行就可能造成錯誤。如下的代碼:(CPU1)線程1(CPU2)線程2obj->ready=1;obj->data=100;if(obj->read){do_something(obj->data);}線程1給標記位ready置1和設置data,線程2根據(jù)標記位ready的值來判斷是否讀取data然后做處理。由于date和ready沒有邏輯關(guān)系,那么亂序很可能發(fā)生,結(jié)果執(zhí)行流程可能為這樣:線程1設置data,線程2判斷ready的值為假,不讀取data進行處理。內(nèi)存屏障主要有:讀屏障、數(shù)據(jù)相關(guān)讀屏障、寫屏障、通用屏障和編譯器屏障。讀屏障:它用于保證讀操作有序,屏障之前的讀操作一定會先于屏障之后的讀操作完成,寫操作不受影響,且同屬于屏障某一側(cè)的讀操作不受影響。舉個例子,如下所示:a=b;//讀操作a2=b2;//讀操作i=1;//寫操作rmb();//讀屏障c=d;//讀操作j=2;//寫操作語句c=d一定后于a=b以及a2=b2執(zhí)行,屏障同一側(cè)的a=b和a2=b2的執(zhí)行順序可以重新排序,寫操作i=1和j=2的執(zhí)行順序也可以重新排序。數(shù)據(jù)相關(guān)度屏障:它與讀屏障類似,但是屏障之后的讀操作與屏障之前的讀操作是數(shù)據(jù)相關(guān)的。其速度比讀屏障速度要快。舉個例子,如下所示:pp=p;read_barrier_depends();//數(shù)據(jù)相關(guān)讀屏障b=*pp;//注意:這里是*pp不是pp,否則就不需要屏障來保證執(zhí)行順序由于b=*pp,所以b和pp是數(shù)據(jù)相關(guān)的,使用數(shù)據(jù)相關(guān)度屏障可以保證執(zhí)行順序。寫屏障:它用于保證寫操作有序,屏障之前的寫操作一定會先于屏障之后的寫操作完成,讀操作不受影響,且同屬于屏障某一側(cè)的寫操作不受影響。通用屏障:它用于保證讀和寫操作有序,屏障之前的讀和寫操作一定會先于屏障之后的讀和寫操作完成,且同屬于屏障某一側(cè)的讀和寫操作不受影響。編譯器屏障:用于限制編譯器的指令重新排序。讀屏障、寫屏障、通用屏障都隱含了編譯器屏障的功能。多處理器的屏障:一個處理器(CPU—a)對內(nèi)存的寫操作并不是直接就在內(nèi)存上生效的,而是要先經(jīng)過自身的Cache。另一處理器(CPU—b)如果要讀取相應內(nèi)存上的新值,需要等CPU-a的Cache同步到內(nèi)存,然后CPU-b的Cache再從內(nèi)存同步這個新值。而如果需要同步的值不止一個的話,就會存在順序問題。如下所示:CPU-aCPU-ba=3;wmb();b=4;c=b;rmb();d=a;由于使用了wmb()保證了CPU-a不發(fā)生亂序,同時也保證屏障之前的Cache消息先于屏障之后的消息被發(fā)出去(發(fā)送給CPU-b)。CPU-b接收到CPU-a發(fā)送的Cache消息,但是可能會先處理b=4的消息(更新b的數(shù)據(jù)),導致最后c=4,而d并不等于3。使用rmb()可以確保CPU-b按順序更新接收到的消息,從而使得c=4和d=3。多處理器下屏障的誤區(qū):內(nèi)存屏障保證的是“一個CPU的多個操作的順序”(被另一個CPU所觀察到的順序),而不保證“兩個CPU的操作順序”。如下所示:CPU-aCPU-ba=5;wmb();b=6;j=b;rmb();I=a;i不一定等于5,因為兩個CPU的執(zhí)行操作沒有關(guān)聯(lián),不知誰先執(zhí)行,因此內(nèi)存屏障保證的是一個CPU的多個操作順序。10.2相關(guān)函數(shù)參考RHEL6.5GA_x86_64內(nèi)核文件:/root/arch/x86/include/asm/system.h函數(shù)描述rmb()讀屏障read_barrier_depends()數(shù)據(jù)相關(guān)讀屏障(在x86平臺僅是空操作)wmb()寫屏障mb()通用屏障smp_rmb()在SMP上提供rmb()功能,在UP上提供barrier()功能smp_read_barrier_depends()在SMP上提供提供read_barrier_depends()功能smp_wmb()在SMP上提供wmb()功能,在UP上提供barrier()功能smp_mb()在SMP上提供mb()功能,在UP上提供barrier()功能barrier()阻止編譯器重新排序11RCU(read-copy-update)11.1基本原理RCU的適用范圍:RCU針對經(jīng)常發(fā)生讀取而很少寫入的情形做了優(yōu)化,因此適用于讀者多而寫者少的情況。RCU的使用:讀者不需要獲取任何鎖就可以訪問共享資源,即讀者不需要與其它讀者或?qū)懻弑3滞?。寫者與寫者之間需要保持同步,且寫者必須要等它之前的讀者全部都退出之后才能釋放之前的資源。RCU的機制:RCU保護的是指針。因為指針賦值是一條單指令(原子操作),因此更改指針指向不需要考慮它的同步,只需要考慮Cache的影響。寫者對共享資源進行操作時,會對共享資源進行拷貝,然后對拷貝的數(shù)據(jù)進行修改,修改完成之后,最后會使用一個回調(diào)(callback)機制在適當?shù)臅r機(之前的讀者全部退出之后,這一時機也稱為graceperiod)把指向原來數(shù)據(jù)的指針重新指向新的被修改的數(shù)據(jù)。RCU的規(guī)定:讀者在臨界區(qū)內(nèi)(調(diào)用rcu_read_lock(可以嵌套調(diào)用)和rcu_read_unlock期間)不能發(fā)生進程上下文切換(因為寫者需要等待讀者完成,否則寫者進程也會一直被阻塞),但是允許中斷發(fā)生。如下所示:(參考RHEL6.5GA_x86_64內(nèi)核文件root/include/linux/rcutree.h、root/include/linux/rcupdate.hstaticinlinevoid\o"Multiplereferedfrom902places."rcu_read_lock(void){\o"Multipledefinedin2places."__rcu_read_lock();\o"Multipledefinedin2places."__acquire(\o"Multipleusedin32places."RCU);//選擇編譯函數(shù)\o"Multipled
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 醫(yī)藥代理協(xié)議合同范本
- 華能煤炭供應合同范本
- 農(nóng)副產(chǎn)品化肥購銷合同范本
- 南京銷售人員合同范例
- 占用公路施工合同范本
- 伐木砍樹勞務合同范本
- 醫(yī)院臨時護工陪護合同范本
- 借款終止合同范本
- 勞務派遣甲方合同范本
- 中標工程平移合同范本
- 《供應鏈管理》課程整體設計
- 水利工程危險源辨識評價及風險管控清單
- 桂西北丹池成礦帶主要金屬礦床成礦特征及成礦規(guī)律
- 申論范文:社區(qū)微治理 共建美好家園
- 高等工程熱力學教案課件
- 2023年征信知識競賽基礎題考試復習題庫(帶答案)
- 汽車機械基礎PPT(第3版)全套完整教學課件
- 醫(yī)療器械質(zhì)量管理制度
- 【招標控制價編制研究文獻綜述(論文)4800字】
- 紅樓夢讀書筆記4000字(3篇)
- 高等職業(yè)學校鐵道信號自動控制專業(yè)實訓教學條件建設標準
評論
0/150
提交評論