Linux信号量之内核信号量
一、內(nèi)核信號量
Linux內(nèi)核的信號量在概念和原理上與用戶態(tài)的System V的IPC機制信號量是一樣的,但是它絕不可能在內(nèi)核之外使用,它是一種睡眠鎖。
如果有一個任務想要獲得已經(jīng)被占用的信號量時,信號量會將其放入一個等待隊列(它不是站在外面癡癡地等待而是將自己的名字寫在任務隊列中)然后讓其睡眠。
當持有信號量的進程將信號釋放后,處于等待隊列中的一個任務將被喚醒(因為隊列中可能不止一個任務),并讓其獲得信號量。
這一點與自旋鎖不同,處理器可以去執(zhí)行其它代碼。
它與自旋鎖的差異:由于爭用信號量的進程在等待鎖重新變?yōu)榭捎脮r會睡眠,所以信號量適用于鎖會被長時間持有的情況;
相反,鎖被短時間持有時,使用信號量就不太適宜了,因為睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖占用的全部時間表還要長;
由于執(zhí)行線程在鎖被爭用時會睡眠,所以只能在進程上下文中才能獲得信號量鎖,因為在中斷上下文中是不能進行調(diào)試的;持有信號量的進程也可以去睡眠,當然也可以不睡眠,因為當其他進程爭用信號量時不會因此而死鎖;不能同時占用信號量和自旋鎖,因為自旋鎖不可以睡眠而信號量鎖可以睡眠。相對而來說信號量比較簡單,它不會禁止內(nèi)核搶占,持有信號量的代碼可以被搶占。
信號量還有一個特征,就是它允許多個持有者,而自旋鎖在任何時候只能允許一個持有者。
當然我們經(jīng)常遇到也是只有一個持有者,這種信號量叫二值信號量或者叫互斥信號量。允許有多個持有者的信號量叫計數(shù)信號量,在初始化時要說明最多允許有多少個持有者(Count值)
信號量在創(chuàng)建時需要設置一個初始值,表示同時可以有幾個任務可以訪問該信號量保護的共享資源,初始值為1就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問信號量保護的共享資源。
當任務訪問完被信號量保護的共享資源后,必須釋放信號量,釋放信號量通過把信號量的值加1實現(xiàn),如果信號量的值為非正數(shù),表明有任務等待當前信號量,因此它也喚醒所有等待該信號量的任務。
二、內(nèi)核信號量的構成
內(nèi)核信號量類似于自旋鎖,因為當鎖關閉時,它不允許內(nèi)核控制路徑繼續(xù)進行。當內(nèi)核控制路徑試圖獲取內(nèi)核信號量鎖保護的忙資源時,相應的進程就被掛起。只有在資源被釋放時,進程才再次變?yōu)榭蛇\行。
只有可以睡眠的函數(shù)才能獲取內(nèi)核信號量;中斷處理程序和可延遲函數(shù)都不能使用內(nèi)核信號量。 內(nèi)核信號量是struct semaphore類型的對象,在內(nèi)核源碼中位于include\linux\semaphore.h文件
三、內(nèi)核信號量中的等待隊列
內(nèi)核信號量使用了等待隊列wait_queue來實現(xiàn)阻塞操作。
當某任務由于沒有某種條件沒有得到滿足時,它就被掛到等待隊列中睡眠。當條件得到滿足時,該任務就被移出等待隊列,此時并不意味著該任務就被馬上執(zhí)行,因為它又被移進工作隊列中等待CPU資源,在適當?shù)臅r機被調(diào)度。
內(nèi)核信號量是在內(nèi)部使用等待隊列的,也就是說該等待隊列對用戶是隱藏的,無須用戶干涉。由用戶真正使用的等待隊列我們將在另外的篇章進行詳解。
四、內(nèi)核信號量的相關函數(shù)
1、初始化
(1)、該宏聲明一個信號量name是直接將結構體中count值設置成n,此時信號量可用于實現(xiàn)進程間的互斥量。
#define __SEMAPHORE_INITIALIZER(name, n) { .lock = __SPIN_LOCK_UNLOCKED((name).lock), .count = n, .wait_list = LIST_HEAD_INIT((name).wait_list), }(2)、該宏聲明一個互斥鎖name,但把它的初始值設置為1
#define DECLARE_MUTEX(name) \struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)(3)、初始化設置信號量的初值,它設置信號量sem的值為val。
void sema_init (struct semaphore *sem, int val);2、獲取信號量–申請內(nèi)核信號量所保護的資源
void down(struct semaphore * sem);(1)、該函數(shù)用于獲得信號量sem,它會導致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數(shù)。該函數(shù)將把sem的值減1,如果信號量sem的值非負,就直接返回,否則調(diào)用者將被掛起,直到別的任務釋放該信號量才能繼續(xù)運行。
int down_interruptible(struct semaphore * sem);(2)、該函數(shù)功能與down類似,不同之處為,down不會被信號(signal)打斷,但down_interruptible能被信號(比如Ctrl+C)打斷,因此該函數(shù)有返回值來區(qū)分是正常返回還是被信號中斷,如果返回0,表示獲得信號量正常返回,如果被信號打斷,返回-EINTR
int down_trylock(struct semaphore * sem);該函數(shù)試著獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量并返回0,否則,表示不能獲得信號量sem,返回值為非0值。因此,它不會導致調(diào)用者睡眠,可以在中斷上下文使用。
3、釋放內(nèi)核信號量所保護的資源
void up(struct semaphore * sem);該函數(shù)釋放信號量sem,即把sem的值加1,如果sem的值為非正數(shù),表明有任務等待該信號量,因此喚醒這些等待者。
五、內(nèi)核信號量的使用例程
在驅動程序中,當多個線程同時訪問相同的資源時(驅動中的全局變量時一種典型的
共享資源),可能會引發(fā)“競態(tài)“,因此我們必須對共享資源進行并發(fā)控制。Linux內(nèi)核中
解決并發(fā)控制的最常用方法是自旋鎖與信號量(絕大多數(shù)時候作為互斥鎖使用)。
六、讀-寫信號量
跟自旋鎖一樣,信號量也有區(qū)分讀-寫信號量之分
如果一個讀寫信號量當前沒有被寫者擁有并且也沒有寫者等待讀者釋放信號量,那么任何讀者都可以成功獲得該讀寫信號量;
否則,讀者必須被掛起直到寫者釋放該信號量。如果一個讀寫信號量當前沒有被讀者或寫者擁有并且也沒有寫者等待該信號量,那么一個寫者可以成功獲得該讀寫信號量,否則寫者將被掛起,直到?jīng)]有任何訪問者。因此,寫者是排他性的,獨占性的。
讀寫信號量有兩種實現(xiàn),一種是通用的,不依賴于硬件架構,因此,增加新的架構不需要重新實現(xiàn)它,但缺點是性能低,獲得和釋放讀寫信號量的開銷大;另一種是架構相關的,因此性能高,獲取和釋放讀寫信號量的開銷小,但增加新的架構需要重新實現(xiàn)。在內(nèi)核配置時,可以通過選項去控制使用哪一種實現(xiàn)。
讀寫信號量的相關API有:
1、該宏聲明一個讀寫信號量name并對其進行初始化。
DECLARE_RWSEM(name)2、該函數(shù)對讀寫信號量sem進行初始化
void init_rwsem(struct rw_semaphore *sem);3、讀者調(diào)用該函數(shù)來得到讀寫信號量sem。該函數(shù)會導致調(diào)用者睡眠,因此只能在進程上下文使用。
void down_read(struct rw_semaphore *sem);4、該函數(shù)類似于down_read,只是它不會導致調(diào)用者睡眠。它盡力得到讀寫信號量sem,如果能夠立即得到,它就得到該讀寫信號量,并且返回1,否則表示不能立刻得到該信號量,返回0。因此,它也可以在中斷上下文使用。
int down_read_trylock(struct rw_semaphore *sem);5、寫者使用該函數(shù)來得到讀寫信號量sem,它也會導致調(diào)用者睡眠,因此只能在進程上下文使用。
void down_write(struct rw_semaphore *sem);6、該函數(shù)類似于down_write,只是它不會導致調(diào)用者睡眠。該函數(shù)盡力得到讀寫信號量,如果能夠立刻獲得,就獲得該讀寫信號量并且返回1,否則表示無法立刻獲得,返回0。它可以在中斷上下文使用
int down_write_trylock(struct rw_semaphore *sem);7、讀者使用該函數(shù)釋放讀寫信號量sem。它與down_read,down_read_trylock配對使用。如果down_read_trylock返回0,不需要調(diào)用up_read來釋放讀寫信號量,因為根本就沒有獲得信號量。
void up_read(struct rw_semaphore *sem);8、寫者調(diào)用該函數(shù)釋放信號量sem。它與down_writedown_write_trylock配對使用。如果down_write_trylock返回0,不需要調(diào)用up_write,因為返回0表示沒有獲得該讀寫信號量。
void up_write(struct rw_semaphore *sem);9、該函數(shù)用于把寫者降級為讀者,這有時是必要的。因為寫者是排他性的,因此在寫者保持讀寫信號量期間,任何讀者或寫者都將無法訪問該讀寫信號量保護的共享資源,對于那些當前條件下不需要寫訪問的寫者,降級為讀者將,使得等待訪問的讀者能夠立刻訪問,從而增加了并發(fā)性,提高了效率。
讀寫信號量適于在讀多寫少的情況下使用,在linux內(nèi)核中對進程的內(nèi)存映像描述結構的訪問就使用了讀寫信號量進行保護。
究竟什么時候使用自旋鎖什么時候使用信號量,下面給出建議的方案
當對低開銷、短期、中斷上下文加鎖,優(yōu)先考慮自旋鎖;當對長期、持有鎖需要休眠的任務,優(yōu)先考慮信號量。
總結
以上是生活随笔為你收集整理的Linux信号量之内核信号量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux信号量简介
- 下一篇: Linux信号量之用户态信号量(Posi