歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

高手進階必讀:Linux內核的同步機制二

  四、讀寫信號量(rw_semaphore)    讀寫信號量對訪問者進行了細分,或者為讀者,或者為寫者,讀者在保持讀寫信號量期間只能對該讀寫信號量保護的共享資源進行讀訪問,如果一個任務除了需要讀,可能還需要寫,那麼它必須被歸類為寫者,它在對共享資源訪問之前必須先獲得寫者身份,寫者在發現自己不需要寫訪問的情況下可以降級為讀者。讀寫信號量同時擁有的讀者數不受限制,也就說可以有任意多個讀者同時擁有一個讀寫信號量。    如果一個讀寫信號量當前沒有被寫者擁有並且也沒有寫者等待讀者釋放信號量,那麼任何讀者都可以成功獲得該讀寫信號量;否則,讀者必須被掛起直到寫者釋放該信號量。如果一個讀寫信號量當前沒有被讀者或寫者擁有並且也沒有寫者等待該信號量,那麼一個寫者可以成功獲得該讀寫信號量,否則寫者將被掛起,直到沒有任何訪問者。因此,寫者是排他性的,獨占性的。    讀寫信號量有兩種實現,一種是通用的,不依賴於硬件架構,因此,增加新的架構不需要重新實現它,但缺點是性能低,獲得和釋放讀寫信號量的開銷大;另一種是架構相關的,因此性能高,獲取和釋放讀寫信號量的開銷小,但增加新的架構需要重新實現。在內核配置時,可以通過選項去控制使用哪一種實現。    讀寫信號量的相關API有:    DECLARE_RWSEM(name)    該宏聲明一個讀寫信號量name並對其進行初始化。    void init_rwsem(strUCt rw_semaphore *sem);    該函數對讀寫信號量sem進行初始化。    void down_read(struct rw_semaphore *sem);    讀者調用該函數來得到讀寫信號量sem。該函數會導致調用者睡眠,因此只能在進程上下文使用。    int down_read_trylock(struct rw_semaphore *sem);    該函數類似於down_read,只是它不會導致調用者睡眠。它盡力得到讀寫信號量sem,如果能夠立即得到,它就得到該讀寫信號量,並且返回1,否則表示不能立刻得到該信號量,返回0。因此,它也可以在中斷上下文使用。    void down_write(struct rw_semaphore *sem);    寫者使用該函數來得到讀寫信號量sem,它也會導致調用者睡眠,因此只能在進程上下文使用。    int down_write_trylock(struct rw_semaphore *sem);    該函數類似於down_write,只是它不會導致調用者睡眠。該函數盡力得到讀寫信號量,如果能夠立刻獲得,就獲得該讀寫信號量並且返回1,否則表示無法立刻獲得,返回0。它可以在中斷上下文使用。    void up_read(struct rw_semaphore *sem);    讀者使用該函數釋放讀寫信號量sem。它與down_read或down_read_trylock配對使用。如果down_read_trylock返回0,不需要調用up_read來釋放讀寫信號量,因為根本就沒有獲得信號量。    void up_write(struct rw_semaphore *sem);    寫者調用該函數釋放信號量sem。它與down_write或down_write_trylock配對使用。如果down_write_trylock返回0,不需要調用up_write,因為返回0表示沒有獲得該讀寫信號量。    void downgrade_write(struct rw_semaphore *sem);    該函數用於把寫者降級為讀者,這有時是必要的。因為寫者是排他性的,因此在寫者保持讀寫信號量期間,任何讀者或寫者都將無法訪問該讀寫信號量保護的共享資源,對於那些當前條件下不需要寫訪問的寫者,降級為讀者將,使得等待訪問的讀者能夠立刻訪問,從而增加了並發性,提高了效率。    讀寫信號量適於在讀多寫少的情況下使用,在Linux內核中對進程的內存映像描述結構的訪問就使用了讀寫信號量進行保護。    在Linux中,每一個進程都用一個類型為task_t或struct task_struct的結構來描述,該結構的類型為struct mm_struct的字段mm描述了進程的內存映像,特別是mm_struct結構的mmap字段維護了整個進程的內存塊列表,該列表將在進程生存期間被大量地遍利或修改。    因此mm_struct結構就有一個字段mmap_sem來對mmap的訪問進行保護,mmap_sem就是一個讀寫信號量,在proc文件系統裡有很多進程內存使用情況的接口,通過它們能夠查看某一進程的內存使用情況,命令free、ps和top都是通過proc來得到內存使用信息的,proc接口就使用down_read和up_read來讀取進程的mmap信息。    當進程動態地分配或釋放內存時,需要修改mmap來反映分配或釋放後的內存映像,因此動態內存分配或釋放操作需要以寫者身份獲得讀寫信號量mmap_sem來對mmap進行更新。系統調用brk和munmap就使用了down_write和up_write來保護對mmap的訪問。    五、自旋鎖(spinlock)    自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。    由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。    信號量和讀寫信號量適合於保持時間較長的情況,它們會導致調用者睡眠,因此只能在進程上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。    如果被保護的共享資源只在進程上下文訪問,使用信號量保護該共享資源非常合適,如果對共巷資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。    自旋鎖保持期間是搶占失效的,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內核可搶占或SMP的情況下才真正需要,在單CPU且不可搶占的內核下,自旋鎖的所有操作都是空操作。    跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將自旋在那裡,直到該自旋鎖的保持者釋放了鎖。    無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執行單元獲得鎖。    自旋鎖的API有:    spin_lock_init(x)    該宏用於初始化自旋鎖x。自旋鎖在真正使用前必須先初始化。該宏用於動態初始化。    DEFINE_SPINLOCK(x)    該宏聲明一個自旋鎖x並初始化它。該宏在2.6.11中第一次被定義,在先前的內核中並沒有該宏。    SPIN_LOCK_UNLOCKED    該宏用於靜態初始化一個自旋鎖。    DEFINE_SPINLOCK(x)等同於spinlock_t x = SPIN_LOCK_UNLOCKED  spin_is_locked(x)    該宏用於判斷自旋鎖x是否已經被某執行單元保持(即被鎖),如果是,返回真,否則返回假。    spin_unlock_wait(x)    該宏用於等待自旋鎖x變得沒有被任何執行單元保持,如果沒有任何執行單元保持該自旋鎖,該宏立即返回,否則將循環在那裡,直到該自旋鎖被保持者釋放。    spin_trylock(lock)    該宏盡力獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖並返回真,否則不能立即獲得鎖,立即返回假。它不會自旋等待lock被釋放。    spin_lock(lock)    該宏用於獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那裡,直到該自旋鎖的保持者釋放,這時,它獲得鎖並返回。總之,只有它獲得鎖才返回。    spin_lock_irqsave(lock, flags)    該宏獲得自旋鎖的同時把標志寄存器的值保存到變量flags中並失效本地中斷。    spin_lock_irq(lock)    該宏類似於spin_lock_irqsave,只是該宏不保存標志寄存器的值。    spin_lock_bh(lock)    該宏在得到自旋鎖的同時失效本地軟中斷。    spin_unlock(lock)    該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用。如果spin_trylock返回假,表明沒有獲得自旋鎖,因此不必使用spin_unlock釋放。    spin_unlock_irqrestore(lock, flags)    該宏釋放自旋鎖lock的同時,也恢復標志寄存器的值為變量flags保存的值。它與spin_lock_irqsave配對使用。    spin_unlock_irq(lock)    該宏釋放自旋鎖lock的同時,也使能本地中斷。它與spin_lock_irq配對應用。    spin_unlock_bh(lock)    該宏釋放自旋鎖lock的同時,也使能本地的軟中斷。它與spin_lock_bh配對使用。    spin_trylock_irqsave(lock, flags)    該宏如果獲得自旋鎖lock,它也將保存標志寄存器的值到變量flags中,並且失效本地中斷,如果沒有獲得鎖,它什麼也不做。    因此如果能夠立即獲得鎖,它等同於spin_lock_irqsave,如果不能獲得鎖,它等同於spin_trylock。如果該宏獲得自旋鎖lock,那需要使用spin_unlock_irqrestore來釋放。    spin_trylock_irq(lock)    該宏類似於spin_trylock_irqsave,只是該宏不保存標志寄存器。如果該宏獲得自旋鎖lock,需要使用spin_unlock_irq來釋放。    spin_trylock_bh(lock)    該宏如果獲得了自旋鎖,它也將失效本地軟中斷。如果得不到鎖,它什麼也不做。因此,如果得到了鎖,它等同於spin_lock_bh,如果得不到鎖,它等同於spin_trylock。如果該宏得到了自旋鎖,需要使用spin_unlock_bh來釋放。    spin_can_lock(lock)    該宏用於判斷自旋鎖lock是否能夠被鎖,它實際是spin_is_locked取反。如果lock沒有被鎖,它返回真,否則,返回假。該宏在2.6.11中第一次被定義,在先前的內核中並沒有該宏。    獲得自旋鎖和釋放自旋鎖有好幾個版本,因此讓讀者知道在什麼樣的情況下使用什麼版本的獲得和釋放鎖的宏是非常必要的。    如果被保護的共享資源只在進程上下文訪問和軟中斷上下文訪問,那麼當在進程上下文訪問共享資源時,可能被軟中斷打斷,從而可能進入軟中斷上下文來對被保護的共享資源訪問,因此對於這種情況,對共享資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護。    當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當的,它比其他兩個快。    如果被保護的共享資源只在進程上下文和tasklet或timer上下文訪問,那麼應該使用與上面情況相同的獲得和釋放鎖的宏,因為tasklet和timer是用軟中斷實現的。    如果被保護的共享資源只在一個tasklet或timer上下文訪問,那麼不需要任何自旋鎖保護,因為同一個tasklet或timer只能在一個CPU上運行,即使是在SMP環境下也是如此。實際上tasklet在調用tasklet_schedule標記其需要被調度時已經把該tasklet綁定到當前CPU,因此同一個tasklet決不可能同時在其他CPU上運行。    timer也是在其被使用add_timer添加到timer隊列中時已經被幫定到當前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來保護對共享資源的訪問就可以了。    因為在執行中斷處理句柄期間,不可能被同一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上的執行單元對共享資源的同時訪問以及不同進程上下文互相搶占導致的對共享資源的非同步訪問,而中斷失效和軟中斷失效卻是為了阻止在同一CPU上軟中斷或中斷對共享資源的非同步訪問。




 

來源:賽迪網



Copyright © Linux教程網 All Rights Reserved