本文詳細(xì)的介紹了Linux內(nèi)核中的同步機(jī)制:原子操作、信號量、讀寫信號量和自旋鎖的API,使用要求以及一些典型示例
一、引言
在現(xiàn)代操作系統(tǒng)里,同一時(shí)間可能有多個(gè)內(nèi)核執(zhí)行流在執(zhí)行,因此內(nèi)核其實(shí)象多進(jìn)程多線程編程一樣也需要一些同步機(jī)制來同步各執(zhí)行單元對共享數(shù)據(jù)的訪問。尤其是在多處理器系統(tǒng)上,更需要一些同步機(jī)制來同步不同處理器上的執(zhí)行單元對共享的數(shù)據(jù)的訪問。
在主流的Linux內(nèi)核中包含了幾乎所有現(xiàn)代的操作系統(tǒng)具有的同步機(jī)制,這些同步機(jī)制包括:原子操作、信號量(semaphore)、讀寫信號量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4內(nèi)核中)、RCU(只包含在2.6內(nèi)核中)和seqlock(只包含在2.6內(nèi)核中)。
二、原子操作
所謂原子操作,就是該操作絕不會在執(zhí)行完畢前被任何其他任務(wù)或事件打斷,也就說,它的最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位,因此這里的原子實(shí)際是使用了物理學(xué)里的物質(zhì)微粒的概念。
原子操作需要硬件的支持,因此是架構(gòu)相關(guān)的,其API和原子類型的定義都定義在內(nèi)核源碼樹的include/asm/atomic.h文件中,它們都使用匯編語言實(shí)現(xiàn),因?yàn)?a href="http://www.1cnz.cn/v/tag/1743/" target="_blank">C語言并不能實(shí)現(xiàn)這樣的操作。
原子操作主要用于實(shí)現(xiàn)資源計(jì)數(shù),很多引用計(jì)數(shù)(refcnt)就是通過原子操作實(shí)現(xiàn)的。原子類型定義如下:
typedef struct { volatile int counter; } atomic_t;
volatile修飾字段告訴gcc不要對該類型的數(shù)據(jù)做優(yōu)化處理,對它的訪問都是對內(nèi)存的訪問,而不是對寄存器的訪問。
原子操作API包括:
atomic_read(atomic_t * v);
該函數(shù)對原子類型的變量進(jìn)行原子讀操作,它返回原子類型的變量v的值。
atomic_set(atomic_t * v, int i);
該函數(shù)設(shè)置原子類型的變量v的值為i。
void atomic_add(int i, atomic_t *v);
該函數(shù)給原子類型的變量v增加值i。
atomic_sub(int i, atomic_t *v);
該函數(shù)從原子類型的變量v中減去i。
int atomic_sub_and_test(int i, atomic_t *v);
該函數(shù)從原子類型的變量v中減去i,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。
void atomic_inc(atomic_t *v);
該函數(shù)對原子類型變量v原子地增加1。
void atomic_dec(atomic_t *v);
該函數(shù)對原子類型的變量v原子地減1。
int atomic_dec_and_test(atomic_t *v);
該函數(shù)對原子類型的變量v原子地減1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。
int atomic_inc_and_test(atomic_t *v);
該函數(shù)對原子類型的變量v原子地增加1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。
int atomic_add_negative(int i, atomic_t *v);
該函數(shù)對原子類型的變量v原子地增加I,并判斷結(jié)果是否為負(fù)數(shù),如果是,返回真,否則返回假。
int atomic_add_return(int i, atomic_t *v);
該函數(shù)對原子類型的變量v原子地增加i,并且返回指向v的指針。
int atomic_sub_return(int i, atomic_t *v);
該函數(shù)從原子類型的變量v中減去i,并且返回指向v的指針。
int atomic_inc_return(atomic_t * v);
該函數(shù)對原子類型的變量v原子地增加1并且返回指向v的指針。
int atomic_dec_return(atomic_t * v);
該函數(shù)對原子類型的變量v原子地減1并且返回指向v的指針。
原子操作通常用于實(shí)現(xiàn)資源的引用計(jì)數(shù),在TCP/IP協(xié)議棧的IP碎片處理中,就使用了引用計(jì)數(shù),碎片隊(duì)列結(jié)構(gòu)struct ipq描述了一個(gè)IP碎片,字段refcnt就是引用計(jì)數(shù)器,它的類型為atomic_t,當(dāng)創(chuàng)建IP碎片時(shí)(在函數(shù)ip_frag_create中),使用atomic_set函數(shù)把它設(shè)置為1,當(dāng)引用該IP碎片時(shí),就使用函數(shù)atomic_inc把引用計(jì)數(shù)加1。
當(dāng)不需要引用該IP碎片時(shí),就使用函數(shù)ipq_put來釋放該IP碎片,ipq_put使用函數(shù)atomic_dec_and_test把引用計(jì)數(shù)減1并判斷引用計(jì)數(shù)是否為0,如果是就釋放IP碎片。函數(shù)ipq_kill把IP碎片從ipq隊(duì)列中刪除,并把該刪除的IP碎片的引用計(jì)數(shù)減1(通過使用函數(shù)atomic_dec實(shí)現(xiàn))。
三、信號量(semaphore)
Linux內(nèi)核的信號量在概念和原理上與用戶態(tài)的System V的IPC機(jī)制信號量是一樣的,但是它絕不可能在內(nèi)核之外使用,因此它與System V的IPC機(jī)制信號量毫不相干。
信號量在創(chuàng)建時(shí)需要設(shè)置一個(gè)初始值,表示同時(shí)可以有幾個(gè)任務(wù)可以訪問該信號量保護(hù)的共享資源,初始值為1就變成互斥鎖(Mutex),即同時(shí)只能有一個(gè)任務(wù)可以訪問信號量保護(hù)的共享資源。
一個(gè)任務(wù)要想訪問共享資源,首先必須得到信號量,獲取信號量的操作將把信號量的值減1,若當(dāng)前信號量的值為負(fù)數(shù),表明無法獲得信號量,該任務(wù)必須掛起在該信號量的等待隊(duì)列等待該信號量可用;若當(dāng)前信號量的值為非負(fù)數(shù),表示可以獲得信號量,因而可以立刻訪問被該信號量保護(hù)的共享資源。
當(dāng)任務(wù)訪問完被信號量保護(hù)的共享資源后,必須釋放信號量,釋放信號量通過把信號量的值加1實(shí)現(xiàn),如果信號量的值為非正數(shù),表明有任務(wù)等待當(dāng)前信號量,因此它也喚醒所有等待該信號量的任務(wù)。
信號量的API有:
DECLARE_MUTEX(name)
該宏聲明一個(gè)信號量name并初始化它的值為0,即聲明一個(gè)互斥鎖。
DECLARE_MUTEX_LOCKED(name)
該宏聲明一個(gè)互斥鎖name,但把它的初始值設(shè)置為0,即鎖在創(chuàng)建時(shí)就處在已鎖狀態(tài)。因此對于這種鎖,一般是先釋放后獲得。
void sema_init (struct semaphore *sem, int val);
該函用于數(shù)初始化設(shè)置信號量的初值,它設(shè)置信號量sem的值為val。
void init_MUTEX (struct semaphore *sem);
該函數(shù)用于初始化一個(gè)互斥鎖,即它把信號量sem的值設(shè)置為1。
void init_MUTEX_LOCKED (struct semaphore *sem);
該函數(shù)也用于初始化一個(gè)互斥鎖,但它把信號量sem的值設(shè)置為0,即一開始就處在已鎖狀態(tài)。
void down(struct semaphore * sem);
該函數(shù)用于獲得信號量sem,它會導(dǎo)致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數(shù)。該函數(shù)將把sem的值減1,如果信號量sem的值非負(fù),就直接返回,否則調(diào)用者將被掛起,直到別的任務(wù)釋放該信號量才能繼續(xù)運(yùn)行。
int down_interruptible(struct semaphore * sem);
該函數(shù)功能與down類似,不同之處為,down不會被信號(signal)打斷,但down_interruptible能被信號打斷,因此該函數(shù)有返回值來區(qū)分是正常返回還是被信號中斷,如果返回0,表示獲得信號量正常返回,如果被信號打斷,返回-EINTR。
int down_trylock(struct semaphore * sem);
該函數(shù)試著獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量并返回0,否則,表示不能獲得信號量sem,返回值為非0值。因此,它不會導(dǎo)致調(diào)用者睡眠,可以在中斷上下文使用。
void up(struct semaphore * sem);
該函數(shù)釋放信號量sem,即把sem的值加1,如果sem的值為非正數(shù),表明有任務(wù)等待該信號量,因此喚醒這些等待者。
信號量在絕大部分情況下作為互斥鎖使用,下面以console驅(qū)動(dòng)系統(tǒng)為例說明信號量的使用。
在內(nèi)核源碼樹的kernel/printk.c中,使用宏DECLARE_MUTEX聲明了一個(gè)互斥鎖console_sem,它用于保護(hù)console驅(qū)動(dòng)列表console_drivers以及同步對整個(gè)console驅(qū)動(dòng)系統(tǒng)的訪問。
其中定義了函數(shù)acquire_console_sem來獲得互斥鎖console_sem,定義了release_console_sem來釋放互斥鎖console_sem,定義了函數(shù)try_acquire_console_sem來盡力得到互斥鎖console_sem。這三個(gè)函數(shù)實(shí)際上是分別對函數(shù)down,up和down_trylock的簡單包裝。
需要訪問console_drivers驅(qū)動(dòng)列表時(shí)就需要使用acquire_console_sem來保護(hù)console_drivers列表,當(dāng)訪問完該列表后,就調(diào)用release_console_sem釋放信號量console_sem。
函數(shù)console_unblank,console_device,console_stop,console_start,register_console和unregister_console都需要訪問console_drivers,因此它們都使用函數(shù)對acquire_console_sem和release_console_sem來對console_drivers進(jìn)行保護(hù)。
四、讀寫信號量(rw_semaphore)
讀寫信號量對訪問者進(jìn)行了細(xì)分,或者為讀者,或者為寫者,讀者在保持讀寫信號量期間只能對該讀寫信號量保護(hù)的共享資源進(jìn)行讀訪問,如果一個(gè)任務(wù)除了需要讀,可能還需要寫,那么它必須被歸類為寫者,它在對共享資源訪問之前必須先獲得寫者身份,寫者在發(fā)現(xiàn)自己不需要寫訪問的情況下可以降級為讀者。讀寫信號量同時(shí)擁有的讀者數(shù)不受限制,也就說可以有任意多個(gè)讀者同時(shí)擁有一個(gè)讀寫信號量。
如果一個(gè)讀寫信號量當(dāng)前沒有被寫者擁有并且也沒有寫者等待讀者釋放信號量,那么任何讀者都可以成功獲得該讀寫信號量;否則,讀者必須被掛起直到寫者釋放該信號量。如果一個(gè)讀寫信號量當(dāng)前沒有被讀者或?qū)懻邠碛胁⑶乙矝]有寫者等待該信號量,那么一個(gè)寫者可以成功獲得該讀寫信號量,否則寫者將被掛起,直到?jīng)]有任何訪問者。因此,寫者是排他性的,獨(dú)占性的。
讀寫信號量有兩種實(shí)現(xiàn),一種是通用的,不依賴于硬件架構(gòu),因此,增加新的架構(gòu)不需要重新實(shí)現(xiàn)它,但缺點(diǎn)是性能低,獲得和釋放讀寫信號量的開銷大;另一種是架構(gòu)相關(guān)的,因此性能高,獲取和釋放讀寫信號量的開銷小,但增加新的架構(gòu)需要重新實(shí)現(xiàn)。在內(nèi)核配置時(shí),可以通過選項(xiàng)去控制使用哪一種實(shí)現(xiàn)。
讀寫信號量的相關(guān)API有:
DECLARE_RWSEM(name)
該宏聲明一個(gè)讀寫信號量name并對其進(jìn)行初始化。
void init_rwsem(struct rw_semaphore *sem);
該函數(shù)對讀寫信號量sem進(jìn)行初始化。
void down_read(struct rw_semaphore *sem);
讀者調(diào)用該函數(shù)來得到讀寫信號量sem。該函數(shù)會導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。
int down_read_trylock(struct rw_semaphore *sem);
該函數(shù)類似于down_read,只是它不會導(dǎo)致調(diào)用者睡眠。它盡力得到讀寫信號量sem,如果能夠立即得到,它就得到該讀寫信號量,并且返回1,否則表示不能立刻得到該信號量,返回0。因此,它也可以在中斷上下文使用。
void down_write(struct rw_semaphore *sem);
寫者使用該函數(shù)來得到讀寫信號量sem,它也會導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。
int down_write_trylock(struct rw_semaphore *sem);
該函數(shù)類似于down_write,只是它不會導(dǎo)致調(diào)用者睡眠。該函數(shù)盡力得到讀寫信號量,如果能夠立刻獲得,就獲得該讀寫信號量并且返回1,否則表示無法立刻獲得,返回0。它可以在中斷上下文使用。
void up_read(struct rw_semaphore *sem);
讀者使用該函數(shù)釋放讀寫信號量sem。它與down_read或down_read_trylock配對使用。如果down_read_trylock返回0,不需要調(diào)用up_read來釋放讀寫信號量,因?yàn)楦揪蜎]有獲得信號量。
void up_write(struct rw_semaphore *sem);
寫者調(diào)用該函數(shù)釋放信號量sem。它與down_write或down_write_trylock配對使用。如果down_write_trylock返回0,不需要調(diào)用up_write,因?yàn)榉祷?表示沒有獲得該讀寫信號量。
void downgrade_write(struct rw_semaphore *sem);
該函數(shù)用于把寫者降級為讀者,這有時(shí)是必要的。因?yàn)閷懻呤桥潘缘?,因此在寫者保持讀寫信號量期間,任何讀者或?qū)懻叨紝o法訪問該讀寫信號量保護(hù)的共享資源,對于那些當(dāng)前條件下不需要寫訪問的寫者,降級為讀者將,使得等待訪問的讀者能夠立刻訪問,從而增加了并發(fā)性,提高了效率。
讀寫信號量適于在讀多寫少的情況下使用,在linux內(nèi)核中對進(jìn)程的內(nèi)存映像描述結(jié)構(gòu)的訪問就使用了讀寫信號量進(jìn)行保護(hù)。
在Linux中,每一個(gè)進(jìn)程都用一個(gè)類型為task_t或struct task_struct的結(jié)構(gòu)來描述,該結(jié)構(gòu)的類型為struct mm_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來對mmap的訪問進(jìn)行保護(hù),mmap_sem就是一個(gè)讀寫信號量,在proc文件系統(tǒng)里有很多進(jìn)程內(nèi)存使用情況的接口,通過它們能夠查看某一進(jìn)程的內(nèi)存使用情況,命令free、ps和top都是通過proc來得到內(nèi)存使用信息的,proc接口就使用down_read和up_read來讀取進(jìn)程的mmap信息。
當(dāng)進(jìn)程動(dòng)態(tài)地分配或釋放內(nèi)存時(shí),需要修改mmap來反映分配或釋放后的內(nèi)存映像,因此動(dòng)態(tài)內(nèi)存分配或釋放操作需要以寫者身份獲得讀寫信號量mmap_sem來對mmap進(jìn)行更新。系統(tǒng)調(diào)用brk和munmap就使用了down_write和up_write來保護(hù)對mmap的訪問。
五、自旋鎖(spinlock)
自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。
由于自旋鎖使用者一般保持鎖時(shí)間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖。
信號量和讀寫信號量適合于保持時(shí)間較長的情況,它們會導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合于保持時(shí)間非常短的情況,它可以在任何上下文使用。
如果被保護(hù)的共享資源只在進(jìn)程上下文訪問,使用信號量保護(hù)該共享資源非常合適,如果對共巷資源的訪問時(shí)間非常短,自旋鎖也可以。但是如果被保護(hù)的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。
自旋鎖保持期間是搶占失效的,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內(nèi)核可搶占或SMP的情況下才真正需要,在單CPU且不可搶占的內(nèi)核下,自旋鎖的所有操作都是空操作。
跟互斥鎖一樣,一個(gè)執(zhí)行單元要想訪問被自旋鎖保護(hù)的共享資源,必須先得到鎖,在訪問完共享資源后,必須釋放鎖。如果在獲取自旋鎖時(shí),沒有任何執(zhí)行單元保持該鎖,那么將立即得到鎖;如果在獲取自旋鎖時(shí)鎖已經(jīng)有保持者,那么獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。
無論是互斥鎖,還是自旋鎖,在任何時(shí)刻,最多只能有一個(gè)保持者,也就說,在任何時(shí)刻最多只能有一個(gè)執(zhí)行單元獲得鎖。
自旋鎖的API有:
spin_lock_init(x)
該宏用于初始化自旋鎖x。自旋鎖在真正使用前必須先初始化。該宏用于動(dòng)態(tài)初始化。
DEFINE_SPINLOCK(x)
該宏聲明一個(gè)自旋鎖x并初始化它。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒有該宏。
SPIN_LOCK_UNLOCKED
該宏用于靜態(tài)初始化一個(gè)自旋鎖。
DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKEDspin_is_locked(x)
該宏用于判斷自旋鎖x是否已經(jīng)被某執(zhí)行單元保持(即被鎖),如果是,返回真,否則返回假。
spin_unlock_wait(x)
該宏用于等待自旋鎖x變得沒有被任何執(zhí)行單元保持,如果沒有任何執(zhí)行單元保持該自旋鎖,該宏立即返回,否則將循環(huán)在那里,直到該自旋鎖被保持者釋放。
spin_trylock(lock)
該宏盡力獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖并返回真,否則不能立即獲得鎖,立即返回假。它不會自旋等待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配對使用。如果spin_trylock返回假,表明沒有獲得自旋鎖,因此不必使用spin_unlock釋放。
spin_unlock_irqrestore(lock, flags)
該宏釋放自旋鎖lock的同時(shí),也恢復(fù)標(biāo)志寄存器的值為變量flags保存的值。它與spin_lock_irqsave配對使用。
spin_unlock_irq(lock)
該宏釋放自旋鎖lock的同時(shí),也使能本地中斷。它與spin_lock_irq配對應(yīng)用。
spin_unlock_bh(lock)
該宏釋放自旋鎖lock的同時(shí),也使能本地的軟中斷。它與spin_lock_bh配對使用。
spin_trylock_irqsave(lock, flags)
該宏如果獲得自旋鎖lock,它也將保存標(biāo)志寄存器的值到變量flags中,并且失效本地中斷,如果沒有獲得鎖,它什么也不做。
因此如果能夠立即獲得鎖,它等同于spin_lock_irqsave,如果不能獲得鎖,它等同于spin_trylock。如果該宏獲得自旋鎖lock,那需要使用spin_unlock_irqrestore來釋放。
spin_trylock_irq(lock)
該宏類似于spin_trylock_irqsave,只是該宏不保存標(biāo)志寄存器。如果該宏獲得自旋鎖lock,需要使用spin_unlock_irq來釋放。
spin_trylock_bh(lock)
該宏如果獲得了自旋鎖,它也將失效本地軟中斷。如果得不到鎖,它什么也不做。因此,如果得到了鎖,它等同于spin_lock_bh,如果得不到鎖,它等同于spin_trylock。如果該宏得到了自旋鎖,需要使用spin_unlock_bh來釋放。
spin_can_lock(lock)
該宏用于判斷自旋鎖lock是否能夠被鎖,它實(shí)際是spin_is_locked取反。如果lock沒有被鎖,它返回真,否則,返回假。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒有該宏。
獲得自旋鎖和釋放自旋鎖有好幾個(gè)版本,因此讓讀者知道在什么樣的情況下使用什么版本的獲得和釋放鎖的宏是非常必要的。
如果被保護(hù)的共享資源只在進(jìn)程上下文訪問和軟中斷上下文訪問,那么當(dāng)在進(jìn)程上下文訪問共享資源時(shí),可能被軟中斷打斷,從而可能進(jìn)入軟中斷上下文來對被保護(hù)的共享資源訪問,因此對于這種情況,對共享資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護(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上下文訪問,那么應(yīng)該使用與上面情況相同的獲得和釋放鎖的宏,因?yàn)閠asklet和timer是用軟中斷實(shí)現(xiàn)的。
如果被保護(hù)的共享資源只在一個(gè)tasklet或timer上下文訪問,那么不需要任何自旋鎖保護(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上下文訪問,那么對共享資源的訪問僅需要用spin_lock和spin_unlock來保護(hù),不必使用_bh版本,因?yàn)楫?dāng)tasklet或timer運(yùn)行時(shí),不可能有其他tasklet或timer在當(dāng)前CPU上運(yùn)行。
如果被保護(hù)的共享資源只在一個(gè)軟中斷(tasklet和timer除外)上下文訪問,那么這個(gè)共享資源需要用spin_lock和spin_unlock來保護(hù),因?yàn)橥瑯拥能浿袛嗫梢酝瑫r(shí)在不同的CPU上運(yùn)行。
如果被保護(hù)的共享資源在兩個(gè)或多個(gè)軟中斷上下文訪問,那么這個(gè)共享資源當(dāng)然更需要用spin_lock和spin_unlock來保護(hù),不同的軟中斷能夠同時(shí)在不同的CPU上運(yùn)行。
如果被保護(hù)的共享資源在軟中斷(包括tasklet和timer)或進(jìn)程上下文和硬中斷上下文訪問,那么在軟中斷或進(jìn)程上下文訪問期間,可能被硬中斷打斷,從而進(jìn)入硬中斷上下文對共享資源進(jìn)行訪問,因此,在進(jìn)程或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護(hù)對共享資源的訪問。
而在中斷處理句柄中使用什么版本,需依情況而定,如果只有一個(gè)中斷處理句柄訪問該共享資源,那么在中斷處理句柄中僅需要spin_lock和spin_unlock來保護(hù)對共享資源的訪問就可以了。
因?yàn)樵趫?zhí)行中斷處理句柄期間,不可能被同一CPU上的軟中斷或進(jìn)程打斷。但是如果有不同的中斷處理句柄訪問該共享資源,那么需要在中斷處理句柄中使用spin_lock_irq和spin_unlock_irq來保護(hù)對共享資源的訪問。
在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應(yīng)該使用哪一個(gè)也需要依情況而定,如果可以確信在對共享資源訪問前中斷是使能的,那么使用spin_lock_irq更好一些。
因?yàn)樗萻pin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因?yàn)樗鼘⒒謴?fù)訪問共享資源前的中斷標(biāo)志而不是直接使能中斷。
當(dāng)然,有些情況下需要在訪問共享資源時(shí)必須中斷失效,而訪問完后必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。
需要特別提醒讀者,spin_lock用于阻止在不同CPU上的執(zhí)行單元對共享資源的同時(shí)訪問以及不同進(jìn)程上下文互相搶占導(dǎo)致的對共享資源的非同步訪問,而中斷失效和軟中斷失效卻是為了阻止在同一CPU上軟中斷或中斷對共享資源的非同步訪問。
?
評論
查看更多