在linux內(nèi)核中,有很多同步機(jī)制。比較經(jīng)典的有原子操作、spin_lock(忙等待的鎖)、mutex(互斥鎖)、semaphore(信號(hào)量)等。并且它們幾乎都有對(duì)應(yīng)的rw_XXX(讀寫(xiě)鎖),以便在能夠區(qū)分讀與寫(xiě)的情況下,讓讀操作相互不互斥(讀寫(xiě)、寫(xiě)寫(xiě)依然互斥)。而seqlock和rcu應(yīng)該可以不算在經(jīng)典之列,它們是兩種比較有意思的同步機(jī)制。 atomic(原子操作): 所謂原子操作,就是該操作絕不會(huì)在執(zhí)行完畢前被任何其他任務(wù)或事件打斷,也就說(shuō),它的最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位,因此這里的原子實(shí)際是使用了物理學(xué)里的物質(zhì)微粒的概念。
Spanlock(自旋鎖) 自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會(huì)引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。由于自旋鎖使用者一般保持鎖時(shí)間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖。 信號(hào)量和讀寫(xiě)信號(hào)量適合于保持時(shí)間較長(zhǎng)的情況,它們會(huì)導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合于保持時(shí)間非常短的情況,它可以在任何上下文使用。如果被保護(hù)的共享資源只在進(jìn)程上下文訪問(wèn),使用信號(hào)量保護(hù)該共享資源非常合適,如果對(duì)共巷資源的訪問(wèn)時(shí)間非常短,自旋鎖也可以。但是如果被保護(hù)的共享資源需要在中斷上下文訪問(wèn)(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。 自旋鎖保持期間是搶占失效的,而信號(hào)量和讀寫(xiě)信號(hào)量保持期間是可以被搶占的。自旋鎖只有在內(nèi)核可搶占或SMP的情況下才真正需要,在單CPU且不可搶占的內(nèi)核下,自旋鎖的所有操作都是空操作。 跟互斥鎖一樣,一個(gè)執(zhí)行單元要想訪問(wèn)被自旋鎖保護(hù)的共享資源,必須先得到鎖,在訪問(wèn)完共享資源后,必須釋放鎖。如果在獲取自旋鎖時(shí),沒(méi)有任何執(zhí)行單元保持該鎖,那么將立即得到鎖;如果在獲取自旋鎖時(shí)鎖已經(jīng)有保持者,那么獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。 無(wú)論是互斥鎖,還是自旋鎖,在任何時(shí)刻,最多只能有一個(gè)保持者,也就說(shuō),在任何時(shí)刻最多只能有一個(gè)執(zhí)行單元獲得鎖。 自旋鎖的API有: spin_lock_init(x) 該宏用于初始化自旋鎖x。自旋鎖在真正使用前必須先初始化。該宏用于動(dòng)態(tài)初始化。 DEFINE_SPINLOCK(x) 該宏聲明一個(gè)自旋鎖x并初始化它。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒(méi)有該宏。 SPIN_LOCK_UNLOCKED 該宏用于靜態(tài)初始化一個(gè)自旋鎖。 DEFINE_SPINLOCK(x)等同于spinlock_tx = SPIN_LOCK_UNLOCKED spin_is_locked(x) 該宏用于判斷自旋鎖x是否已經(jīng)被某執(zhí)行單元保持(即被鎖),如果是,返回真,否則返回假。 spin_unlock_wait(x) 該宏用于等待自旋鎖x變得沒(méi)有被任何執(zhí)行單元保持,如果沒(méi)有任何執(zhí)行單元保持該自旋鎖,該宏立即返回,否則將循環(huán)在那里,直到該自旋鎖被保持者釋放。 spin_trylock(lock) 該宏盡力獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖并返回真,否則不能立即獲得鎖,立即返回假。它不會(huì)自旋等待lock被釋放。 spin_lock(lock) 該宏用于獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那里,直到該自旋鎖的保持者釋放,這時(shí),它獲得鎖并返回。總之,只有它獲得鎖才返回。 spin_lock_irqsave(lock, flags) 該宏獲得自旋鎖的同時(shí)把標(biāo)志寄存器的值保存到變量flags中并失效本地中斷。 spin_lock_irq(lock) 該宏類似于spin_lock_irqsave,只是該宏不保存標(biāo)志寄存器的值。 spin_lock_bh(lock) 該宏在得到自旋鎖的同時(shí)失效本地軟中斷。 spin_unlock(lock) 該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對(duì)使用。如果spin_trylock返回假,表明沒(méi)有獲得自旋鎖,因此不必使用spin_unlock釋放。 spin_unlock_irqrestore(lock, flags) 該宏釋放自旋鎖lock的同時(shí),也恢復(fù)標(biāo)志寄存器的值為變量flags保存的值。它與spin_lock_irqsave配對(duì)使用。 spin_unlock_irq(lock) 該宏釋放自旋鎖lock的同時(shí),也使能本地中斷。它與spin_lock_irq配對(duì)應(yīng)用。 spin_unlock_bh(lock) 該宏釋放自旋鎖lock的同時(shí),也使能本地的軟中斷。它與spin_lock_bh配對(duì)使用。 spin_trylock_irqsave(lock, flags) 該宏如果獲得自旋鎖lock,它也將保存標(biāo)志寄存器的值到變量flags中,并且失效本地中斷,如果沒(méi)有獲得鎖,它什么也不做。因此如果能夠立即獲得鎖,它等同于spin_lock_irqsave,如果不能獲得鎖,它等同于spin_trylock。如果該宏獲得自旋鎖lock,那需要使用spin_unlock_irqrestore來(lái)釋放。 spin_trylock_irq(lock) 該宏類似于spin_trylock_irqsave,只是該宏不保存標(biāo)志寄存器。如果該宏獲得自旋鎖lock,需要使用spin_unlock_irq來(lái)釋放。 spin_trylock_bh(lock) 該宏如果獲得了自旋鎖,它也將失效本地軟中斷。如果得不到鎖,它什么也不做。因此,如果得到了鎖,它等同于spin_lock_bh,如果得不到鎖,它等同于spin_trylock。如果該宏得到了自旋鎖,需要使用spin_unlock_bh來(lái)釋放。 spin_can_lock(lock) 該宏用于判斷自旋鎖lock是否能夠被鎖,它實(shí)際是spin_is_locked取反。如果lock沒(méi)有被鎖,它返回真,否則,返回假。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒(méi)有該宏。 獲得自旋鎖和釋放自旋鎖有好幾個(gè)版本,因此讓讀者知道在什么樣的情況下使用什么版本的獲得和釋放鎖的宏是非常必要的。 如果被保護(hù)的共享資源只在進(jìn)程上下文訪問(wèn)和軟中斷上下文訪問(wèn),那么當(dāng)在進(jìn)程上下文訪問(wèn)共享資源時(shí),可能被軟中斷打斷,從而可能進(jìn)入軟中斷上下文來(lái)對(duì)被保護(hù)的共享資源訪問(wèn),因此對(duì)于這種情況,對(duì)共享資源的訪問(wèn)必須使用spin_lock_bh和spin_unlock_bh來(lái)保護(hù)。當(dāng)然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當(dāng)?shù)?,它比其他兩個(gè)快。 如果被保護(hù)的共享資源只在進(jìn)程上下文和tasklet或timer上下文訪問(wèn),那么應(yīng)該使用與上面情況相同的獲得和釋放鎖的宏,因?yàn)閠asklet和timer是用軟中斷實(shí)現(xiàn)的。 如果被保護(hù)的共享資源只在一個(gè)tasklet或timer上下文訪問(wèn),那么不需要任何自旋鎖保護(hù),因?yàn)橥粋€(gè)tasklet或timer只能在一個(gè)CPU上運(yùn)行,即使是在SMP環(huán)境下也是如此。實(shí)際上tasklet在調(diào)用tasklet_schedule標(biāo)記其需要被調(diào)度時(shí)已經(jīng)把該tasklet綁定到當(dāng)前CPU,因此同一個(gè)tasklet決不可能同時(shí)在其他CPU上運(yùn)行。timer也是在其被使用add_timer添加到timer隊(duì)列中時(shí)已經(jīng)被幫定到當(dāng)前CPU,所以同一個(gè)timer絕不可能運(yùn)行在其他CPU上。當(dāng)然同一個(gè)tasklet有兩個(gè)實(shí)例同時(shí)運(yùn)行在同一個(gè)CPU就更不可能了。 如果被保護(hù)的共享資源只在兩個(gè)或多個(gè)tasklet或timer上下文訪問(wèn),那么對(duì)共享資源的訪問(wèn)僅需要用spin_lock和spin_unlock來(lái)保護(hù),不必使用_bh版本,因?yàn)楫?dāng)tasklet或timer運(yùn)行時(shí),不可能有其他tasklet或timer在當(dāng)前CPU上運(yùn)行。如果被保護(hù)的共享資源只在一個(gè)軟中斷(tasklet和timer除外)上下文訪問(wèn),那么這個(gè)共享資源需要用spin_lock和spin_unlock來(lái)保護(hù),因?yàn)橥瑯拥能浿袛嗫梢酝瑫r(shí)在不同的CPU上運(yùn)行。 如果被保護(hù)的共享資源在兩個(gè)或多個(gè)軟中斷上下文訪問(wèn),那么這個(gè)共享資源當(dāng)然更需要用spin_lock和spin_unlock來(lái)保護(hù),不同的軟中斷能夠同時(shí)在不同的CPU上運(yùn)行。 如果被保護(hù)的共享資源在軟中斷(包括tasklet和timer)或進(jìn)程上下文和硬中斷上下文訪問(wèn),那么在軟中斷或進(jìn)程上下文訪問(wèn)期間,可能被硬中斷打斷,從而進(jìn)入硬中斷上下文對(duì)共享資源進(jìn)行訪問(wèn),因此,在進(jìn)程或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來(lái)保護(hù)對(duì)共享資源的訪問(wèn)。而在中斷處理句柄中使用什么版本,需依情況而定,如果只有一個(gè)中斷處理句柄訪問(wèn)該共享資源,那么在中斷處理句柄中僅需要spin_lock和spin_unlock來(lái)保護(hù)對(duì)共享資源的訪問(wèn)就可以了。因?yàn)樵趫?zhí)行中斷處理句柄期間,不可能被同一CPU上的軟中斷或進(jìn)程打斷。但是如果有不同的中斷處理句柄訪問(wèn)該共享資源,那么需要在中斷處理句柄中使用spin_lock_irq和spin_unlock_irq來(lái)保護(hù)對(duì)共享資源的訪問(wèn)。 在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應(yīng)該使用哪一個(gè)也需要依情況而定,如果可以確信在對(duì)共享資源訪問(wèn)前中斷是使能的,那么使用spin_lock_irq更好一些,因?yàn)樗萻pin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因?yàn)樗鼘⒒謴?fù)訪問(wèn)共享資源前的中斷標(biāo)志而不是直接使能中斷。當(dāng)然,有些情況下需要在訪問(wèn)共享資源時(shí)必須中斷失效,而訪問(wèn)完后必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。 需要特別提醒讀者,spin_lock用于阻止在不同CPU上的執(zhí)行單元對(duì)共享資源的同時(shí)訪
mutex(互斥鎖) /linux/include/linux/mutex.h 47struct mutex { 48 49 atomic_t count; 50 spinlock_t wait_lock; 51 struct list_head wait_list; 52#ifdef CONFIG_DEBUG_MUTEXES 53 struct thread_info *owner; 54 const char *name; 55 void *magic; 56#endif 57#ifdef CONFIG_DEBUG_LOCK_ALLOC 58 struct lockdep_map dep_map; 59#endif 60};
一、作用及訪問(wèn)規(guī)則: 互斥鎖主要用于實(shí)現(xiàn)內(nèi)核中的互斥訪問(wèn)功能。內(nèi)核互斥鎖是在原子API之上實(shí)現(xiàn)的,但這對(duì)于內(nèi)核用戶是不可見(jiàn)的。對(duì)它的訪問(wèn)必須遵循一些規(guī)則:同一時(shí)間只能有一個(gè)任務(wù)持有互斥鎖,而且只有這個(gè)任務(wù)可以對(duì)互斥鎖進(jìn)行解鎖?;コ怄i不能進(jìn)行遞歸鎖定或解鎖。一個(gè)互斥鎖對(duì)象必須通過(guò)其API初始化,而不能使用memset或復(fù)制初始化。一個(gè)任務(wù)在持有互斥鎖的時(shí)候是不能結(jié)束的?;コ怄i所使用的內(nèi)存區(qū)域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。并且互斥鎖不能用于中斷上下文。但是互斥鎖比當(dāng)前的內(nèi)核信號(hào)量選項(xiàng)更快,并且更加緊湊,因此如果它們滿足您的需求,那么它們將是您明智的選擇。 二、各字段詳解: 1、atomic_t count; --指示互斥鎖的狀態(tài):1沒(méi)有上鎖,可以獲得;0被鎖定,不能獲得;負(fù)數(shù)被鎖定,且可能在該鎖上有等待進(jìn)程初始化為沒(méi)有上鎖。 2、spinlock_t wait_lock;--等待獲取互斥鎖中使用的自旋鎖。在獲取互斥鎖的過(guò)程中,操作會(huì)在自旋鎖的保護(hù)中進(jìn)行。初始化為為鎖定。 3、struct list_head wait_list;--等待互斥鎖的進(jìn)程隊(duì)列。 四、操作: 1、定義并初始化: struct mutex mutex; mutex_init(&mutex); 79# define mutex_init(mutex) \ 80do { \ 81 static struct lock_class_key __key; \ 82 \ 83 __mutex_init((mutex), #mutex, &__key); \ 84} while (0) 42void 43__mutex_init(struct mutex *lock, const char *name, structlock_class_key *key) 44{ 45 atomic_set(&lock->count, 1); 46 spin_lock_init(&lock->wait_lock); 47 INIT_LIST_HEAD(&lock->wait_list); 48 49 debug_mutex_init(lock, name, key); 50} 直接定于互斥鎖mutex并初始化為未鎖定,己count為1,wait_lock為未上鎖,等待隊(duì)列wait_list為空。 2、獲取互斥鎖: (1)具體參見(jiàn)linux/kernel/mutex.c void inline __sched mutex_lock(struct mutex *lock) { might_sleep(); __mutex_fastpath_lock(&lock->count,__mutex_lock_slowpath); } 獲取互斥鎖。實(shí)際上是先給count做自減操作,然后使用本身的自旋鎖進(jìn)入臨界區(qū)操作。首先取得count的值,再將count置為-1,判斷如果原來(lái)count的值為1,也即互斥鎖可以獲得,則直接獲取,跳出。否則進(jìn)入循環(huán)反復(fù)測(cè)試互斥鎖的狀態(tài)。在循環(huán)中,也是先取得互斥鎖原來(lái)的狀態(tài),再將其置為-1,判斷如果可以獲取(等于1),則退出循環(huán),否則設(shè)置當(dāng)前進(jìn)程的狀態(tài)為不可中斷狀態(tài),解鎖自身的自旋鎖,進(jìn)入睡眠狀態(tài),待被在調(diào)度喚醒時(shí),再獲得自身的自旋鎖,進(jìn)入新一次的查詢其自身狀態(tài)(該互斥鎖的狀態(tài))的循環(huán)。 (2)具體參見(jiàn)linux/kernel/mutex.c int __sched mutex_lock_interruptible(struct mutex *lock) { might_sleep(); return __mutex_fastpath_lock_retval(&lock->count,__mutex_lock_interruptible_slowpath); } 和mutex_lock()一樣,也是獲取互斥鎖。在獲得了互斥鎖或進(jìn)入睡眠直到獲得互斥鎖之后會(huì)返回0。如果在等待獲取鎖的時(shí)候進(jìn)入睡眠狀態(tài)收到一個(gè)信號(hào)(被信號(hào)打斷睡眠),則返回_EINIR。 (3)具體參見(jiàn)linux/kernel/mutex.c int __sched mutex_trylock(struct mutex *lock) { return __mutex_fastpath_trylock(&lock->count, __mutex_trylock_slowpath); } 試圖獲取互斥鎖,如果成功獲取則返回1,否則返回0,不等待。 3、釋放互斥鎖: 具體參見(jiàn)linux/kernel/mutex.c void __sched mutex_unlock(struct mutex *lock) { __mutex_fastpath_unlock(&lock->count,__mutex_unlock_slowpath); } 釋放被當(dāng)前進(jìn)程獲取的互斥鎖。該函數(shù)不能用在中斷上下文中,而且不允許去釋放一個(gè)沒(méi)有上鎖的互斥鎖。 4.void mutex_destroy(struct mutex *lock) --清除互斥鎖,使互斥鎖不可用 用mutex_destroy()函數(shù)解除由lock指向的互斥鎖的任何狀態(tài)。在調(diào)用執(zhí)行這個(gè)函數(shù)的時(shí)候,lock指向的互斥鎖不能在被鎖狀態(tài)。儲(chǔ)存互斥鎖的內(nèi)存不被釋放。 返回值--mutex_destroy()在成功執(zhí)行后返回零。其他值意味著錯(cuò)誤。在以下情況發(fā)生時(shí),函數(shù)失敗并返回相關(guān)值。 EINVAL 非法參數(shù) EFAULT mp指向一個(gè)非法地址。 5.static inline int mutex_is_locked(struct mutex *lock)--測(cè)試互斥鎖的狀態(tài) 這個(gè)調(diào)用實(shí)際上編譯成一個(gè)內(nèi)聯(lián)函數(shù)。如果互斥鎖被持有(鎖定),那么就會(huì)返回1;否則,返回0。 五、使用形式: struct mutex mutex; mutex_init(&mutex); ... mutex_lock(&mutex); ... mutex_unlock(&mutex); semaphore (信號(hào)量) Linux內(nèi)核的信號(hào)量在概念和原理上與用戶態(tài)的SystemV的IPC機(jī)制信號(hào)量是一樣的,但是它絕不可能在內(nèi)核之外使用,因此它與SystemV的IPC機(jī)制信號(hào)量毫不相干。 信號(hào)量在創(chuàng)建時(shí)需要設(shè)置一個(gè)初始值,表示同時(shí)可以有幾個(gè)任務(wù)可以訪問(wèn)該信號(hào)量保護(hù)的共享資源,初始值為1就變成互斥鎖(Mutex),即同時(shí)只能有一個(gè)任務(wù)可以訪問(wèn)信號(hào)量保護(hù)的共享資源。一個(gè)任務(wù)要想訪問(wèn)共享資源,首先必須得到信號(hào)量,獲取信號(hào)量的操作將把信號(hào)量的值減1,若當(dāng)前信號(hào)量的值為負(fù)數(shù),表明無(wú)法獲得信號(hào)量,該任務(wù)必須掛起在該信號(hào)量的等待隊(duì)列等待該信號(hào)量可用;若當(dāng)前信號(hào)量的值為非負(fù)數(shù),表示可以獲得信號(hào)量,因而可以立刻訪問(wèn)被該信號(hào)量保護(hù)的共享資源。當(dāng)任務(wù)訪問(wèn)完被信號(hào)量保護(hù)的共享資源后,必須釋放信號(hào)量,釋放信號(hào)量通過(guò)把信號(hào)量的值加1實(shí)現(xiàn),如果信號(hào)量的值為非正數(shù),表明有任務(wù)等待當(dāng)前信號(hào)量,因此它也喚醒所有等待該信號(hào)量的任務(wù)。 信號(hào)量的API有: DECLARE_MUTEX(name) 該宏聲明一個(gè)信號(hào)量name并初始化它的值為0,即聲明一個(gè)互斥鎖。 DECLARE_MUTEX_LOCKED(name) 該宏聲明一個(gè)互斥鎖name,但把它的初始值設(shè)置為0,即鎖在創(chuàng)建時(shí)就處在已鎖狀態(tài)。因此對(duì)于這種鎖,一般是先釋放后獲得。 void sema_init (struct semaphore *sem, int val); 該函用于數(shù)初始化設(shè)置信號(hào)量的初值,它設(shè)置信號(hào)量sem的值為val。 void init_MUTEX (struct semaphore *sem); 該函數(shù)用于初始化一個(gè)互斥鎖,即它把信號(hào)量sem的值設(shè)置為1。 void init_MUTEX_LOCKED (struct semaphore *sem); 該函數(shù)也用于初始化一個(gè)互斥鎖,但它把信號(hào)量sem的值設(shè)置為0,即一開(kāi)始就處在已鎖狀態(tài)。 void down(struct semaphore * sem); 該函數(shù)用于獲得信號(hào)量sem,它會(huì)導(dǎo)致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數(shù)。該函數(shù)將把sem的值減1,如果信號(hào)量sem的值非負(fù),就直接返回,否則調(diào)用者將被掛起,直到別的任務(wù)釋放該信號(hào)量才能繼續(xù)運(yùn)行。 int down_interruptible(struct semaphore * sem); 該函數(shù)功能與down類似,不同之處為,down不會(huì)被信號(hào)(signal)打斷,但down_interruptible能被信號(hào)打斷,因此該函數(shù)有返回值來(lái)區(qū)分是正常返回還是被信號(hào)中斷,如果返回0,表示獲得信號(hào)量正常返回,如果被信號(hào)打斷,返回-EINTR。 int down_trylock(struct semaphore * sem); 該函數(shù)試著獲得信號(hào)量sem,如果能夠立刻獲得,它就獲得該信號(hào)量并返回0,否則,表示不能獲得信號(hào)量sem,返回值為非0值。因此,它不會(huì)導(dǎo)致調(diào)用者睡眠,可以在中斷上下文使用。 void up(struct semaphore * sem); 該函數(shù)釋放信號(hào)量sem,即把sem的值加1,如果sem的值為非正數(shù),表明有任務(wù)等待該信號(hào)量,因此喚醒這些等待者。 信號(hào)量在絕大部分情況下作為互斥鎖使用,下面以console驅(qū)動(dòng)系統(tǒng)為例說(shuō)明信號(hào)量的使用。 在內(nèi)核源碼樹(shù)的kernel/printk.c中,使用宏DECLARE_MUTEX聲明了一個(gè)互斥鎖console_sem,它用于保護(hù)console驅(qū)動(dòng)列表console_drivers以及同步對(duì)整個(gè)console驅(qū)動(dòng)系統(tǒng)的訪問(wèn),其中定義了函數(shù)acquire_console_sem來(lái)獲得互斥鎖console_sem,定義了release_console_sem來(lái)釋放互斥鎖console_sem,定義了函數(shù)try_acquire_console_sem來(lái)盡力得到互斥鎖console_sem。這三個(gè)函數(shù)實(shí)際上是分別對(duì)函數(shù)down,up和down_trylock的簡(jiǎn)單包裝。需要訪問(wèn)console_drivers驅(qū)動(dòng)列表時(shí)就需要使用acquire_console_sem來(lái)保護(hù)console_drivers列表,當(dāng)訪問(wèn)完該列表后,就調(diào)用release_console_sem釋放信號(hào)量console_sem。函數(shù)console_unblank,console_device,console_stop,console_start,register_console和unregister_console都需要訪問(wèn)console_drivers,因此它們都使用函數(shù)對(duì)acquire_console_sem和release_console_sem來(lái)對(duì)console_drivers進(jìn)行保護(hù)。
rw_semaphore (讀寫(xiě)信號(hào)量) 讀寫(xiě)信號(hào)量對(duì)訪問(wèn)者進(jìn)行了細(xì)分,或者為讀者,或者為寫(xiě)者,讀者在保持讀寫(xiě)信號(hào)量期間只能對(duì)該讀寫(xiě)信號(hào)量保護(hù)的共享資源進(jìn)行讀訪問(wèn),如果一個(gè)任務(wù)除了需要讀,可能還需要寫(xiě),那么它必須被歸類為寫(xiě)者,它在對(duì)共享資源訪問(wèn)之前必須先獲得寫(xiě)者身份,寫(xiě)者在發(fā)現(xiàn)自己不需要寫(xiě)訪問(wèn)的情況下可以降級(jí)為讀者。讀寫(xiě)信號(hào)量同時(shí)擁有的讀者數(shù)不受限制,也就說(shuō)可以有任意多個(gè)讀者同時(shí)擁有一個(gè)讀寫(xiě)信號(hào)量。如果一個(gè)讀寫(xiě)信號(hào)量當(dāng)前沒(méi)有被寫(xiě)者擁有并且也沒(méi)有寫(xiě)者等待讀者釋放信號(hào)量,那么任何讀者都可以成功獲得該讀寫(xiě)信號(hào)量;否則,讀者必須被掛起直到寫(xiě)者釋放該信號(hào)量。如果一個(gè)讀寫(xiě)信號(hào)量當(dāng)前沒(méi)有被讀者或?qū)懻邠碛胁⑶乙矝](méi)有寫(xiě)者等待該信號(hào)量,那么一個(gè)寫(xiě)者可以成功獲得該讀寫(xiě)信號(hào)量,否則寫(xiě)者將被掛起,直到?jīng)]有任何訪問(wèn)者。因此,寫(xiě)者是排他性的,獨(dú)占性的。 讀寫(xiě)信號(hào)量有兩種實(shí)現(xiàn),一種是通用的,不依賴于硬件架構(gòu),因此,增加新的架構(gòu)不需要重新實(shí)現(xiàn)它,但缺點(diǎn)是性能低,獲得和釋放讀寫(xiě)信號(hào)量的開(kāi)銷大;另一種是架構(gòu)相關(guān)的,因此性能高,獲取和釋放讀寫(xiě)信號(hào)量的開(kāi)銷小,但增加新的架構(gòu)需要重新實(shí)現(xiàn)。在內(nèi)核配置時(shí),可以通過(guò)選項(xiàng)去控制使用哪一種實(shí)現(xiàn)。 讀寫(xiě)信號(hào)量的相關(guān)API有: DECLARE_RWSEM(name) 該宏聲明一個(gè)讀寫(xiě)信號(hào)量name并對(duì)其進(jìn)行初始化。 void init_rwsem(struct rw_semaphore *sem); 該函數(shù)對(duì)讀寫(xiě)信號(hào)量sem進(jìn)行初始化。 void down_read(struct rw_semaphore *sem); 讀者調(diào)用該函數(shù)來(lái)得到讀寫(xiě)信號(hào)量sem。該函數(shù)會(huì)導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。 int down_read_trylock(struct rw_semaphore *sem); 該函數(shù)類似于down_read,只是它不會(huì)導(dǎo)致調(diào)用者睡眠。它盡力得到讀寫(xiě)信號(hào)量sem,如果能夠立即得到,它就得到該讀寫(xiě)信號(hào)量,并且返回1,否則表示不能立刻得到該信號(hào)量,返回0。因此,它也可以在中斷上下文使用。 void down_write(struct rw_semaphore *sem); 寫(xiě)者使用該函數(shù)來(lái)得到讀寫(xiě)信號(hào)量sem,它也會(huì)導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。 int down_write_trylock(struct rw_semaphore *sem); 該函數(shù)類似于down_write,只是它不會(huì)導(dǎo)致調(diào)用者睡眠。該函數(shù)盡力得到讀寫(xiě)信號(hào)量,如果能夠立刻獲得,就獲得該讀寫(xiě)信號(hào)量并且返回1,否則表示無(wú)法立刻獲得,返回0。它可以在中斷上下文使用。 void up_read(struct rw_semaphore *sem); 讀者使用該函數(shù)釋放讀寫(xiě)信號(hào)量sem。它與down_read或down_read_trylock配對(duì)使用。如果down_read_trylock返回0,不需要調(diào)用up_read來(lái)釋放讀寫(xiě)信號(hào)量,因?yàn)楦揪蜎](méi)有獲得信號(hào)量。 void up_write(struct rw_semaphore *sem); 寫(xiě)者調(diào)用該函數(shù)釋放信號(hào)量sem。它與down_write或down_write_trylock配對(duì)使用。如果down_write_trylock返回0,不需要調(diào)用up_write,因?yàn)榉祷?表示沒(méi)有獲得該讀寫(xiě)信號(hào)量。 void downgrade_write(struct rw_semaphore *sem); 該函數(shù)用于把寫(xiě)者降級(jí)為讀者,這有時(shí)是必要的。因?yàn)閷?xiě)者是排他性的,因此在寫(xiě)者保持讀寫(xiě)信號(hào)量期間,任何讀者或?qū)懻叨紝o(wú)法訪問(wèn)該讀寫(xiě)信號(hào)量保護(hù)的共享資源,對(duì)于那些當(dāng)前條件下不需要寫(xiě)訪問(wèn)的寫(xiě)者,降級(jí)為讀者將,使得等待訪問(wèn)的讀者能夠立刻訪問(wèn),從而增加了并發(fā)性,提高了效率。 讀寫(xiě)信號(hào)量適于在讀多寫(xiě)少的情況下使用,在linux內(nèi)核中對(duì)進(jìn)程的內(nèi)存映像描述結(jié)構(gòu)的訪問(wèn)就使用了讀寫(xiě)信號(hào)量進(jìn)行保護(hù)。在Linux中,每一個(gè)進(jìn)程都用一個(gè)類型為task_t或structtask_struct的結(jié)構(gòu)來(lái)描述,該結(jié)構(gòu)的類型為structmm_struct的字段mm描述了進(jìn)程的內(nèi)存映像,特別是mm_struct結(jié)構(gòu)的mmap字段維護(hù)了整個(gè)進(jìn)程的內(nèi)存塊列表,該列表將在進(jìn)程生存期間被大量地遍利或修改,因此mm_struct結(jié)構(gòu)就有一個(gè)字段mmap_sem來(lái)對(duì)mmap的訪問(wèn)進(jìn)行保護(hù),mmap_sem就是一個(gè)讀寫(xiě)信號(hào)量,在proc文件系統(tǒng)里有很多進(jìn)程內(nèi)存使用情況的接口,通過(guò)它們能夠查看某一進(jìn)程的內(nèi)存使用情況,命令free、ps和top都是通過(guò)proc來(lái)得到內(nèi)存使用信息的,proc接口就使用down_read和up_read來(lái)讀取進(jìn)程的mmap信息。當(dāng)進(jìn)程動(dòng)態(tài)地分配或釋放內(nèi)存時(shí),需要修改mmap來(lái)反映分配或釋放后的內(nèi)存映像,因此動(dòng)態(tài)內(nèi)存分配或釋放操作需要以寫(xiě)者身份獲得讀寫(xiě)信號(hào)量mmap_sem來(lái)對(duì)mmap進(jìn)行更新。系統(tǒng)調(diào)用brk和munmap就使用了down_write和up_write來(lái)保護(hù)對(duì)mmap的訪問(wèn)。
seqlock(順序鎖) BKL(大內(nèi)核鎖)
大內(nèi)核鎖這個(gè)簡(jiǎn)單且不常用的內(nèi)核加鎖機(jī)制一直是內(nèi)核開(kāi)發(fā)者之間頗具爭(zhēng)議的話題。它在早期linux版本里的廣泛使用,從2.4內(nèi)核開(kāi)始逐漸被各種各樣的自旋鎖替代,可是直到現(xiàn)在還不能完全將它拋棄;它曾經(jīng)使用自旋鎖實(shí)現(xiàn),到了2.6.11版修改為信號(hào)量,可是在2.6.26-rc2又退回到使用自旋鎖的老路上;它甚至引發(fā)了linux的創(chuàng)始人LinusTorvalds和著名的完全公平調(diào)度(CFS)算法的貢獻(xiàn)者IngoMolnar之間的一場(chǎng)爭(zhēng)議。這究竟是怎么回事呢? 1.1 應(yīng)運(yùn)而生,特立獨(dú)行 使用過(guò)自旋鎖或信號(hào)量這些內(nèi)核互斥機(jī)制的人幾乎不會(huì)想到還有大內(nèi)核鎖這個(gè)東西。和自旋鎖或信號(hào)量一樣,大內(nèi)核鎖也是用來(lái)保護(hù)臨界區(qū)資源,避免出現(xiàn)多個(gè)處理器上的進(jìn)程同時(shí)訪問(wèn)同一區(qū)域的。但這把鎖獨(dú)特的地方是,它不象自旋鎖或信號(hào)量一樣可以創(chuàng)建許多實(shí)例或者叫對(duì)象,每個(gè)對(duì)象保護(hù)特定的臨界區(qū)。事實(shí)上整個(gè)內(nèi)核只有一把這樣的鎖,一旦一個(gè)進(jìn)程獲得大內(nèi)核鎖,進(jìn)入了被它保護(hù)的臨界區(qū),不但該臨界區(qū)被鎖住,所有被它保護(hù)的其它臨界區(qū)都將無(wú)法訪問(wèn),直到該進(jìn)程釋放大內(nèi)核鎖。這看似不可思議:一個(gè)進(jìn)程在一個(gè)處理器上操作一個(gè)全局的鏈表,怎么可能導(dǎo)致其它進(jìn)程無(wú)法訪問(wèn)另一個(gè)全局?jǐn)?shù)組呢?使用兩個(gè)自旋鎖,一個(gè)保護(hù)鏈表,另一個(gè)保護(hù)數(shù)組不就解決了嗎?可是如果你使用大內(nèi)核鎖,效果就是這樣的。 大內(nèi)核鎖的產(chǎn)生是有其歷史原因的。早期linux版本對(duì)對(duì)稱多處理(SMP)器的支持非常有限,為了保證可靠性,對(duì)處理器之間的互斥采取了‘寧可錯(cuò)殺三千,不可放過(guò)一個(gè)’的方式:在內(nèi)核入口處安裝一把‘巨大’的鎖,一旦一個(gè)處理器進(jìn)入內(nèi)核態(tài)就立刻上鎖,其它將要進(jìn)入內(nèi)核態(tài)的進(jìn)程只能在門口等待,以此保證每次只有一個(gè)進(jìn)程處于內(nèi)核態(tài)運(yùn)行。這把鎖就是大內(nèi)核鎖。有了大內(nèi)核鎖保護(hù)的系統(tǒng)當(dāng)然可以安全地運(yùn)行在多處理器上:由于同時(shí)只有一個(gè)處理器在運(yùn)行內(nèi)核代碼,內(nèi)核的執(zhí)行本質(zhì)上和單處理器沒(méi)有什么區(qū)別;而多個(gè)處理器同時(shí)運(yùn)行于進(jìn)程的用戶態(tài)也是安全的,因?yàn)槊總€(gè)進(jìn)程有自己獨(dú)立的地址空間。但是這樣粗魯?shù)丶渔i其缺點(diǎn)也是顯而易見(jiàn)的:多處理器對(duì)性能的提示只能體現(xiàn)在用戶態(tài)的并行處理上,而在內(nèi)核態(tài)下還是單線執(zhí)行,完全無(wú)法發(fā)揮多處理器的威力。于是內(nèi)核開(kāi)發(fā)者就開(kāi)始想辦法逐步縮小這把鎖保護(hù)的范圍。實(shí)際上內(nèi)核大部分代碼是多處理器安全的,只有少數(shù)全局資源需要需要在做互斥加以保護(hù),所以沒(méi)必要限制同時(shí)運(yùn)行于內(nèi)核態(tài)處理器的個(gè)數(shù)。所有處理器都可隨時(shí)進(jìn)入內(nèi)核態(tài)運(yùn)行,只要把這些需要保護(hù)的資源一一挑出來(lái),限制同時(shí)訪問(wèn)這些資源的處理器個(gè)數(shù)就可以了。這樣一來(lái),大內(nèi)核鎖從保護(hù)整個(gè)內(nèi)核態(tài)縮小為零散地保護(hù)內(nèi)核態(tài)某些關(guān)鍵片段。這是一個(gè)進(jìn)步,可步伐還不夠大,仍有上面提到的,‘鎖了臥室廚房也沒(méi)法進(jìn)’的毛病。隨著自旋鎖的廣泛應(yīng)用,新的內(nèi)核代碼里已經(jīng)不再有人使用大內(nèi)核鎖了。 1.2 食之無(wú)味,揮之不去 既然已經(jīng)有了替代物,大內(nèi)核鎖應(yīng)該可以‘光榮下崗’了??墒聦?shí)上沒(méi)這么簡(jiǎn)單。如果大內(nèi)核鎖僅僅是‘只有一個(gè)實(shí)例’的自旋鎖,睿智的內(nèi)核開(kāi)發(fā)者早就把它替換掉了:為每一種處于自旋鎖保護(hù)下的資源創(chuàng)建一把自旋鎖,把大內(nèi)核鎖加鎖/解鎖替換成相應(yīng)的自旋鎖的加鎖/解鎖就可以了。但如今的大內(nèi)核鎖就象一個(gè)被寵壞的孩子,內(nèi)核在一些關(guān)鍵點(diǎn)給予了它許多額外關(guān)照,使得大內(nèi)核鎖的替換變得有點(diǎn)煩。下面是IngoMolnar在一封名為 ’kill the BigKernel Lock (BKL)’的郵件里的抱怨: The biggest technical complication is that the BKL is unlike anyother lock: it "self-releases" when schedule() is called.This makes the BKL spinlock very "sticky", "invisible"and viral: it's very easy to add it to a piece of code (evenunknowingly) and you never really know whether it's held or not.PREEMPT_BKL made it even more invisible, because it made its effectseven less visible to ordinary users. 這段話的大意是:最大的技術(shù)難點(diǎn)是大內(nèi)核鎖的與眾不同:它在調(diào)用schedule()時(shí)能夠‘自動(dòng)釋放’。這一點(diǎn)使得大內(nèi)核鎖非常麻煩和隱蔽:它使你能夠非常容易地添加一段代碼而幾乎從不知道它鎖上與否。PREEMPT_BKL選項(xiàng)使得它更加隱蔽,因?yàn)檫@導(dǎo)致它的效果在普通用戶面前更加‘遁形’。 翻譯linux開(kāi)發(fā)者的話比看懂他們寫(xiě)的代碼更難,但有一點(diǎn)很明白:是schedule()函數(shù)里對(duì)于大內(nèi)核鎖的自動(dòng)釋放導(dǎo)致了問(wèn)題的復(fù)雜化。那就看看schedule()里到底對(duì)大內(nèi)核鎖執(zhí)行了什么操作: 1 /* 2 * schedule() is the main scheduler function. 3 */ 4 asmlinkage void __sched schedule(void) 5 { … 19 release_kernel_lock(prev); … 55 context_switch(rq, prev, next); /* unlocks the rq */ … 67 if (unlikely(reacquire_kernel_lock(current) < 0)) { 68 prev = rq->curr; 69 switch_count = &prev->nivcsw; 70 goto need_resched_nonpreemptible; 71 } … code 1.2 1 linux_2.6.34/kernel/sched.c 在第19行release_kernel_lock(prev)函數(shù)釋放當(dāng)前進(jìn)程(prev)所占據(jù)的大內(nèi)核鎖,接著在第55行執(zhí)行進(jìn)程的切換,從當(dāng)前進(jìn)程prev切換到了下一個(gè)進(jìn)程next。context_switch()可以看做一個(gè)超級(jí)函數(shù),調(diào)用它不是去執(zhí)行一段代碼,而是去執(zhí)行另一個(gè)進(jìn)程。系統(tǒng)的多任務(wù)切換就是依靠這個(gè)超級(jí)函數(shù)從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程,從另一個(gè)進(jìn)程再切換下一個(gè)進(jìn)程,如此連續(xù)不斷地輪轉(zhuǎn)。只要被切走的進(jìn)程還處于就緒狀態(tài),總有一天還會(huì)有機(jī)會(huì)調(diào)度回來(lái)繼續(xù)運(yùn)行,效果看起來(lái)就象函數(shù)context_switch()運(yùn)行完畢返回到了schedule()。繼續(xù)運(yùn)行到第67行,調(diào)用函數(shù)reacquire_kernel_lock()。這是和release_kernel_lock()配對(duì)的函數(shù),將前面釋放的大內(nèi)核鎖又重新鎖起來(lái)。If語(yǔ)句測(cè)試為真表示對(duì)大內(nèi)核鎖嘗試加鎖失敗,這時(shí)可以做一些優(yōu)化。正常的加鎖應(yīng)該是‘原地踏步’,在同一個(gè)地方反復(fù)查詢大內(nèi)核鎖的狀態(tài),直到其它進(jìn)程釋放為止。但這樣做會(huì)浪費(fèi)寶貴的處理器時(shí)間,尤其是當(dāng)運(yùn)行隊(duì)列里有進(jìn)程在等待運(yùn)行時(shí)。所以release_lernel_lock()只是做了’try_lock’的工作,即假如沒(méi)人把持大內(nèi)核鎖就把它鎖住,返回0表示成功;假如已經(jīng)被鎖住就立即返回-1表示失敗。一旦失敗就重新執(zhí)行一遍schedule()的主體部分,檢查運(yùn)行隊(duì)列,挑選一個(gè)合適的進(jìn)程運(yùn)行,等到下一次被調(diào)度運(yùn)行時(shí)可能鎖就解開(kāi)了。這樣做利用另一個(gè)進(jìn)程(假如有進(jìn)程在排隊(duì)等候)的運(yùn)行代替了原地死等,提高了處理器利用率。 除了在schedule()中的‘照顧’,大內(nèi)核鎖還有另外的優(yōu)待:在同一進(jìn)程中你可以對(duì)它反復(fù)嵌套加鎖解鎖,只要加鎖個(gè)數(shù)和解鎖個(gè)數(shù)能配上對(duì)就不會(huì)有任何問(wèn)題,這是自旋鎖望塵莫及的,同一進(jìn)程里自旋鎖如果發(fā)生嵌套加鎖就會(huì)死鎖。為此在進(jìn)程控制塊(PCB)中專門為大內(nèi)核鎖開(kāi)辟了加鎖計(jì)數(shù)器,即task_struct中的lock_depth域。該域的初始值為-1,表示進(jìn)程沒(méi)有獲得大內(nèi)核鎖。每次加鎖時(shí)lock_depth都會(huì)加1,再檢查如果lock_depth為0就執(zhí)行真正的加鎖操作,這樣保證在加了一次鎖以后所有嵌套的加鎖操作都會(huì)被忽略,從而避免了死鎖。解鎖過(guò)程正好相反,每次都將lock_depth減1,直到發(fā)現(xiàn)其值變?yōu)?1時(shí)就執(zhí)行真正的解鎖操作。 內(nèi)核對(duì)大內(nèi)核鎖的偏袒導(dǎo)致開(kāi)發(fā)者在鎖住了它,進(jìn)入被它保護(hù)的臨界區(qū)后,執(zhí)行了不該執(zhí)行的代碼卻還無(wú)法察覺(jué)。其一:程序在鎖住臨界區(qū)后必須盡快退出,否則會(huì)阻塞其它將要進(jìn)入臨界區(qū)的進(jìn)程。所以在臨界區(qū)里絕對(duì)不可以調(diào)用schedule()函數(shù),否則一旦發(fā)生進(jìn)程切換何時(shí)能解鎖就變得遙遙無(wú)期。另外在使用自旋鎖保護(hù)的臨界區(qū)中做進(jìn)程切換很容易造成死鎖。比如一個(gè)進(jìn)程鎖住了一把自旋鎖,期間調(diào)用schedule()切換到另一個(gè)進(jìn)程,而這個(gè)進(jìn)程又要獲得這把鎖,這是系統(tǒng)就會(huì)掛死在這個(gè)進(jìn)程等待解鎖的自旋處。這個(gè)問(wèn)題在大內(nèi)核鎖保護(hù)的臨界區(qū)是不存在的,因?yàn)閟chedule()函數(shù)在調(diào)度到新進(jìn)程之前會(huì)自動(dòng)解鎖已經(jīng)獲得的大內(nèi)核鎖;在切回該進(jìn)程時(shí)又會(huì)自動(dòng)將大內(nèi)核鎖鎖住。用戶在鎖住了大內(nèi)核鎖后,幾乎無(wú)法察覺(jué)期間是否用過(guò)schedule()函數(shù)。這一點(diǎn)就是上面IngoMolnar提到的’technicalcomplication’:將大內(nèi)核鎖替換成自旋鎖后,萬(wàn)一在加鎖過(guò)程中調(diào)用了schedule(),會(huì)造成不可預(yù)估的,災(zāi)難性的后果。當(dāng)然作為一個(gè)訓(xùn)練有素的程序員,即使大內(nèi)核鎖放寬了約束條件,也不會(huì)在臨界區(qū)中有意識(shí)地調(diào)用schedule()函數(shù)的??墒侨绻钦{(diào)用陌生模塊的代碼,再高超的程序員也無(wú)法保證其中不會(huì)調(diào)用到該函數(shù)。其二就是上面提到的,在臨界區(qū)中不能再次獲得保護(hù)該臨界區(qū)的鎖,否則會(huì)死鎖??墒怯捎诖髢?nèi)核鎖有加鎖計(jì)數(shù)器的保護(hù),怎樣嵌套也不會(huì)有事。這也是一個(gè)’technicalcomplication’:將大內(nèi)核鎖替換成自旋鎖后,萬(wàn)一發(fā)生了同一把自旋鎖的嵌套加鎖后果也是災(zāi)難性的。同schedule()函數(shù)一樣,訓(xùn)練有素的程序員是不會(huì)有意識(shí)地多次鎖住大內(nèi)核鎖,但在獲得自旋鎖后調(diào)用了陌生模塊的代碼就無(wú)法保證這些模塊中不會(huì)再次使用大內(nèi)核鎖。這種情況在開(kāi)發(fā)大型系統(tǒng)時(shí)非常常見(jiàn):每個(gè)人都很小心地避免自己模塊的死鎖,可誰(shuí)也無(wú)法避免當(dāng)調(diào)用其它模塊時(shí)可能引入的死鎖問(wèn)題。 IngoMolnar還提到了大內(nèi)核鎖的另一弊端:大內(nèi)核鎖沒(méi)有被lockdep所覆蓋。lockdep是linux內(nèi)核的一個(gè)調(diào)試模塊,用來(lái)檢查內(nèi)核互斥機(jī)制尤其是自旋鎖潛在的死鎖問(wèn)題。自旋鎖由于是查詢方式等待,不釋放處理器,比一般的互斥機(jī)制更容易死鎖,故引入lockdep檢查以下幾種情況可能的死鎖(lockdep將有專門的文章詳細(xì)介紹,在此只是簡(jiǎn)單列舉): · 同一個(gè)進(jìn)程遞歸地加鎖同一把鎖; · 一把鎖既在中斷(或中斷下半部)使能的情況下執(zhí)行過(guò)加鎖操作,又在中斷(或中斷下半部)里執(zhí)行過(guò)加鎖操作。這樣該鎖有可能在鎖定時(shí)由于中斷發(fā)生又試圖在同一處理器上加鎖; · 加鎖后導(dǎo)致依賴圖產(chǎn)生成閉環(huán),這是典型的死鎖現(xiàn)象。 由于大內(nèi)核鎖游離于lockdep之外,它自身以及和其它互斥機(jī)制之間的依賴關(guān)系沒(méi)有受到監(jiān)控,可能會(huì)導(dǎo)致死鎖的場(chǎng)景也無(wú)法被記錄下來(lái),使得它的使用越來(lái)越混亂,處于失控狀態(tài)。 如此看來(lái),大內(nèi)核鎖已經(jīng)成了內(nèi)核的雞肋,而且不能與時(shí)俱進(jìn),到了非整改不可的地步??墒菍⒋髢?nèi)核鎖完全從內(nèi)核中移除將要面臨重重挑戰(zhàn),對(duì)于那些散落在‘年久失修’,多年無(wú)人問(wèn)津的代碼里的大內(nèi)核鎖,更是沒(méi)人敢去動(dòng)它們。既然完全移除希望不大,那就想辦法優(yōu)化它也不失為一種權(quán)宜之計(jì)。 1.3 一改再改:無(wú)奈的選擇 早些時(shí)候大內(nèi)核鎖是在自旋鎖的基礎(chǔ)上實(shí)現(xiàn)的。自旋鎖是處理器之間臨界區(qū)互斥常用的機(jī)制。當(dāng)臨界區(qū)非常短暫,比如只改變幾個(gè)變量的值時(shí),自旋鎖是一種簡(jiǎn)單高效的互斥手段。但自旋鎖的缺點(diǎn)是會(huì)增大系統(tǒng)負(fù)荷,因?yàn)樵谧孕却^(guò)程中進(jìn)程依舊占據(jù)處理器,這部分等待時(shí)間是在做無(wú)用功。尤其是使用大內(nèi)核鎖時(shí),一把鎖管所有臨界區(qū),發(fā)生‘碰撞’的機(jī)會(huì)就更大了。另外為了使進(jìn)程能夠盡快全速‘沖’出臨界區(qū),自旋鎖在加鎖的同時(shí)關(guān)閉了內(nèi)核搶占式調(diào)度。因此鎖住自旋鎖就意味著在一個(gè)處理器上制造了一個(gè)調(diào)度‘禁區(qū)’:期間既不被其它進(jìn)程搶占,又不允許調(diào)用schedule()進(jìn)行自主進(jìn)程切換。也就是說(shuō),一旦處理器上某個(gè)進(jìn)程獲得了自旋鎖,該處理器就只能一直運(yùn)行該進(jìn)程,即便有高優(yōu)先級(jí)的實(shí)時(shí)進(jìn)程就緒也只能排隊(duì)等候。調(diào)度禁區(qū)的出現(xiàn)增加了調(diào)度延時(shí),降低了系統(tǒng)實(shí)時(shí)反應(yīng)的速度,這與大家一直努力從事的內(nèi)核實(shí)時(shí)化改造是背道而馳的。于是在2.6.7版本的linux中對(duì)自旋鎖做了徹底改造,放棄了自旋鎖改用信號(hào)量。信號(hào)量沒(méi)有上面提到的兩個(gè)問(wèn)題:在等待信號(hào)量空閑時(shí)進(jìn)程不占用處理器,處于阻塞狀態(tài);在獲得信號(hào)量后內(nèi)核搶占依舊是使能的,不會(huì)出現(xiàn)調(diào)度盲區(qū)。這樣的解決方案應(yīng)該毫無(wú)爭(zhēng)議了。可任何事情都是有利有弊的。信號(hào)量最大的缺陷是太復(fù)雜了,每次阻塞一個(gè)進(jìn)程時(shí)都要產(chǎn)生費(fèi)時(shí)的進(jìn)程上下文切換,信號(hào)量就緒喚醒等待的進(jìn)程時(shí)又有一次上下文切換。除了上下文切換耗時(shí),進(jìn)程切換造成的TLB刷新,cache冷卻等都有較大開(kāi)銷。如果阻塞時(shí)間比較長(zhǎng),達(dá)到毫秒級(jí),這樣的切換是值得的。但是大部分情況下只需在臨界區(qū)入口等候幾十上百個(gè)指令循環(huán)另一個(gè)進(jìn)程就可以交出臨界區(qū),這時(shí)候這種切換就有點(diǎn)牛刀殺雞了。這就好象去醫(yī)院看普通門診,當(dāng)醫(yī)生正在為病人看病時(shí),別的病人在門口等待一會(huì)就會(huì)輪到了,不必留下電話號(hào)碼回家睡覺(jué),直到醫(yī)生空閑了打電話通知再匆匆趕往醫(yī)院。 由于使用信號(hào)量引起的進(jìn)程頻繁切換導(dǎo)致大內(nèi)核鎖在某些情況下出現(xiàn)嚴(yán)重性能問(wèn)題,LinusTorvalds不得不考慮將大內(nèi)核鎖的實(shí)現(xiàn)改回自旋鎖,自然調(diào)度延時(shí)問(wèn)題也會(huì)跟著回來(lái)。這使得以‘延時(shí)迷(latencyjunkie)’自居的IngoMolnar不太高興。但linux還是LinusTorvalds說(shuō)了算,于是在2.6.26-rc2版大內(nèi)核鎖又變成了自旋鎖,直到現(xiàn)在??偟膩?lái)說(shuō)LinusTorvalds的改動(dòng)是有道理的。使用繁瑣,重量級(jí)的信號(hào)量保護(hù)短暫的臨界區(qū)確實(shí)不值得;而且Linux也不是以實(shí)時(shí)性見(jiàn)長(zhǎng)的操作系統(tǒng),不應(yīng)該片面追求實(shí)時(shí)信而犧牲了整體性能。 1.4 日薄西山:謝幕在即 改回自旋鎖并不意味著LinusTorvalds不關(guān)心調(diào)度延時(shí),相反他真正的觀點(diǎn)是有朝一日徹底鏟除大內(nèi)核鎖,這一點(diǎn)他和IngoMolnar是英雄所見(jiàn)略同??墒怯捎阽P除大內(nèi)核鎖的難度和風(fēng)險(xiǎn)巨大,IngoMolnar覺(jué)得‘在當(dāng)前的游戲規(guī)則下解決大內(nèi)核鎖是不現(xiàn)實(shí)的’必須使用新的游戲規(guī)則。他專門建立一個(gè)版本分支叫做kill-the-BLK,在這個(gè)分支上將大內(nèi)核鎖替換為新的互斥機(jī)制,一步一步解決這個(gè)問(wèn)題: · 解決所有已知的,利用到了大內(nèi)核鎖自動(dòng)解鎖機(jī)制的臨界區(qū);也就是說(shuō),消除使用大內(nèi)核鎖的代碼對(duì)自動(dòng)解鎖機(jī)制的依賴,使其更加接近普通的互斥機(jī)制; · 添加許多調(diào)試設(shè)施用來(lái)警告那些在新互斥機(jī)制下不再有效的假設(shè); · 將大內(nèi)核鎖轉(zhuǎn)換為普通的互斥體,并刪除遺留在調(diào)度器里的自動(dòng)解鎖代碼; · 添加lockdep對(duì)它的監(jiān)控; · 極大簡(jiǎn)化大內(nèi)核鎖代碼,最終將它從內(nèi)核里刪除。 這已經(jīng)是兩年前的事情了?,F(xiàn)在這項(xiàng)工作還沒(méi)結(jié)束,還在‘義無(wú)反顧’地向前推進(jìn)。期待著在不遠(yuǎn)的將來(lái)大內(nèi)核鎖這一不和諧的音符徹底淡出linux的內(nèi)核。
rwlock (讀寫(xiě)鎖) 讀寫(xiě)鎖實(shí)際是一種特殊的自旋鎖,它把對(duì)共享資源的訪問(wèn)者劃分成讀者和寫(xiě)者,讀者只對(duì)共享資源進(jìn)行讀訪問(wèn),寫(xiě)者則需要對(duì)共享資源進(jìn)行寫(xiě)操作。這種鎖相對(duì)于自旋鎖而言,能提高并發(fā)性,因?yàn)樵诙嗵幚砥飨到y(tǒng)中,它允許同時(shí)有多個(gè)讀者來(lái)訪問(wèn)共享資源,最大可能的讀者數(shù)為實(shí)際的邏輯CPU數(shù)。寫(xiě)者是排他性的,一個(gè)讀寫(xiě)鎖同時(shí)只能有一個(gè)寫(xiě)者或多個(gè)讀者(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者又有寫(xiě)者。 在讀寫(xiě)鎖保持期間也是搶占失效的。 如果讀寫(xiě)鎖當(dāng)前沒(méi)有讀者,也沒(méi)有寫(xiě)者,那么寫(xiě)者可以立刻獲得讀寫(xiě)鎖,否則它必須自旋在那里,直到?jīng)]有任何寫(xiě)者或讀者。如果讀寫(xiě)鎖沒(méi)有寫(xiě)者,那么讀者可以立即獲得該讀寫(xiě)鎖,否則讀者必須自旋在那里,直到寫(xiě)者釋放該讀寫(xiě)鎖。 讀寫(xiě)鎖的API看上去與自旋鎖很象,只是讀者和寫(xiě)者需要不同的獲得和釋放鎖的API。下面是讀寫(xiě)鎖API清單: rwlock_init(x) 該宏用于動(dòng)態(tài)初始化讀寫(xiě)鎖x。 DEFINE_RWLOCK(x) 該宏聲明一個(gè)讀寫(xiě)鎖并對(duì)其進(jìn)行初始化。它用于靜態(tài)初始化。 RW_LOCK_UNLOCKED 它用于靜態(tài)初始化一個(gè)讀寫(xiě)鎖。 DEFINE_RWLOCK(x)等同于rwlock_tx = RW_LOCK_UNLOCKED read_trylock(lock) 讀者用它來(lái)盡力獲得讀寫(xiě)鎖lock,如果能夠立即獲得讀寫(xiě)鎖,它就獲得鎖并返回真,否則不能獲得鎖,返回假。無(wú)論是否能夠獲得鎖,它都將立即返回,絕不自旋在那里。 write_trylock(lock) 寫(xiě)者用它來(lái)盡力獲得讀寫(xiě)鎖lock,如果能夠立即獲得讀寫(xiě)鎖,它就獲得鎖并返回真,否則不能獲得鎖,返回假。無(wú)論是否能夠獲得鎖,它都將立即返回,絕不自旋在那里。 read_lock(lock) 讀者要訪問(wèn)被讀寫(xiě)鎖lock保護(hù)的共享資源,需要使用該宏來(lái)得到讀寫(xiě)鎖lock。如果能夠立即獲得,它將立即獲得讀寫(xiě)鎖并返回,否則,將自旋在那里,直到獲得該讀寫(xiě)鎖。 write_lock(lock) 寫(xiě)者要想訪問(wèn)被讀寫(xiě)鎖lock保護(hù)的共享資源,需要使用該宏來(lái)得到讀寫(xiě)鎖lock。如果能夠立即獲得,它將立即獲得讀寫(xiě)鎖并返回,否則,將自旋在那里,直到獲得該讀寫(xiě)鎖。 read_lock_irqsave(lock, flags) 讀者也可以使用該宏來(lái)獲得讀寫(xiě)鎖,與read_lock不同的是,該宏還同時(shí)把標(biāo)志寄存器的值保存到了變量flags中,并失效了本地中斷。 write_lock_irqsave(lock, flags) 寫(xiě)者可以用它來(lái)獲得讀寫(xiě)鎖,與write_lock不同的是,該宏還同時(shí)把標(biāo)志寄存器的值保存到了變量flags中,并失效了本地中斷。 read_lock_irq(lock) 讀者也可以用它來(lái)獲得讀寫(xiě)鎖,與read_lock不同的是,該宏還同時(shí)失效了本地中斷。該宏與read_lock_irqsave的不同之處是,它沒(méi)有保存標(biāo)志寄存器。 write_lock_irq(lock) 寫(xiě)者也可以用它來(lái)獲得鎖,與write_lock不同的是,該宏還同時(shí)失效了本地中斷。該宏與write_lock_irqsave的不同之處是,它沒(méi)有保存標(biāo)志寄存器。 read_lock_bh(lock) 讀者也可以用它來(lái)獲得讀寫(xiě)鎖,與與read_lock不同的是,該宏還同時(shí)失效了本地的軟中斷。 write_lock_bh(lock) 寫(xiě)者也可以用它來(lái)獲得讀寫(xiě)鎖,與write_lock不同的是,該宏還同時(shí)失效了本地的軟中斷。 read_unlock(lock) 讀者使用該宏來(lái)釋放讀寫(xiě)鎖lock。它必須與read_lock配對(duì)使用。 write_unlock(lock) 寫(xiě)者使用該宏來(lái)釋放讀寫(xiě)鎖lock。它必須與write_lock配對(duì)使用。 read_unlock_irqrestore(lock, flags) 讀者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與read_unlock不同的是,該宏還同時(shí)把標(biāo)志寄存器的值恢復(fù)為變量flags的值。它必須與read_lock_irqsave配對(duì)使用。 write_unlock_irqrestore(lock, flags) 寫(xiě)者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與write_unlock不同的是,該宏還同時(shí)把標(biāo)志寄存器的值恢復(fù)為變量flags的值,并使能本地中斷。它必須與write_lock_irqsave配對(duì)使用。 read_unlock_irq(lock) 讀者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與read_unlock不同的是,該宏還同時(shí)使能本地中斷。它必須與read_lock_irq配對(duì)使用。 write_unlock_irq(lock) 寫(xiě)者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與write_unlock不同的是,該宏還同時(shí)使能本地中斷。它必須與write_lock_irq配對(duì)使用。 read_unlock_bh(lock) 讀者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與read_unlock不同的是,該宏還同時(shí)使能本地軟中斷。它必須與read_lock_bh配對(duì)使用。 write_unlock_bh(lock) 寫(xiě)者也可以使用該宏來(lái)釋放讀寫(xiě)鎖,與write_unlock不同的是,該宏還同時(shí)使能本地軟中斷。它必須與write_lock_bh配對(duì)使用。 讀寫(xiě)鎖的獲得和釋放鎖的方法也有許多版本,具體用哪個(gè)與自旋鎖一樣,因此參考自旋鎖部分就可以了。只是需要區(qū)分讀者與寫(xiě)者,讀者要用讀者版本,而寫(xiě)者必須用寫(xiě)者版本。
From:http://blog.csdn.net/lucien_cc/article/details/7440225
|
|
來(lái)自: linux_android > 《鎖》