0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)核中有哪些鎖

Wildesbeast ? 來源:今日頭條 ? 作者:青峰科技 ? 2020-02-24 15:26 ? 次閱讀

Linux內(nèi)核中的各種鎖

在LInux操作系統(tǒng)里,同一時間可能有多個內(nèi)核執(zhí)行流在執(zhí)行,因此內(nèi)核其實象多進程多線程編程一樣也需要一些同步機制來同步各執(zhí)行單元對共享數(shù)據(jù)的訪問。尤其是在多處理器系統(tǒng)上,更需要一些同步機制來同步不同處理器上的執(zhí)行單元對共享的數(shù)據(jù)的訪問。

linux內(nèi)核

在主流的Linux內(nèi)核中包含了幾乎所有現(xiàn)代的操作系統(tǒng)具有的同步機制,這些同步機制包括:原子操作、信號量(semaphore)、讀寫信號量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock ,RCU(在開發(fā)內(nèi)核2.5.43中引入該技術(shù)的并正式包含在2.6內(nèi)核中)和seqlock(只包含在2.6以后內(nèi)核中)。

首先明確鎖的引入不可避免的引起性能的損失,研究表明隨著計算機硬件的快速發(fā)展,獲得這種鎖的開銷相對于CPU的速度在成倍地增加,原因很簡單,CPU的速度與訪問內(nèi)存的速度差距越來越大,而這種鎖使用了原子操作指令,它需要原子地訪問內(nèi)存,也就說獲得鎖的開銷與訪存速度相關(guān),另外在大部分非x86架構(gòu)上獲取鎖使用了內(nèi)存柵(Memory Barrier),這會導致處理器流水線停滯或刷新,因此它的開銷相對于CPU速度而言就影響

越來越大。

因此對于以上每種鎖都要明確其特定的應用場合,使用不當反而會影響性能甚至錯誤。

原子操作

原子操作簡介

什么是原子性,就是不可再分,該操作絕不會在執(zhí)行完畢前被任何其他任務或事件打斷,也就說,它的最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位,因此這里的原子實際是使用了物理學里的物質(zhì)微粒的概念。

原子操作需要硬件的支持,因此是架構(gòu)相關(guān)的,其API和原子類型的定義都定義在內(nèi)核源碼樹的include/asm/atomic.h文件中,它們都使用匯編語言實現(xiàn),因為C語言并不能實現(xiàn)這樣的操作。

原子操作主要用于實現(xiàn)資源計數(shù),很多引用計數(shù)(refcnt)就是通過原子操作實現(xiàn)的。

原子類型定義如下:

typedef struct { volatile int counter; } atomic_t;1

volatile修飾字段告訴gcc不要對該類型的數(shù)據(jù)做優(yōu)化處理,對它的訪問都是對內(nèi)存的訪問,而不是對寄存器的訪問。

api如下:

atomic_read(atomic_t * v);1

該函數(shù)對原子類型的變量進行原子讀操作,它返回原子類型的變量v的值。

atomic_set(atomic_t * v, int i);1

該函數(shù)設(shè)置原子類型的變量v的值為i。

void atomic_add(int i, atomic_t *v);1

該函數(shù)給原子類型的變量v增加值i。

atomic_sub(int i, atomic_t *v);1

該函數(shù)從原子類型的變量v中減去i。

int atomic_sub_and_test(int i, atomic_t *v);1

該函數(shù)從原子類型的變量v中減去i,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。

void atomic_inc(atomic_t *v);1

該函數(shù)對原子類型變量v原子地增加1。

void atomic_dec(atomic_t *v);1

該函數(shù)對原子類型的變量v原子地減1。

int atomic_dec_and_test(atomic_t *v);1

該函數(shù)對原子類型的變量v原子地減1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。

int atomic_inc_and_test(atomic_t *v);1

該函數(shù)對原子類型的變量v原子地增加1,并判斷結(jié)果是否為0,如果為0,返回真,否則返回假。

int atomic_add_negative(int i, atomic_t *v);1

該函數(shù)對原子類型的變量v原子地增加i,并判斷結(jié)果是否為負數(shù),如果是,返回真,否則返回假。

int atomic_add_return(int i, atomic_t *v);1

該函數(shù)對原子類型的變量v原子地增加i,并且返回指向v的指針。

int atomic_sub_return(int i, atomic_t *v);1

該函數(shù)從原子類型的變量v中減去i,并且返回指向v的指針。

int atomic_inc_return(atomic_t * v);1

該函數(shù)對原子類型的變量v原子地增加1并且返回指向v的指針。

int atomic_dec_return(atomic_t * v);1

該函數(shù)對原子類型的變量v原子地減1并且返回指向v的指針。

原子操作通常用于實現(xiàn)資源的引用計數(shù),在TCP/IP協(xié)議棧的IP碎片處理中,就使用了引用計數(shù),碎片隊列結(jié)構(gòu)struct ipq描述了一個IP碎片,字段refcnt就是引用計數(shù)器,它的類型為atomic_t,當創(chuàng)建IP碎片時(在函數(shù)ip_frag_create中),使用atomic_set函數(shù)把它設(shè)置為1,當引用該IP碎片時,就使用函數(shù)atomic_inc把引用計數(shù)加1,當不需要引用該IP碎片時,就使用函數(shù)ipq_put來釋放該IP碎片,ipq_put使用函數(shù)atomic_dec_and_test把引用計數(shù)減1并判斷引用計數(shù)是否為0,如果是就釋放IP碎片。函數(shù)ipq_kill把IP碎片從ipq隊列中刪除,并把該刪除的IP碎片的引用計數(shù)減1(通過使用函數(shù)atomic_dec實現(xiàn))。

信號量(semaphore)

信號量簡介

Linux內(nèi)核的信號量在概念和原理上與用戶態(tài)的System V的IPC機制信號量是一樣的,但是它絕不可能在內(nèi)核之外使用,因此它與System V的IPC機制信號量毫不相干。

信號量在創(chuàng)建時需要設(shè)置一個初始值,表示同時可以有幾個任務可以訪問該信號量保護的共享資源,初始值為1就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問信號量保護的共享資源。一個任務要想訪問共享資源,首先必須得到信號量,獲取信號量的操作將把信號量的值減1,若當前信號量的值為負數(shù),表明無法獲得信號量,該任務必須掛起在該信號量的等待隊列等待該信號量可用;若當前信號量的值為非負數(shù),表示可以獲得信號量,因而可以立刻訪問被該信號量保護的共享資源。當任務訪問完被信號量保護的共享資源后,必須釋放信號量,釋放信號量通過把信號量的值加1實現(xiàn),如果信號量的值為非正數(shù),表明有任務等待當前信號量,因此它也喚醒所有等待該信號量的任務。

信號量api介紹

DEFINE_MUTEX1

靜態(tài)定義和初始化一個互斥鎖. 1

void mutex_init(struct mutex *mutex);1

動態(tài)初始化一個互斥鎖

void sema_init (struct semaphore *sem, int val);1

該函用于數(shù)初始化設(shè)置信號量的初值,它設(shè)置信號量sem的值為val。

void down(struct semaphore * sem);1

該函數(shù)用于獲得信號量sem,它會導致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數(shù)。該函數(shù)首先判斷sem->count的值是否大于0,如果true則sem->count–,否者調(diào)用者將被掛起,直到別的任務釋放該信號量才能繼續(xù)運行。

int down_interruptible(struct semaphore * sem);1

該函數(shù)功能與down類似,不同之處為,down不會被信號(signal)打斷,但down_interruptible能被信號打斷,因此該函數(shù)有返回值來區(qū)分是正常返回還是被信號中斷,如果返回0,表示獲得信號量正常返回,如果被信號打斷,返回-EINTR。

int down_trylock(struct semaphore * sem);1

該函數(shù)試著獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量并返回0,否則,表示不能獲得信號量sem,返回值為非0值。因此,它不會導致調(diào)用者睡眠,可以在中斷上下文使用。

void up(struct semaphore * sem);1

該函數(shù)釋放信號量sem,即把sem的值加1,如果sem的值為非正數(shù),表明有任務等待該信號量,因此喚醒這些等待者。

信號量在絕大部分情況下作為互斥鎖使用,下面以console驅(qū)動系統(tǒng)為例說明信號量的使用。

在內(nèi)核源碼樹的kernel/printk.c中,使用宏DECLARE_MUTEX聲明了一個互斥鎖console_sem,它用于保護console驅(qū)動列表console_drivers以及同步對整個console驅(qū)動系統(tǒng)的訪問,其中定義了函數(shù)acquire_console_sem來獲得互斥鎖console_sem,定義了release_console_sem來釋放互斥鎖console_sem,定義了函數(shù)try_acquire_console_sem來盡力得到互斥鎖console_sem。這三個函數(shù)實際上是分別對函數(shù)down,up和down_trylock的簡單包裝。需要訪問console_drivers驅(qū)動列表時就需要使用acquire_console_sem來保護console_drivers列表,當訪問完該列表后,就調(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進行保護。

讀寫信號量(rw_semaphore)

讀寫信號量簡介

讀寫信號量對訪問者進行了細分,或者為讀者,或者為寫者,讀者在保持讀寫信號量期間只能對該讀寫信號量保護的共享資源進行讀訪問,如果一個任務除了需要讀,可能還需要寫,那么它必須被歸類為寫者,它在對共享資源訪問之前必須先獲得寫者身份,寫者在發(fā)現(xiàn)自己不需要寫訪問的情況下可以降級為讀者。讀寫信號量的訪問規(guī)則:

讀寫信號量同時擁有的讀者數(shù)不受限制,也就說可以有任意多個讀者同時擁有一個讀寫信號量。

如果一個讀寫信號量當前沒有被寫者擁有并且也沒有寫者等待讀者釋放信號量,那么任何讀者都可以成功獲得該讀寫信號量;否則,讀者必須被掛起直到寫者釋放該信號量。

如果一個讀寫信號量當前沒有被讀者或?qū)懻邠碛胁⑶乙矝]有寫者等待該信號量,那么一個寫者可以成功獲得該讀寫信號量,否則寫者將被掛起,直到?jīng)]有任何訪問者。

因此,寫者是排他性的,獨占性的。

讀寫信號量有兩種實現(xiàn),一種是通用的,不依賴于硬件架構(gòu),因此,增加新的架構(gòu)不需要重新實現(xiàn)它,但缺點是性能低,獲得和釋放讀寫信號量的開銷大;另一種是架構(gòu)相關(guān)的,因此性能高,獲取和釋放讀寫信號量的開銷小,但增加新的架構(gòu)需要重新實現(xiàn)。在內(nèi)核配置時,可以通過選項去控制使用哪一種實現(xiàn)。

讀寫信號量api介紹

DECLARE_RWSEM(name)1

該宏聲明一個讀寫信號量name并對其進行初始化。

void init_rwsem(struct rw_semaphore *sem);1

該函數(shù)對讀寫信號量sem進行初始化。

void down_read(struct rw_semaphore *sem);1

讀者調(diào)用該函數(shù)來得到讀寫信號量sem。該函數(shù)會導致調(diào)用者睡眠,因此只能在進程上下文使用。

int down_read_trylock(struct rw_semaphore *sem);1

該函數(shù)類似于down_read,只是它不會導致調(diào)用者睡眠。它盡力得到讀寫信號量sem,如果能夠立即得到,它就得到該讀寫信號量,并且返回1,否則表示不能立刻得到該信號量,返回0。因此,它也可以在中斷上下文使用。

void down_write(struct rw_semaphore *sem);1

寫者使用該函數(shù)來得到讀寫信號量sem,它也會導致調(diào)用者睡眠,因此只能在進程上下文使用。

int down_write_trylock(struct rw_semaphore *sem);1

該函數(shù)類似于down_write,只是它不會導致調(diào)用者睡眠。該函數(shù)盡力得到讀寫信號量,如果能夠立刻獲得,就獲得該讀寫信號量并且返回1,否則表示無法立刻獲得,返回0。它可以在中斷上下文使用。

void up_read(struct rw_semaphore *sem);1

讀者使用該函數(shù)釋放讀寫信號量sem。它與down_read或down_read_trylock配對使用。如果down_read_trylock返回0,不需要調(diào)用up_read來釋放讀寫信號量,因為根本就沒有獲得信號量。

void up_write(struct rw_semaphore *sem);1

寫者調(diào)用該函數(shù)釋放信號量sem。它與down_write或down_write_trylock配對使用。如果down_write_trylock返回0,不需要調(diào)用up_write,因為返回0表示沒有獲得該讀寫信號量。

void downgrade_write(struct rw_semaphore *sem);1

該函數(shù)用于把寫者降級為讀者,這有時是必要的。因為寫者是排他性的,因此在寫者保持讀寫信號量期間,任何讀者或?qū)懻叨紝o法訪問該讀寫信號量保護的共享資源,對于那些當前條件下不需要寫訪問的寫者,降級為讀者將使得等待訪問的讀者能夠立刻訪問,從而增加了并發(fā)性,提高了效率。

讀寫信號量適于在讀多寫少的情況下使用,在linux內(nèi)核中對進程的內(nèi)存映像描述結(jié)構(gòu)的訪問就使用了讀寫信號量進行保護。在Linux中,每一個進程都用一個類型為task_t或struct task_struct的結(jié)構(gòu)來描述,該結(jié)構(gòu)的類型為struct mm_struct的字段mm描述了進程的內(nèi)存映像,特別是mm_struct結(jié)構(gòu)的mmap字段維護了整個進程的內(nèi)存塊列表,該列表將在進程生存期間被大量地遍利或修改,因此mm_struct結(jié)構(gòu)就有一個字段mmap_sem來對mmap的訪問進行保護,mmap_sem就是一個讀寫信號量,在proc文件系統(tǒng)里有很多進程內(nèi)存使用情況的接口,通過它們能夠查看某一進程的內(nèi)存使用情況,命令free、ps和top都是通過proc來得到內(nèi)存使用信息的,proc接口就使用down_read和up_read來讀取進程的mmap信息。當進程動態(tài)地分配或釋放內(nèi)存時,需要修改mmap來反映分配或釋放后的內(nèi)存映像,因此動態(tài)內(nèi)存分配或釋放操作需要以寫者身份獲得讀寫信號量mmap_sem來對mmap進行更新。系統(tǒng)調(diào)用brk和munmap就使用了down_write和up_write來保護對mmap的訪問。

自旋鎖(spinlock)

自旋鎖簡介

自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,”自旋”一詞就是因此而得名。由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。

信號量和讀寫信號量適合于保持時間較長的情況,它們會導致調(diào)用者睡眠,因此只能在進程上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合于保持時間非常短的情況,它可以在任何上下文使用。如果被保護的共享資源只在進程上下文訪問,使用信號量保護該共享資源非常合適,如果對共巷資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。

自旋鎖保持期間是搶占失效的,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內(nèi)核可搶占或SMP的情況下才真正需要,在單CPU且不可搶占的內(nèi)核下,自旋鎖的所有操作都是空操作,在單CPU且可搶占的內(nèi)核下,自旋鎖實際上只進行開啟和關(guān)閉內(nèi)核搶占的操作。

跟互斥鎖一樣,一個執(zhí)行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源后,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執(zhí)行單元保持該鎖,那么將立即得到鎖;如果在獲取自旋鎖時鎖已經(jīng)有保持者,那么獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。

無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執(zhí)行單元獲得鎖。

自旋鎖的API

spin_lock_init(x)1

該宏用于初始化自旋鎖x。自旋鎖在真正使用前必須先初始化。該宏用于動態(tài)初始化。

DEFINE_SPINLOCK(x)1

該宏聲明一個自旋鎖x并初始化它。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒有該宏。

SPIN_LOCK_UNLOCKED1

該宏用于靜態(tài)初始化一個自旋鎖。

DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKED

spin_is_locked(x)1

該宏用于判斷自旋鎖x是否已經(jīng)被某執(zhí)行單元保持(即被鎖),如果是,返回真,否則返回假。

spin_unlock_wait(x)1

該宏用于等待自旋鎖x變得沒有被任何執(zhí)行單元保持,如果沒有任何執(zhí)行單元保持該自旋鎖,該宏立即返回,否則將循環(huán)在那里,直到該自旋鎖被保持者釋放。

spin_trylock(lock)1

該宏盡力獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖并返回真,否則不能立即獲得鎖,立即返回假。它不會自旋等待lock被釋放。

spin_lock(lock)1

該宏用于獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那里,直到該自旋鎖的保持者釋放,這時,它獲得鎖并返回。總之,只有它獲得鎖才返回。

spin_lock_irqsave(lock, flags)1

該宏獲得自旋鎖的同時把標志寄存器的值保存到變量flags中并失效本地中斷。

spin_lock_irq(lock)1

該宏類似于spin_lock_irqsave,只是該宏不保存標志寄存器的值。

spin_lock_bh(lock)1

該宏在得到自旋鎖的同時失效本地軟中斷。

spin_unlock(lock)1

該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用。如果spin_trylock返回假,表明沒有獲得自旋鎖,因此不必使用spin_unlock釋放。

spin_unlock_irqrestore(lock, flags)1

該宏釋放自旋鎖lock的同時,也恢復標志寄存器的值為變量flags保存的值。它與spin_lock_irqsave配對使用。

spin_unlock_irq(lock)1

該宏釋放自旋鎖lock的同時,也使能本地中斷。它與spin_lock_irq配對應用。

spin_unlock_bh(lock)1

該宏釋放自旋鎖lock的同時,也使能本地的軟中斷。它與spin_lock_bh配對使用。

spin_trylock_irqsave(lock, flags)1

該宏如果獲得自旋鎖lock,它也將保存標志寄存器的值到變量flags中,并且失效本地中斷,如果沒有獲得鎖,它什么也不做。因此如果能夠立即獲得鎖,它等同于spin_lock_irqsave,如果不能獲得鎖,它等同于spin_trylock。如果該宏獲得自旋鎖lock,那需要使用spin_unlock_irqrestore來釋放。

spin_trylock_irq(lock)1

該宏類似于spin_trylock_irqsave,只是該宏不保存標志寄存器。如果該宏獲得自旋鎖lock,需要使用spin_unlock_irq來釋放。

spin_trylock_bh(lock)1

該宏如果獲得了自旋鎖,它也將失效本地軟中斷。如果得不到鎖,它什么也不做。因此,如果得到了鎖,它等同于spin_lock_bh,如果得不到鎖,它等同于spin_trylock。如果該宏得到了自旋鎖,需要使用spin_unlock_bh來釋放。

spin_can_lock(lock)1

該宏用于判斷自旋鎖lock是否能夠被鎖,它實際是spin_is_locked取反。如果lock沒有被鎖,它返回真,否則,返回假。該宏在2.6.11中第一次被定義,在先前的內(nèi)核中并沒有該宏。

獲得自旋鎖和釋放自旋鎖有好幾個版本,因此讓讀者知道在什么樣的情況下使用什么版本的獲得和釋放鎖的宏是非常必要的。

被保護的共享資源只在進程上下文訪問和軟中斷上下文訪問

當在進程上下文訪問共享資源時,可能被軟中斷打斷,從而可能進入軟中斷上下文來對被保護的共享資源訪問,因此對于這種情況,對共享資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護。當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當?shù)模绕渌麅蓚€快。

舉例說明:spinlock用在進程上下文和中斷

進程A中調(diào)用了spin_lock(&lock)然后進入臨界區(qū),此時來了一個中斷(interrupt),該中斷也運行在和進程A相同的CPU上,并且在該中斷處理程序中恰巧也會spin_lock(&lock)試圖獲取同一個鎖。由于是在同一個CPU上被中斷,進程A會被設(shè)置為TASK_INTERRUPT狀態(tài),

中斷處理程序無法獲得鎖,會不停的忙等,由于進程A被設(shè)置為中斷狀態(tài),schedule()進程調(diào)度就無法再調(diào)度進程A運行,這樣就導致了死鎖!但是如果該中斷處理程序運行在不同的CPU上就不會觸發(fā)死鎖。因為在不同的CPU上出現(xiàn)中斷不會導致進程A的狀態(tài)被設(shè)為TASK_INTERRUPT,只是換出。當中斷處理程序忙等被換出后,進程A還是有機會獲得CPU,執(zhí)行并退出臨界區(qū),(有一個問題沒有搞懂關(guān)中斷是關(guān)閉對所有cpu的中斷還是本地cpu的?從關(guān)閉中斷的函數(shù)來看似乎是針對本地的),所以在使用spin_lock時要明確知道該鎖不會在中斷處理程序中使用。如果有,那就需要使用spinlock_irq_save,該函數(shù)即會關(guān)搶占,也會關(guān)本地中斷(因為不能保證打斷A的中斷和A不是一個cpu,因此spin_lock在多核cpu上使用還是要關(guān)中斷)。

被保護的共享資源只在進程上下文和tasklet或timer上下文訪問

應該使用與上面情況相同的獲得和釋放鎖的宏,因為tasklet和timer是用軟中斷實現(xiàn)的。

被保護的共享資源只在一個tasklet或timer上下文訪問

不需要任何自旋鎖保護,因為同一個tasklet或timer只能在一個CPU上運行,即使是在SMP環(huán)境下也是如此。實際上tasklet在調(diào)用tasklet_schedule標記其需要被調(diào)度時已經(jīng)把該tasklet綁定到當前CPU,因此同一個tasklet決不可能同時在其他CPU上運行。timer也是在其被使用add_timer添加到timer隊列中時已經(jīng)被幫定到當前CPU,所以同一個timer絕不可能運行在其他CPU上。當然同一個tasklet有兩個實例同時運行在同一個CPU就更不可能了。

被保護的共享資源只在兩個或多個tasklet或timer上下文訪問

對共享資源的訪問僅需要用spin_lock和spin_unlock來保護,不必使用_bh版本,因為當tasklet或timer運行時,不可能有其他tasklet或timer在當前CPU上運行。 如果被保護的共享資源只在一個軟中斷(tasklet和timer除外)上下文訪問,那么這個共享資源需要用spin_lock和spin_unlock來保護,因為同樣的軟中斷可以同時在不同的CPU上運行。

被保護的共享資源在兩個或多個軟中斷上下文訪問

這個共享資源當然更需要用spin_lock和spin_unlock來保護,不同的軟中斷能夠同時在不同的CPU上運行。

被保護的共享資源在軟中斷(包括tasklet和timer)或進程上下文和硬中斷上下文訪問

在軟中斷或進程上下文訪問期間,可能被硬中斷打斷,從而進入硬中斷上下文對共享資源進行訪問,因此,在進程或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。而在中斷處理句柄中使用什么版本,需依情況而定,如果只有一個中斷處理句柄訪問該共享資源,那么在中斷處理句柄中僅需要spin_lock和spin_unlock來保護對共享資源的訪問就可以了。因為在執(zhí)行中斷處理句柄期間,不可能被同一CPU上的軟中斷或進程打斷。但是如果有不同的中斷處理句柄訪問該共享資源,那么需要在中斷處理句柄中使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。

在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應該使用哪一個也需要依情況而定,如果可以確信在對共享資源訪問前中斷是使能的,那么使用spin_lock_irq更好一些,因為它比spin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因為它將恢復訪問共享資源前的中斷標志而不是直接使能中斷。當然,有些情況下需要在訪問共享資源時必須中斷失效,而訪問完后必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。

需要特別提醒讀者,spin_lock用于阻止在不同CPU上的執(zhí)行單元對共享資源的同時訪問以及不同進程上下文互相搶占導致的對共享資源的非同步訪問,而中斷失效和軟中斷失效卻是為了阻止在同一CPU上軟中斷或中斷對共享資源的非同步訪問。

對于spin_lock用于阻止不同CPU上的執(zhí)行單元對共享資源的同時訪問以及不同進程上下文互相搶占導致的對共享資源的非同步訪問在單核和多核cpu上的實現(xiàn)是不同的:

單核cpu

如果spin_lock不處于中斷上下文,則spin_lock鎖定的代碼只會在內(nèi)核發(fā)生搶占2的時候才會丟失CPU擁有權(quán)。所以,對于單核來說,需要在spin_lock獲得鎖的時候禁止搶占,釋放鎖的時候開放搶占。因此這不是真正意義上的鎖。

內(nèi)核代碼實現(xiàn)如下:

static inline void spin_lock(spinlock_t *lock){raw_spin_lock(&lock->rlock);}#define raw_spin_lock(lock) _raw_spin_lock(lock)#define _raw_spin_lock(lock) __LOCK(lock)#define __LOCK(lock) do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)可以看到僅禁止了內(nèi)核搶占static inline void spin_unlock(spinlock_t *lock){raw_spin_unlock(&lock->rlock);}#define raw_spin_unlock(lock) _raw_spin_unlock(lock)#define _raw_spin_unlock(lock) __UNLOCK(lock)#define __UNLOCK(lock) do { preempt_enable(); __release(lock); (void)(lock); } while (0)

開啟內(nèi)核搶占

多核

存在臨界區(qū)同時在多核上被執(zhí)行的情況,這時候才需要一個真正的鎖來宣告代碼對資源的占有。幾個核可能會同時access臨界區(qū),這時的spinlock是如何實現(xiàn)的呢?

要用到CPU提供的一些特殊指令,對lock變量進行原子操作。

SMP中spin_lock的實現(xiàn)

實現(xiàn)在include/linux/spinlock_api_smp.h

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);}

SMP上的實現(xiàn)被分解為三句話。

preempt_disable() 關(guān)搶占

spin_acquire()是sparse檢查需要

LOCK_CONTENDED()是一個宏,如果不考慮CONFIG_LOCK_STAT(該宏是為了統(tǒng)計lock的操作),則:

#define LOCK_CONTENDED(_lock, try, lock) lock(_lock)

則第三句話等同于:

do_raw_spin_lock(lock)

而do_raw_spin_lock()則可以從spinlock.h中找到痕跡:

static inline intdo_raw_spin_trylock(raw_spinlock_t *lock){ return arch_spin_trylock(&(lock)->raw_lock);}

看到arch,我們明白這個函數(shù)是體系結(jié)構(gòu)相關(guān)的。這部分代碼使用匯編實現(xiàn)。(具體例子后續(xù)再加)

大內(nèi)核鎖(BKL–Big Kernel Lock)

大內(nèi)核鎖簡介

大內(nèi)核鎖本質(zhì)上也是自旋鎖,但是它又不同于自旋鎖,自旋鎖是不可以遞歸獲得鎖的,因為那樣會導致死鎖。但大內(nèi)核鎖可以遞歸獲得鎖。大內(nèi)核鎖用于保護整個內(nèi)核,而自旋鎖用于保護非常特定的某一共享資源。進程保持大內(nèi)核鎖時可以發(fā)生調(diào)度,具體實現(xiàn)是:在執(zhí)行schedule時,schedule將檢查進程是否擁有大內(nèi)核鎖,如果有,它將被釋放,以致于其它的進程能夠獲得該鎖,而當輪到該進程運行時,再讓它重新獲得大內(nèi)核鎖。注意在保持自旋鎖期間是不允許發(fā)生調(diào)度的。

需要特別指出,整個內(nèi)核只有一個大內(nèi)核鎖,其實不難理解,內(nèi)核只有一個,而大內(nèi)核鎖是保護整個內(nèi)核的,當然有且只有一個就足夠了。

還需要特別指出的是,大內(nèi)核鎖是歷史遺留,內(nèi)核中用的非常少,一般保持該鎖的時間較長,因此不提倡使用它。從2.6.11內(nèi)核起,大內(nèi)核鎖可以通過配置內(nèi)核使其變得可搶占(自旋鎖是不可搶占的),這時它實質(zhì)上是一個互斥鎖,使用信號量實現(xiàn)。

大內(nèi)核鎖的API

void lock_kernel(void);1

該函數(shù)用于得到大內(nèi)核鎖。它可以遞歸調(diào)用而不會導致死鎖。

void unlock_kernel(void);1

該函數(shù)用于釋放大內(nèi)核鎖。當然必須與lock_kernel配對使用,調(diào)用了多少次lock_kernel,就需要調(diào)用多少次unlock_kernel。

大內(nèi)核鎖的API使用非常簡單,按照以下方式使用就可以了:

lock_kernel();

//對被保護的共享資源的訪問

unlock_kernel();1234

(六)讀寫鎖(rwlock)

讀寫鎖簡介

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對于自旋鎖而言,能提高并發(fā)性,因為在多處理器系統(tǒng)中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數(shù)為實際的邏輯CPU數(shù)。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數(shù)相關(guān)),但不能同時既有讀者又有寫者。

在讀寫鎖保持期間也是搶占失效的。

讀寫鎖訪問規(guī)則:

如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里,直到?jīng)]有任何寫者或讀者。

如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里,直到寫者釋放該讀寫鎖。

讀寫鎖API

讀寫鎖的API看上去與自旋鎖很象,只是讀者和寫者需要不同的獲得和釋放鎖的API。

rwlock_init(x)1

該宏用于動態(tài)初始化讀寫鎖x。

DEFINE_RWLOCK(x)1

該宏聲明一個讀寫鎖并對其進行初始化。它用于靜態(tài)初始化。

RW_LOCK_UNLOCKED1

它用于靜態(tài)初始化一個讀寫鎖。

DEFINE_RWLOCK(x)等同于rwlock_t x = RW_LOCK_UNLOCKED

read_trylock(lock)1

讀者用它來盡力獲得讀寫鎖lock,如果能夠立即獲得讀寫鎖,它就獲得鎖并返回真,否則不能獲得鎖,返回假。無論是否能夠獲得鎖,它都將立即返回,絕不自旋在那里。

write_trylock(lock)1

寫者用它來盡力獲得讀寫鎖lock,如果能夠立即獲得讀寫鎖,它就獲得鎖并返回真,否則不能獲得鎖,返回假。無論是否能夠獲得鎖,它都將立即返回,絕不自旋在那里。

read_lock(lock)1

讀者要訪問被讀寫鎖lock保護的共享資源,需要使用該宏來得到讀寫鎖lock。如果能夠立即獲得,它將立即獲得讀寫鎖并返回,否則,將自旋在那里,直到獲得該讀寫鎖。

write_lock(lock)1

寫者要想訪問被讀寫鎖lock保護的共享資源,需要使用該宏來得到讀寫鎖lock。如果能夠立即獲得,它將立即獲得讀寫鎖并返回,否則,將自旋在那里,直到獲得該讀寫鎖。

read_lock_irqsave(lock, flags)1

讀者也可以使用該宏來獲得讀寫鎖,與read_lock不同的是,該宏還同時把標志寄存器的值保存到了變量flags中,并失效了本地中斷。

write_lock_irqsave(lock, flags)1

寫者可以用它來獲得讀寫鎖,與write_lock不同的是,該宏還同時把標志寄存器的值保存到了變量flags中,并失效了本地中斷。

read_lock_irq(lock)1

讀者也可以用它來獲得讀寫鎖,與read_lock不同的是,該宏還同時失效了本地中斷。該宏與read_lock_irqsave的不同之處是,它沒有保存標志寄存器。

write_lock_irq(lock)1

寫者也可以用它來獲得鎖,與write_lock不同的是,該宏還同時失效了本地中斷。該宏與write_lock_irqsave的不同之處是,它沒有保存標志寄存器。

read_lock_bh(lock)1

讀者也可以用它來獲得讀寫鎖,與與read_lock不同的是,該宏還同時失效了本地的軟中斷。

write_lock_bh(lock)1

寫者也可以用它來獲得讀寫鎖,與write_lock不同的是,該宏還同時失效了本地的軟中斷。

read_unlock(lock)1

讀者使用該宏來釋放讀寫鎖lock。它必須與read_lock配對使用。

write_unlock(lock)1

寫者使用該宏來釋放讀寫鎖lock。它必須與write_lock配對使用。

read_unlock_irqrestore(lock, flags)1

讀者也可以使用該宏來釋放讀寫鎖,與read_unlock不同的是,該宏還同時把標志寄存器的值恢復為變量flags的值。它必須與read_lock_irqsave配對使用。

write_unlock_irqrestore(lock, flags)1

寫者也可以使用該宏來釋放讀寫鎖,與write_unlock不同的是,該宏還同時把標志寄存器的值恢復為變量flags的值,并使能本地中斷。它必須與write_lock_irqsave配對使用。

read_unlock_irq(lock)1

讀者也可以使用該宏來釋放讀寫鎖,與read_unlock不同的是,該宏還同時使能本地中斷。它必須與read_lock_irq配對使用。

write_unlock_irq(lock)1

寫者也可以使用該宏來釋放讀寫鎖,與write_unlock不同的是,該宏還同時使能本地中斷。它必須與write_lock_irq配對使用。

read_unlock_bh(lock)1

讀者也可以使用該宏來釋放讀寫鎖,與read_unlock不同的是,該宏還同時使能本地軟中斷。它必須與read_lock_bh配對使用。

write_unlock_bh(lock)1

寫者也可以使用該宏來釋放讀寫鎖,與write_unlock不同的是,該宏還同時使能本地軟中斷。它必須與write_lock_bh配對使用。

讀寫鎖的獲得和釋放鎖的方法也有許多版本,具體用哪個與自旋鎖一樣,因此參考自旋鎖部分就可以了。只是需要區(qū)分讀者與寫者,讀者要用讀者版本,而寫者必須用寫者版本。

RCU(Read-Copy Update)

RCU簡介

RCU(Read-Copy Update),顧名思義就是讀-拷貝修改,它是基于其原理命名的。對于被RCU保護的共享數(shù)據(jù)結(jié)構(gòu),讀者不需要獲得任何鎖就可以訪問它,但寫者在訪問它時首先拷貝一個副本,然后對副本進行修改,最后使用一個回調(diào)(callback)機制在適當?shù)臅r機把指向原來數(shù)據(jù)的指針重新指向新的被修改的數(shù)據(jù)。這個時機就是所有引用該數(shù)據(jù)的CPU都退出對共享數(shù)據(jù)的操作。

因此RCU實際上是一種改進的rwlock,讀者幾乎沒有什么同步開銷,它不需要鎖,不使用原子指令,而且在除alpha的所有架構(gòu)上也不需要內(nèi)存柵(Memory Barrier),因此不會導致鎖競爭,內(nèi)存延遲以及流水線停滯。不需要鎖也使得使用更容易,因為死鎖問題就不需要考慮了。寫者的同步開銷比較大,它需要延遲數(shù)據(jù)結(jié)構(gòu)的釋放,復制被修改的數(shù)據(jù)結(jié)構(gòu),它也必須使用某種鎖機制同步并行的其它寫者的修改操作。讀者必須提供一個信號給寫者以便寫者能夠確定數(shù)據(jù)可以被安全地釋放或修改的時機。有一個專門的垃圾收集器來探測讀者的信號,一旦所有的讀者都已經(jīng)發(fā)送信號告知它們都不在使用被RCU保護的數(shù)據(jù)結(jié)構(gòu),垃圾收集器就調(diào)用回調(diào)函數(shù)完成最后的數(shù)據(jù)釋放或修改操作。 RCU與rwlock的不同之處是:它既允許多個讀者同時訪問被保護的數(shù)據(jù),又允許多個讀者和多個寫者同時訪問被保護的數(shù)據(jù)(注意:是否可以有多個寫者并行訪問取決于寫者之間使用的同步機制),讀者沒有任何同步開銷,而寫者的同步開銷則取決于使用的寫者間同步機制。但RCU不能替代rwlock,因為如果寫比較多時,對讀者的性能提高不能彌補寫者導致的損失。

讀者在訪問被RCU保護的共享數(shù)據(jù)期間不能被阻塞,這是RCU機制得以實現(xiàn)的一個基本前提,也就說當讀者在引用被RCU保護的共享數(shù)據(jù)期間,讀者所在的CPU不能發(fā)生上下文切換,spinlock和rwlock都需要這樣的前提。寫者在訪問被RCU保護的共享數(shù)據(jù)時不需要和讀者競爭任何鎖,只有在有多于一個寫者的情況下需要獲得某種鎖以與其他寫者同步。寫者修改數(shù)據(jù)前首先拷貝一個被修改元素的副本,然后在副本上進行修改,修改完畢后它向垃圾回收器注冊一個回調(diào)函數(shù)以便在適當?shù)臅r機執(zhí)行真正的修改操作。等待適當時機的這一時期稱為寬限期(grace period),而CPU發(fā)生了上下文切換稱為經(jīng)歷一個quiescent state,grace period就是所有CPU都經(jīng)歷一次quiescent state所需要的等待的時間。垃圾收集器就是在grace period之后調(diào)用寫者注冊的回調(diào)函數(shù)來完成真正的數(shù)據(jù)修改或數(shù)據(jù)釋放操作的。

以下以鏈表元素刪除為例詳細說明這一過程。

寫者要從鏈表中刪除元素 B,它首先遍歷該鏈表得到指向元素 B 的指針,然后修改元素 B 的前一個元素的 next 指針指向元素 B 的 next 指針指向的元素C,修改元素 B 的 next 指針指向的元素 C 的 prep 指針指向元素 B 的 prep指針指向的元素 A,在這期間可能有讀者訪問該鏈表,修改指針指向的操作是原子的,所以不需要同步,而元素 B 的指針并沒有去修改,因為讀者可能正在使用 B 元素來得到下一個或前一個元素。寫者完成這些操作后注冊一個回調(diào)函數(shù)以便在 grace period 之后刪除元素 B,然后就認為已經(jīng)完成刪除操作。垃圾收集器在檢測到所有的CPU不在引用該鏈表后,即所有的 CPU 已經(jīng)經(jīng)歷了 quiescent state,grace period 已經(jīng)過去后,就調(diào)用剛才寫者注冊的回調(diào)函數(shù)刪除了元素 B。

使用 RCU 進行鏈表刪除操作

RCU的API

rcu_read_lock()1

讀者在讀取由RCU保護的共享數(shù)據(jù)時使用該函數(shù)標記它進入讀端臨界區(qū)。

rcu_read_unlock()1

該函數(shù)與rcu_read_lock配對使用,用以標記讀者退出讀端臨界區(qū)。

夾在這兩個函數(shù)之間的代碼區(qū)稱為”讀端臨界區(qū)”(read-side critical section)。讀端臨界區(qū)可以嵌套,如圖3,臨界區(qū)2被嵌套在臨界區(qū)1內(nèi)。

嵌套讀端臨界區(qū)示例

那么在讀端臨界區(qū)發(fā)生了什么?要回答這個問題需要搞清楚rcu_read_lock和rcu_read_unlock做了什么操作,實際上即關(guān)閉內(nèi)核搶占和打開內(nèi)核搶占

static inline void __rcu_read_lock(void)

{

preempt_disable();

}

static inline void __rcu_read_unlock(void)

{preempt_enable();}

即在讀端臨界區(qū)中時內(nèi)核是禁止搶占的。

那么這時是否度過寬限期(Grace Period)的判斷就比較簡單:每個CPU都經(jīng)過一次搶占。因為發(fā)生搶占,就說明不在rcu_read_lock和rcu_read_unlock之間,必然已經(jīng)完成訪問或者還未開始訪問。

synchronize_rcu()1

該函數(shù)由RCU寫端調(diào)用,它將阻塞寫者,直到經(jīng)過grace period后,即所有的讀者已經(jīng)完成讀端臨界區(qū),寫者才可以繼續(xù)下一步操作。如果有多個RCU寫端調(diào)用該函數(shù),他們將在一個grace period之后全部被喚醒。注意,該函數(shù)在2.6.11及以前的2.6內(nèi)核版本中為synchronize_kernel,只是在2.6.12才更名為synchronize_rcu,但在2.6.12中也提供了synchronize_kernel和一個新的函數(shù)synchronize_sched,因為以前有很多內(nèi)核開發(fā)者使用synchronize_kernel用于等待所有CPU都退出不可搶占區(qū),而在RCU設(shè)計時該函數(shù)只是用于等待所有CPU都退出讀端臨界區(qū),它可能會隨著RCU實現(xiàn)的修改而發(fā)生語意變化,因此為了預先防止這種情況發(fā)生,在新的修改中增加了專門的用于其它內(nèi)核用戶的synchronize_sched函數(shù)和只用于RCU使用的synchronize_rcu,現(xiàn)在建議非RCU內(nèi)核代碼部分不使用synchronize_kernel而使用synchronize_sched,RCU代碼部分則使用synchronize_rcu,synchronize_kernel之所以存在是為了保證代碼兼容性。

synchronize_kernel()1

其他非RCU的內(nèi)核代碼使用該函數(shù)來等待所有CPU處在可搶占狀態(tài),目前功能等同于synchronize_rcu,但現(xiàn)在已經(jīng)不建議使用,而使用synchronize_sched。

synchronize_sched()1

該函數(shù)用于等待所有CPU都處在可搶占狀態(tài),它能保證正在運行的中斷處理函數(shù)處理完畢,但不能保證正在運行的softirq處理完畢。注意,synchronize_rcu只保證所有CPU都處理完正在運行的讀端臨界區(qū)。

void fastcall call_rcu(struct rcu_head *head,

void (*func)(struct rcu_head *rcu))

struct rcu_head {

struct rcu_head *next;

void (*func)(struct rcu_head *head);

};123456

函數(shù)call_rcu也由RCU寫端調(diào)用,它不會使寫者阻塞,因而可以在中斷上下文或softirq使用。該函數(shù)將把函數(shù)func掛接到RCU回調(diào)函數(shù)鏈上,然后立即返回。一旦所有的CPU都已經(jīng)完成讀端臨界區(qū)操作,該函數(shù)將被調(diào)用來釋放刪除的將絕不在被應用的數(shù)據(jù)。參數(shù)head用于記錄回調(diào)函數(shù)func,一般該結(jié)構(gòu)會作為被RCU保護的數(shù)據(jù)結(jié)構(gòu)的一個字段,以便省去單獨為該結(jié)構(gòu)分配內(nèi)存的操作。需要指出的是,函數(shù)synchronize_rcu的實現(xiàn)實際上使用函數(shù)call_rcu。

void fastcall call_rcu_bh(struct rcu_head *head,

void (*func)(struct rcu_head *rcu))12

函數(shù)call_ruc_bh功能幾乎與call_rcu完全相同,唯一差別就是它把softirq的完成也當作經(jīng)歷一個quiescent state,因此如果寫端使用了該函數(shù),在進程上下文的讀端必須使用rcu_read_lock_bh。

#define rcu_dereference(p) ({

typeof(p) _________p1 = p;

smp_read_barrier_depends();

(_________p1);

})12345

該宏用于在RCU讀端臨界區(qū)獲得一個RCU保護的指針,該指針可以在以后安全地引用,內(nèi)存柵只在alpha架構(gòu)上才使用。

除了這些API,RCU還增加了鏈表操作的RCU版本,因為對于RCU,對共享數(shù)據(jù)的操作必須保證能夠被沒有使用同步機制的讀者看到,所以內(nèi)存柵是非常必要的。

static inline void list_add_rcu(struct list_head *new, struct list_head *head)1

該函數(shù)把鏈表項new插入到RCU保護的鏈表head的開頭。使用內(nèi)存柵保證了在引用這個新插入的鏈表項之前,新鏈表項的鏈接指針的修改對所有讀者是可見的。

static inline void list_add_tail_rcu(struct list_head *new,

struct list_head *head)12

該函數(shù)類似于list_add_rcu,它將把新的鏈表項new添加到被RCU保護的鏈表的末尾。

static inline void list_del_rcu(struct list_head *entry)1

該函數(shù)從RCU保護的鏈表中移走指定的鏈表項entry,并且把entry的prev指針設(shè)置為LIST_POISON2,但是并沒有把entry的next指針設(shè)置為LIST_POISON1,因為該指針可能仍然在被讀者用于便利該鏈表。

static inline void list_replace_rcu(struct list_head *old, struct list_head *new)1

該函數(shù)是RCU新添加的函數(shù),并不存在非RCU版本。它使用新的鏈表項new取代舊的鏈表項old,內(nèi)存柵保證在引用新的鏈表項之前,它的鏈接指針的修正對所有讀者可見。

list_for_each_rcu(pos, head)1

該宏用于遍歷由RCU保護的鏈表head,只要在讀端臨界區(qū)使用該函數(shù),它就可以安全地和其它_rcu鏈表操作函數(shù)(如list_add_rcu)并發(fā)運行。

list_for_each_safe_rcu(pos, n, head)1

該宏類似于list_for_each_rcu,但不同之處在于它允許安全地刪除當前鏈表項pos。

list_for_each_entry_rcu(pos, head, member)1

該宏類似于list_for_each_rcu,不同之處在于它用于遍歷指定類型的數(shù)據(jù)結(jié)構(gòu)鏈表,當前鏈表項pos為一包含struct list_head結(jié)構(gòu)的特定的數(shù)據(jù)結(jié)構(gòu)。

list_for_each_continue_rcu(pos, head)1

該宏用于在退出點之后繼續(xù)遍歷由RCU保護的鏈表head。

static inline void hlist_del_rcu(struct hlist_node *n)1

它從由RCU保護的哈希鏈表中移走鏈表項n,并設(shè)置n的ppre指針為LIST_POISON2,但并沒有設(shè)置next為LIST_POISON1,因為該指針可能被讀者使用用于遍利鏈表。

static inline void hlist_add_head_rcu(struct hlist_node *n,

struct hlist_head *h)12

該函數(shù)用于把鏈表項n插入到被RCU保護的哈希鏈表的開頭,但同時允許讀者對該哈希鏈表的遍歷。內(nèi)存柵確保在引用新鏈表項之前,它的指針修正對所有讀者可見。

hlist_for_each_rcu(pos, head)1

該宏用于遍歷由RCU保護的哈希鏈表head,只要在讀端臨界區(qū)使用該函數(shù),它就可以安全地和其它_rcu哈希鏈表操作函數(shù)(如hlist_add_rcu)并發(fā)運行。

hlist_for_each_entry_rcu(tpos, pos, head, member)1

類似于hlist_for_each_rcu,不同之處在于它用于遍歷指定類型的數(shù)據(jù)結(jié)構(gòu)哈希鏈表,當前鏈表項pos為一包含struct list_head結(jié)構(gòu)的特定的數(shù)據(jù)結(jié)構(gòu)。

下面部分將就 RCU 的幾種典型應用情況詳細講解。

1. 只有增加和刪除的鏈表操作

在這種應用情況下,絕大部分是對鏈表的遍歷,即讀操作,而很少出現(xiàn)的寫操作只有增加或刪除鏈表項,并沒有對鏈表項的修改操作,這種情況使用RCU非常容易,從rwlock轉(zhuǎn)換成RCU非常自然。路由表的維護就是這種情況的典型應用,對路由表的操作,絕大部分是路由表查詢,而對路由表的寫操作也僅僅是增加或刪除,因此使用RCU替換原來的rwlock順理成章。系統(tǒng)調(diào)用審計也是這樣的情況。

這是一段使用rwlock的系統(tǒng)調(diào)用審計部分的讀端代碼:

static enum audit_state audit_filter_task(struct task_struct *tsk)

{

struct audit_entry *e;

enum audit_state state;

read_lock(&auditsc_lock);

/* Note: audit_netlink_sem held by caller. */

list_for_each_entry(e, &audit_tsklist, list) {

if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {

read_unlock(&auditsc_lock);

return state;

}

}

read_unlock(&auditsc_lock);

return AUDIT_BUILD_CONTEXT;

}

使用RCU后將變成:

static enum audit_state audit_filter_task(struct task_struct *tsk)

{

struct audit_entry *e;

enum audit_state state;

rcu_read_lock();

/* Note: audit_netlink_sem held by caller. */

list_for_each_entry_rcu(e, &audit_tsklist, list) {

if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {

rcu_read_unlock();

return state;

}

}

rcu_read_unlock();

return AUDIT_BUILD_CONTEXT;

}

這種轉(zhuǎn)換非常直接,使用rcu_read_lock和rcu_read_unlock分別替換read_lock和read_unlock,鏈表遍歷函數(shù)使用_rcu版本替換就可以了。

使用rwlock的寫端代碼:

static inline int audit_del_rule(struct audit_rule *rule,

struct list_head *list)

{

struct audit_entry *e;

write_lock(&auditsc_lock);

list_for_each_entry(e, list, list) {

if (!audit_compare_rule(rule, &e->rule)) {

list_del(&e->list);

write_unlock(&auditsc_lock);

return 0;

}

}

write_unlock(&auditsc_lock);

return -EFAULT; /* No matching rule */

}

static inline int audit_add_rule(struct audit_entry *entry,

struct list_head *list)

{

write_lock(&auditsc_lock);

if (entry->rule.flags & AUDIT_PREPEND) {

entry->rule.flags &= ~AUDIT_PREPEND;

list_add(&entry->list, list);

} else {

list_add_tail(&entry->list, list);

}

write_unlock(&auditsc_lock);

return 0;

}

使用RCU后寫端代碼變成為

static inline int audit_del_rule(struct audit_rule *rule,

struct list_head *list)

{

struct audit_entry *e;

/* Do not use the _rcu iterator here, since this is the only

* deletion routine. */

list_for_each_entry(e, list, list) {

if (!audit_compare_rule(rule, &e->rule)) {

list_del_rcu(&e->list);

call_rcu(&e->rcu, audit_free_rule, e);

return 0;

}

}

return -EFAULT; /* No matching rule */

}

static inline int audit_add_rule(struct audit_entry *entry,

struct list_head *list)

{

if (entry->rule.flags & AUDIT_PREPEND) {

entry->rule.flags &= ~AUDIT_PREPEND;

list_add_rcu(&entry->list, list);

} else {

list_add_tail_rcu(&entry->list, list);

}

return 0;

}

對于鏈表刪除操作,list_del替換為list_del_rcu和call_rcu,這是因為被刪除的鏈表項可能還在被別的讀者引用,所以不能立即刪除,必須等到所有讀者經(jīng)歷一個quiescent state才可以刪除。另外,list_for_each_entry并沒有被替換為list_for_each_entry_rcu,這是因為,只有一個寫者在做鏈表刪除操作,因此沒有必要使用_rcu版本。

通常情況下,write_lock和write_unlock應當分別替換成spin_lock和spin_unlock,但是對于只是對鏈表進行增加和刪除操作而且只有一個寫者的寫端,在使用了_rcu版本的鏈表操作API后,rwlock可以完全消除,不需要spinlock來同步讀者的訪問。對于上面的例子,由于已經(jīng)有audit_netlink_sem被調(diào)用者保持,所以spinlock就沒有必要了。

這種情況允許修改結(jié)果延后一定時間才可見,而且寫者對鏈表僅僅做增加和刪除操作,所以轉(zhuǎn)換成使用RCU非常容易。

2.寫端需要對鏈表條目進行修改操作

如果寫者需要對鏈表條目進行修改,那么就需要首先拷貝要修改的條目,然后修改條目的拷貝,等修改完畢后,再使用條目拷貝取代要修改的條目,要修改條目將被在經(jīng)歷一個grace period后安全刪除。

對于系統(tǒng)調(diào)用審計代碼,并沒有這種情況。這里假設(shè)有修改的情況,那么使用rwlock的修改代碼應當如下:

static inline int audit_upd_rule(struct audit_rule *rule,

struct list_head *list,

__u32 newaction,

__u32 newfield_count)

{

struct audit_entry *e;

struct audit_newentry *ne;

write_lock(&auditsc_lock);

/* Note: audit_netlink_sem held by caller. */

list_for_each_entry(e, list, list) {

if (!audit_compare_rule(rule, &e->rule)) {

e->rule.action = newaction;

e->rule.file_count = newfield_count;

write_unlock(&auditsc_lock);

return 0;

}

}

write_unlock(&auditsc_lock);

return -EFAULT; /* No matching rule */

}

如果使用RCU,修改代碼應當為;

static inline int audit_upd_rule(struct audit_rule *rule,

struct list_head *list,

__u32 newaction,

__u32 newfield_count)

{

struct audit_entry *e;

struct audit_newentry *ne;

list_for_each_entry(e, list, list) {

if (!audit_compare_rule(rule, &e->rule)) {

ne = kmalloc(sizeof(*entry), GFP_ATOMIC);

if (ne == NULL)

return -ENOMEM;

audit_copy_rule(&ne->rule, &e->rule);

ne->rule.action = newaction;

ne->rule.file_count = newfield_count;

list_replace_rcu(e, ne);

call_rcu(&e->rcu, audit_free_rule, e);

return 0;

}

}

return -EFAULT; /* No matching rule */

}

3.修改操作立即可見

前面兩種情況,讀者能夠容忍修改可以在一段時間后看到,也就說讀者在修改后某一時間段內(nèi),仍然看到的是原來的數(shù)據(jù)。在很多情況下,讀者不能容忍看到舊的數(shù)據(jù),這種情況下,需要使用一些新措施,如System V IPC,它在每一個鏈表條目中增加了一個deleted字段,標記該字段是否刪除,如果刪除了,就設(shè)置為真,否則設(shè)置為假,當代碼在遍歷鏈表時,核對每一個條目的deleted字段,如果為真,就認為它是不存在的。

還是以系統(tǒng)調(diào)用審計代碼為例,如果它不能容忍舊數(shù)據(jù),那么,讀端代碼應該修改為:

static enum audit_state audit_filter_task(struct task_struct *tsk)

{

struct audit_entry *e;

enum audit_state state;

rcu_read_lock();

list_for_each_entry_rcu(e, &audit_tsklist, list) {

if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {

spin_lock(&e->lock);

if (e->deleted) {

spin_unlock(&e->lock);

rcu_read_unlock();

return AUDIT_BUILD_CONTEXT;

}

rcu_read_unlock();

return state;

}

}

rcu_read_unlock();

return AUDIT_BUILD_CONTEXT;

}

注意,對于這種情況,每一個鏈表條目都需要一個spinlock保護,因為刪除操作將修改條目的deleted標志。此外,該函數(shù)如果搜索到條目,返回時應當保持該條目的鎖,因為只有這樣,才能看到新的修改的數(shù)據(jù),否則,仍然可能看到舊的數(shù)據(jù)。

寫端的刪除操作將變成:

static inline int audit_del_rule(struct audit_rule *rule,

struct list_head *list)

{

struct audit_entry *e;

/* Do not use the _rcu iterator here, since this is the only

* deletion routine. */

list_for_each_entry(e, list, list) {

if (!audit_compare_rule(rule, &e->rule)) {

spin_lock(&e->lock);

list_del_rcu(&e->list);

e->deleted = 1;

spin_unlock(&e->lock);

call_rcu(&e->rcu, audit_free_rule, e);

return 0;

}

}

return -EFAULT; /* No matching rule *

刪除條目時,需要標記該條目為已刪除。這樣讀者就可以通過該標志立即得知條目是否已經(jīng)刪除.

RCU是2.6內(nèi)核引入的新的鎖機制,在絕大部分為讀而只有極少部分為寫的情況下,它是非常高效的,因此在路由表維護、系統(tǒng)調(diào)用審計、SELinux的AVC、dcache和IPC等代碼部分中,使用它來取代rwlock來獲得更高的性能。但是,它也有缺點,延后的刪除或釋放將占用一些內(nèi)存,尤其是對嵌入式系統(tǒng),這可能是非常昂貴的內(nèi)存開銷。此外,寫者的開銷比較大,尤其是對于那些無法容忍舊數(shù)據(jù)的情況以及不只一個寫者的情況,寫者需要spinlock或其他的鎖機制來與其他寫者同步。

順序鎖(seqlock)

順序鎖簡介

順序鎖也是對讀寫鎖的一種優(yōu)化,對于順序鎖,讀者絕不會被寫者阻塞,也就說,讀者可以在寫者對被順序鎖保護的共享資源進行寫操作時仍然可以繼續(xù)讀,而不必等待寫者完成寫操作,寫者也不需要等待所有讀者完成讀操作才去進行寫操作。但是,寫者與寫者之間仍然是互斥的,即如果有寫者在進行寫操作,其他寫者必須自旋在那里,直到寫者釋放了順序鎖。

這種鎖有一個限制,它必須要求被保護的共享資源不含有指針,因為寫者可能使得指針失效,但讀者如果正要訪問該指針,將導致OOPs。

如果讀者在讀操作期間,寫者已經(jīng)發(fā)生了寫操作,那么,讀者必須重新讀取數(shù)據(jù),以便確保得到的數(shù)據(jù)是完整的。

這種鎖對于讀寫同時進行的概率比較小的情況,性能是非常好的,而且它允許讀寫同時進行,因而更大地提高了并發(fā)性。

順序鎖的API

void write_seqlock(seqlock_t *sl);1

寫者在訪問被順序鎖s1保護的共享資源前需要調(diào)用該函數(shù)來獲得順序鎖s1。它實際功能上等同于spin_lock,只是增加了一個對順序鎖順序號的加1操作,以便讀者能夠檢查出是否在讀期間有寫者訪問過。

void write_sequnlock(seqlock_t *sl);1

寫者在訪問完被順序鎖s1保護的共享資源后需要調(diào)用該函數(shù)來釋放順序鎖s1。它實際功能上等同于spin_unlock,只是增加了一個對順序鎖順序號的加1操作,以便讀者能夠檢查出是否在讀期間有寫者訪問過。

寫者使用順序鎖的模式如下:

write_seqlock(&seqlock_a);

//寫操作代碼塊

write_sequnlock(&seqlock_a);

因此,對寫者而言,它的使用與spinlock相同。

int write_tryseqlock(seqlock_t *sl);1

寫者在訪問被順序鎖s1保護的共享資源前也可以調(diào)用該函數(shù)來獲得順序鎖s1。它實際功能上等同于spin_trylock,只是如果成功獲得鎖后,該函數(shù)增加了一個對順序鎖順序號的加1操作,以便讀者能夠檢查出是否在讀期間有寫者訪問過。

unsigned read_seqbegin(const seqlock_t *sl);1

讀者在對被順序鎖s1保護的共享資源進行訪問前需要調(diào)用該函數(shù)。讀者實際沒有任何得到鎖和釋放鎖的開銷,該函數(shù)只是返回順序鎖s1的當前順序號。

int read_seqretry(const seqlock_t *sl, unsigned iv);1

讀者在訪問完被順序鎖s1保護的共享資源后需要調(diào)用該函數(shù)來檢查,在讀訪問期間是否有寫者訪問了該共享資源,如果是,讀者就需要重新進行讀操作,否則,讀者成功完成了讀操作。

因此,讀者使用順序鎖的模式如下:

do {

seqnum = read_seqbegin(&seqlock_a);

//讀操作代碼塊

...

} while (read_seqretry(&seqlock_a, seqnum));

write_seqlock_irqsave(lock, flags)1

寫者也可以用該宏來獲得順序鎖lock,與write_seqlock不同的是,該宏同時還把標志寄存器的值保存到變量flags中,并且失效了本地中斷。

write_seqlock_irq(lock)1

寫者也可以用該宏來獲得順序鎖lock,與write_seqlock不同的是,該宏同時還失效了本地中斷。與write_seqlock_irqsave不同的是,該宏不保存標志寄存器。

write_seqlock_bh(lock)1

寫者也可以用該宏來獲得順序鎖lock,與write_seqlock不同的是,該宏同時還失效了本地軟中斷。

write_sequnlock_irqrestore(lock, flags)1

寫者也可以用該宏來釋放順序鎖lock,與write_sequnlock不同的是,該宏同時還把標志寄存器的值恢復為變量flags的值。它必須與write_seqlock_irqsave配對使用。

write_sequnlock_irq(lock)1

寫者也可以用該宏來釋放順序鎖lock,與write_sequnlock不同的是,該宏同時還使能本地中斷。它必須與write_seqlock_irq配對使用。

write_sequnlock_bh(lock)1

寫者也可以用該宏來釋放順序鎖lock,與write_sequnlock不同的是,該宏同時還使能本地軟中斷。它必須與write_seqlock_bh配對使用。

read_seqbegin_irqsave(lock, flags)1

讀者在對被順序鎖lock保護的共享資源進行訪問前也可以使用該宏來獲得順序鎖lock的當前順序號,與read_seqbegin不同的是,它同時還把標志寄存器的值保存到變量flags中,并且失效了本地中斷。注意,它必須與read_seqretry_irqrestore配對使用。

read_seqretry_irqrestore(lock, iv, flags)1

讀者在訪問完被順序鎖lock保護的共享資源進行訪問后也可以使用該宏來檢查,在讀訪問期間是否有寫者訪問了該共享資源,如果是,讀者就需要重新進行讀操作,否則,讀者成功完成了讀操作。它與read_seqretry不同的是,該宏同時還把標志寄存器的值恢復為變量flags的值。注意,它必須與read_seqbegin_irqsave配對使用。

因此,讀者使用順序鎖的模式也可以為:

do {

seqnum = read_seqbegin_irqsave(&seqlock_a, flags);

//讀操作代碼塊

...

} while (read_seqretry_irqrestore(&seqlock_a, seqnum, flags));

讀者和寫者所使用的API的幾個版本應該如何使用與自旋鎖的類似。

如果寫者在操作被順序鎖保護的共享資源時已經(jīng)保持了互斥鎖保護對共享數(shù)據(jù)的寫操作,即寫者與寫者之間已經(jīng)是互斥的,但讀者仍然可以與寫者同時訪問,那么這種情況僅需要使用順序計數(shù)(seqcount),而不必要spinlock。

順序計數(shù)的API

unsigned read_seqcount_begin(const seqcount_t *s);1

讀者在對被順序計數(shù)保護的共享資源進行讀訪問前需要使用該函數(shù)來獲得當前的順序號。

int read_seqcount_retry(const seqcount_t *s, unsigned iv);1

讀者在訪問完被順序計數(shù)s保護的共享資源后需要調(diào)用該函數(shù)來檢查,在讀訪問期間是否有寫者訪問了該共享資源,如果是,讀者就需要重新進行讀操作,否則,讀者成功完成了讀操作。

因此,讀者使用順序計數(shù)的模式如下:

do {

seqnum = read_seqbegin_count(&seqcount_a);

//讀操作代碼塊

...

} while (read_seqretry(&seqcount_a, seqnum));

void write_seqcount_begin(seqcount_t *s);1

寫者在訪問被順序計數(shù)保護的共享資源前需要調(diào)用該函數(shù)來對順序計數(shù)的順序號加1,以便讀者能夠檢查出是否在讀期間有寫者訪問過。

void write_seqcount_end(seqcount_t *s);1

寫者在訪問完被順序計數(shù)保護的共享資源后需要調(diào)用該函數(shù)來對順序計數(shù)的順序號加1,以便讀者能夠檢查出是否在讀期間有寫者訪問過。

寫者使用順序計數(shù)的模式為:

write_seqcount_begin(&seqcount_a);

//寫操作代碼塊

write_seqcount_end(&seqcount_a);

需要特別提醒,順序計數(shù)的使用必須非常謹慎,只有確定在訪問共享數(shù)據(jù)時已經(jīng)保持了互斥鎖才可以使用。

內(nèi)核在寫和讀netdevice的名字時分別使用dev_change_name和netdev_get_name中使用了該方法。而dev_change_name由dev_ifsioc調(diào)用,其如下所示使用互斥鎖進行了保護。

rtnl_lock(); //內(nèi)部即互斥鎖

ret = dev_ifsioc(net, &ifr, cmd)

rtnl_unlock();123

#define local_irq_enable() do { raw_local_irq_enable(); } while (0)

#define local_irq_disable() do { raw_local_irq_disable(); } while (0)

#define raw_local_irq_disable() arch_local_irq_disable()

#define raw_local_irq_enable() arch_local_irq_enable()

可以看到這個與體系結(jié)構(gòu)有關(guān)

禁止內(nèi)核搶占就比較簡單了,就是防止當前進程不會突然被另一個進程搶占。在Linux的實現(xiàn)就是preempt_disable()和preempt_enable()函數(shù)

#define preempt_disable()

do {

//增加preempt_count

inc_preempt_count();

//保證先加了preempt_count才進行以后的操作

barrier();

} while (0)

#define preempt_enable()

do {

preempt_enable_no_resched();

barrier();

//檢查當前進程是否可搶占

preempt_check_resched();

} while

不管是禁止中斷還是禁止內(nèi)核搶占,都是為了提供內(nèi)核同步,但是他們都沒有提供任何保護機制來防止其它處理器的并發(fā)訪問。Linux支持多處理器,因此,內(nèi)核代碼一般都需要獲取某種鎖,防止來自其他處理器對共享數(shù)據(jù)的并發(fā)訪問,而禁止中斷提供保護機制,這是防止來自其他中斷處理程序的并發(fā)訪問。

前面說的都是概念,現(xiàn)在我們來討論幾個問題

1.在單處理器條件下,為什么禁止中斷就可以禁止內(nèi)核搶占?

首先來回顧一下內(nèi)核搶占發(fā)生在哪些時候:

1. 在中斷返回內(nèi)核空間的時候,這個沒什么好說的,跟中斷密切相關(guān),沒了中斷就不會發(fā)生

2. 內(nèi)核顯式調(diào)用schedule()(可搶占或阻塞)

我們先搞清楚一件事,就是我們說禁止中斷可以禁止內(nèi)核搶占只是說禁止任何意外的搶占,如果進程自己要調(diào)用schedule函數(shù),那誰也攔不住,事實上調(diào)用schedule這個函數(shù)本來就要禁止中斷,所以剩下的就是考慮創(chuàng)建或者喚醒一個更高優(yōu)先級的進程,或者調(diào)用信號量、完成量,所有的這些情況都要通過try_to_wake_up函數(shù)喚醒另一個進程,但是這個函數(shù)真正干的事只是設(shè)置了一下need_resched這個函數(shù),并沒有真的調(diào)用schedule函數(shù),調(diào)用是在系統(tǒng)調(diào)用返回用戶空間的時候進行的,所以跟內(nèi)核搶占也沒啥關(guān)系,所以從這些方面來說,禁止中斷是可以禁止內(nèi)核搶占的

2.自旋鎖關(guān)中斷后,為什么要再禁止搶占?

假設(shè)有這么個情況:

1)CPU-1在進程A的上下文調(diào)用了spin_lock_irqsave;

2)CPU-2調(diào)用wake_up_process喚醒了CPU-1上的進程B,由于進程B的優(yōu)先級高于進程A,進程A的TIF_NEED_RESCHED標記被設(shè)置。(CPU-2還會用IPI通知CPU-1進行resched,但是CPU-1禁用了中斷而不會響應);

3)CPU-1調(diào)用了某某函數(shù),這個函數(shù)包含了preempt_disable和preempt_enable(沒有規(guī)定關(guān)中斷的情況下不能調(diào)用這樣的函數(shù)吧~);

那么,如果spin_lock_irqsave沒有preempt_disable,第3步中的preempt_enable將觸發(fā)preempt_check_resched,從而讓進程B搶占掉進程A。

總之就是只有關(guān)了搶占,才能保證在臨界區(qū)成對出現(xiàn)的preempt_disable()/preempt_enable()(preempt_enable()也是一個潛在的主動調(diào)度的測試點)不會造成傷害。不然這種代碼就不能放在臨界區(qū)中了。

Linux可以使用互斥信號量來表示互斥鎖,那就是通過宏DECLARE_MUTEX來定義一個互斥信號量,因為DECLARE_MUTEX這個宏,Marcin Slusarz在08年提交的了一個patch,郵件地址為:https://lkml.org/lkml/2008/10/26/74,Marcin Slusarz認為DECLARE_MUTEX宏會誤導開發(fā)者,所以建議將DECLARE_MUTEX修改成DEFINE_SEMAPHORE,這個提議最終被內(nèi)核社區(qū)所接受,在2.6.36版本后的內(nèi)核就沒有DECLARE_MUTEX這個宏了,取而代之的是DEFINE_SEMAPHORE宏,在后來同互斥信號量相關(guān)的init_MUTEX、init_MUTEX_LOCKED也從文件中移除了。

雖然可以使用信號量來表示互斥鎖,但是互斥鎖其實是存在的,只是前面的宏DECLARE_MUTEX因為會引起歧義,所以修改成了DEFINE_SEMAPHORE,mutex在2.6.16版本就融入到了主內(nèi)核中了,使用mutex需要包含頭文件.

禁止中斷和禁止搶占的簡介

禁止中斷指的是Linux內(nèi)核提供了一組接口用于操作機器上的中斷狀態(tài)。這些接口為我們提供了能夠禁止當前處理器的中斷系統(tǒng),或者屏蔽掉整個機器的一條中斷線的能力。通過禁止中斷,可以確保某個中斷處理程序不會搶占當前的代碼??刂浦袛嘞到y(tǒng)在Linux的實現(xiàn)有很多,以local_irq_disable()和 local_irq_enable()函數(shù)為例

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1360

    瀏覽量

    40185
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10804

    瀏覽量

    210833
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11207

    瀏覽量

    208721
收藏 人收藏

    評論

    相關(guān)推薦

    Linux讀寫邏輯解析—Linux為何會引入讀寫?

    除了mutex,在linux內(nèi)核中,還有一個經(jīng)常用到的睡眠就是rw semaphore(后文簡稱為rwsem),它到底和mutex有什么不同呢?
    的頭像 發(fā)表于 12-04 11:04 ?844次閱讀
    <b class='flag-5'>Linux</b>讀寫<b class='flag-5'>鎖</b>邏輯解析—<b class='flag-5'>Linux</b>為何會引入讀寫<b class='flag-5'>鎖</b>?

    Linux內(nèi)核中RCU的用法

    Linux內(nèi)核中,RCU最常見的用途是替換讀寫。在20世紀90年代初期,Paul在實現(xiàn)通用RCU之前,實現(xiàn)了一種輕量級的讀寫。后來,為這個輕量級讀寫
    的頭像 發(fā)表于 12-27 09:56 ?1616次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>中RCU的用法

    Linux內(nèi)核同步機制的自旋原理是什么?

    自旋是專為防止多處理器并發(fā)而引入的一種,它在內(nèi)核中大量應用于中斷處理等部分(對于單處理器來說,防止中斷處理中的并發(fā)可簡單采用關(guān)閉中斷的方式,即在標志寄存器中關(guān)閉/打開中斷標志位,不需要自旋
    發(fā)表于 03-31 08:06

    Linux內(nèi)核教程

    本章學習目標掌握LINUX內(nèi)核版本的含義理解并掌握進程的概念掌握管道的概念及實現(xiàn)了解內(nèi)核的數(shù)據(jù)結(jié)構(gòu)了解LINUX內(nèi)核的算法掌握
    發(fā)表于 04-10 16:59 ?0次下載

    Linux內(nèi)核同步機制的自旋原理

    一、自旋 自旋是專為防止多處理器并發(fā)而引入的一種,它在內(nèi)核中大量應用于中斷處理等部分(對于單處理器來說,防止中斷處理中的并發(fā)可簡單采用關(guān)閉中
    發(fā)表于 06-08 14:50 ?1298次閱讀

    Linux內(nèi)核編譯詳談

    Linux內(nèi)核編譯詳談
    發(fā)表于 10-30 09:51 ?7次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>編譯詳談

    Linux內(nèi)核配置系統(tǒng)詳解

    隨著 Linux 操作系統(tǒng)的廣泛應用,特別是 Linux 在嵌入式領(lǐng)域的發(fā)展,越來越多的人開始投身到 Linux 內(nèi)核級的開發(fā)中。面對日益龐大的 L
    發(fā)表于 11-01 15:45 ?4次下載

    可以了解并學習Linux 內(nèi)核的同步機制

    Linux內(nèi)核同步機制,挺復雜的一個東西,常用的有自旋,信號量,互斥體,原子操作,順序,RCU,內(nèi)存屏障等。
    發(fā)表于 05-14 14:10 ?681次閱讀

    linux內(nèi)核是什么_linux內(nèi)核學習路線

    Linux內(nèi)核是一個操作系統(tǒng)(OS)內(nèi)核,本質(zhì)上定義為類Unix。它用于不同的操作系統(tǒng),主要是以不同的Linux發(fā)行版的形式。Linux
    發(fā)表于 09-16 15:49 ?2604次閱讀

    linux內(nèi)核參數(shù)設(shè)置_linux內(nèi)核的功能有哪些

    本文主要闡述了linux內(nèi)核參數(shù)設(shè)置及linux內(nèi)核的功能。
    發(fā)表于 09-17 14:40 ?1355次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>參數(shù)設(shè)置_<b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>的功能有哪些

    最硬核的Linux內(nèi)核文章

    來源 :頭條號@Linux學習教程,冰凌塊兒 01 前言 本文主要講解什么是Linux內(nèi)核,以及通過多張圖片展示Linux內(nèi)核的作用與功能,
    的頭像 發(fā)表于 10-19 17:46 ?2088次閱讀
    最硬核的<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>文章

    快速理解什么是Linux內(nèi)核以及Linux內(nèi)核的內(nèi)容

    01 前言 本文主要講解什么是Linux內(nèi)核,以及通過多張圖片展示Linux內(nèi)核的作用與功能,以便于讀者能快速理解什么是Linux
    的頭像 發(fā)表于 10-21 12:02 ?4246次閱讀
    快速理解什么是<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>以及<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>的內(nèi)容

    Linux 5.10.5內(nèi)核正式發(fā)布

    1月6日,Linux基金會宣布,Linux 5.10.5內(nèi)核正式發(fā)布,所有5.10內(nèi)核系列的用戶都必須升級。
    的頭像 發(fā)表于 01-07 14:36 ?2572次閱讀

    Linux內(nèi)核睡眠的三種狀態(tài)講解

    會因為等待某些事件而睡眠(可能是等待IO讀寫完成,也可能等待其他內(nèi)核路徑釋放一把等)。本文來探討一下,任務處于睡眠中有哪些狀態(tài)?睡眠對于任務來說究竟意味著什么?內(nèi)核是如何管理睡眠的任
    的頭像 發(fā)表于 08-16 15:13 ?3024次閱讀

    介紹一下Linux內(nèi)核中的各種

    Linux內(nèi)核中有許多不同類型的,它們都可以用來保護關(guān)鍵資源,以避免多個線程或進程之間發(fā)生競爭條件,從而保護系統(tǒng)的穩(wěn)定性和可靠性。
    的頭像 發(fā)表于 05-16 14:13 ?5091次閱讀