linux内核同步之信号量、顺序锁、RCU、完成量、关闭中断【转】
轉自:http://blog.csdn.net/goodluckwhh/article/details/9006065
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
目錄(?)[-]
一、信號量
?
1.信號量的概念
信號量也是一種鎖,當信號量不可用時,嘗試獲取信號量的任務將掛起直到它拿到了信號量。由于嘗試獲取信號量的任務可能掛起,因而中斷服務程序以及可延遲函數不能使用信號量。?
對于信號量來說需要注意:
因為down操作可能導致調用者休眠,因而不能休眠的場景是不允許調用該函數的,比如中斷上下文,而up可以在任意上下文調用。如果要在中斷上下文調用可以使用down_trylock,它嘗試獲取信號量,但是當無法獲取時會返回1而不是等待,如果可以獲取則返回0。
2.信號量的數據結構和相關API
1.數據結構
信號量用數據結構semaphore表示, 它包含以下域:- count:受該信號狼保護的資源的計數值,如果大于0表示資源可用;否則表示資源不可用。對它的操作必須是原子的。如果存在檢查并更新的操作,這兩個操作的組合必須也是原子的,比如down中的比較并減1。
- wait_list:等待該信號量保護的資源可用的任務隊列的鏈表。
- lock:保護等待任務鏈表的自旋鎖。
2.初始化
init_MUTEX( ) 將信號量的count域初始化為1,表示資源當前可用init_MUTEX_LOCKED( ) 將信號量的count域初始化為0 ,表示資源當前不可用?
DECLARE_MUTEX 完成和 init_MUTEX類似的操作,但是它還多一個靜態分配一個信號量的動作
DECLARE_MUTEX_LOCKED 和init_MUTEX_LOCKED類似,但是它還多一個靜態分配一個信號量的動作
當然也可以將信號量的初始者設置為其它正值。
3.獲取和釋放信號量
?
up()函數用于釋放信號量,如果當前信號量的等待隊列為空,即沒有任務在等待該信號量被釋放,則它增加信號量的count值,然后返回,否則它喚醒等待隊列上的第一個任務。
down用于獲取信號量,如果信號量的值大于0,則它將count的值減1,并返回;否則調用者將被添加到等待隊列的尾部,并等待直到被喚醒即該任務獲得資源。
void down(struct semaphore *sem)
int down_trylock(struct semaphore *sem)//嘗試獲取信號量,但是當無法獲取時會返回1而不是等待,如果可以獲取則返回0
int down_interruptible(struct semaphore *sem)//獲取信號量,但是在等待信號量的時候可以被打斷,如果在等待過程中被打斷,則返回-EINTR
int down_timeout(struct semaphore *sem, long jiffies) //用于獲取信號量,但是最多等待jiffies長的時間,如果在指定的時間期限內沒有獲取信號量,則就返回-ETIME。
void up(struct semaphore *sem)
3. 讀寫信號量的概念
讀寫信號量類似于讀寫自旋鎖,它是為讀多寫少的場景做了優化的信號量,不同于自旋鎖的是,在無法獲得信號量時,它掛起而不是自旋。可以有多個任務并發的為讀獲取讀寫信號量,但是同一時間點只能有一個任務可以為寫獲得讀寫信號量。因此只有沒有任何任務為讀或寫持有該信號量時,新的為寫獲取信號量的操作才能成功。
內核FIFO的方式存儲等待隊列中的任務:
4.讀寫信號量的數據結構
rw_semaphore數據結構用于表示讀寫信號量,它包含如下的域:- activity:0表示沒有任務在讀或者寫,大于0表示有任務正在讀,-1表示有一個任務正在寫
- wait_list:存放等待該信號量的任務的鏈表
- wait_lock:用于保護等待任務鏈表的自旋鎖
void down_read(struct rw_semaphore *sem)
int down_read_trylock(struct rw_semaphore *sem)//嘗試為讀獲取信號量,但是當無法獲取時會返回1而不是等待,如果可以獲取則返回0
void down_write(struct rw_semaphore *sem)
int down_write_trylock(struct rw_semaphore *sem)//嘗試為讀獲取信號量,但是當無法獲取時會返回1而不是等待,如果可以獲取則返回0
void up_read(struct rw_semaphore *sem) ?
void up_write(struct rw_semaphore *sem) ?
void downgrade_write(struct rw_semaphore *sem)//它將一個寫信號量降格為讀信號量,并喚醒等待隊列上的讀任務。因此它的調用者應該持有寫信號量
二、順序鎖
1.順序鎖的概念
使用讀寫鎖時,讀鎖和寫鎖的優先級是一樣的。2.6內核引入的順序鎖和讀寫自旋鎖相同,區別在于它給寫者賦予了更高的優先級:在使用順序鎖時即便讀者正在進行讀操作,寫者也可以進行寫動作。讀寫鎖的優點在于寫者永遠不會由于有讀者正在進行讀而等待,其缺點在于讀者可能需要嘗試讀好多次才能讀到合法的數據。?
不是所有的數據類型都能用順序鎖來保護,如果要使用順序鎖,以下原則必須被遵循:
2.數據結構
順序鎖使用數據結構seqlock_t表示,它包含兩個域:- 自旋鎖lock
- 整數序列號
有兩種方法可以初始化順序鎖:
?
?
1.寫操作
寫者必須先獲取鎖,再操作,然后再釋放鎖。write_seqlock:用于為寫獲取順序鎖,它會獲取順序鎖中的自旋鎖,然后將順序鎖的序列號加1
write_sequnlock:用于釋放順序鎖,它也會增加順序鎖的序列號,然后釋放順序鎖中的自旋鎖
這種設計確保了寫者正在寫數據并且沒有完成時序列號為奇數,沒有寫者在修改數據時,序列號為偶數。
?
?
2.讀操作
對于讀者來說,它需要采取下列形式的操作序列:? ? unsigned int seq;
? ? do {
? ? ? ? seq = read_seqbegin(&seqlock);
? ? ? ? /* ... CRITICAL REGION ... */
? ? } while (read_seqretry(&seqlock, seq));
read_seqbegin:返回順序鎖當前序列號的值
read_seqretry:如果指定的值和順序鎖的序列號的值不等或者順序鎖的序列號為奇數,則返回1。
需要注意的是,讀者并不會關閉內核搶占,而由于寫者獲取了順序鎖中的自旋鎖,因而它會禁止內核搶占。
三、Read-Copy Update (RCU)
RCU是另外一種被設計用來在SMP環境下保護主要操作是讀操作的數據同步技術。RCU允許多個讀者和多個寫者同時并發操作。RCU沒使用任何鎖也沒使用任何由多個CPU共享的計數器;相比于讀寫自旋鎖和順序鎖,這是一個極大地優勢。RCU有極大地優勢,但是也有很大的限制,它限制了它所能保護的數據結構:
- 被保護的資源應當是動態分配的、通過指針來存取的, 并且所有對這些資源的引用必須由原子代碼持有
- 當進入由RCU保護的臨界區時不能休眠
1.寫操作
它的實現原理是:當數據結構需要改變時,寫線程做一個拷貝,改變這個拷貝(這里需要一個內存屏障,以保證更新能被其它CPU看到),接著使相關的指針指向新的版本,當內核確認沒有CPU還在引用舊版本時舊的版本就可以被釋放.2.讀操作
當內核代碼想要讀取一個由RCU保護的數據結構時,它3.釋放舊的版本
在RCU中關鍵的是舊版本何時釋放。由于其它處理器上的代碼可能還有對舊的數據的引用,因而不能立即釋放它。內核必須在它確保已經沒有任何指向舊版本的引用時才能釋放舊版本。實際上,只有當所有讀者都調用了rcu_read_unlock后才能釋放舊的拷貝。內核要求每個讀者在開始下列動作之前調用rcu_read_unlock宏:- CPU進行進程切換
- CPU開始在向用戶模式轉變
- CPU開始執行idle進程
void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
- head:其中head是rcu_head類型的數據結構指針,它通常嵌入在受保護的數據結構中。
- func:在可以釋放RCU數據結構時會被調用以釋放舊的數據結構
?
?
四、完成量(Completions)
1.完成量的概念
內核中常見的一種場景是在當前任務啟動另外一個任務,然后等待該任務完成做完某個事情。考慮使用信號量來完成這個工作:struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);
down(&sem);
當外部完成我們期望的動作時,它調用up(&sem)。
但是信號量并不是特別適合這種場景,信號量對“可用”的情況做了優化,即使用信號量時期望在大部分情況下它是可用的。然而在上述場景中很顯然down必然走的是信號量不可用的分支。
completion是用來解決這種問題的一種機制。completion允許一個任務告訴另一個任務工作已經完成。
2.數據結構和相關API
內核使用數據結構completion來表示completionvoid init_completion(struct completion *x)
可以使用該函數完成completion的初始化或者通過DECLARE_COMPLETION(work)聲明并初始化一個completion,INIT_COMPLETION(x)宏用戶初始化completion x。
void wait_for_completion(struct completion *c);
該函數在c上等待,并且不可打斷。如果調用了wait_for_completion而沒有任何任務調用complete,則wait_for_completion的將永遠等待。
wait_for_completion_interruptible(struct completion *x)
該函數在x上等待,但是可能在等待過程中被打斷,當由于被打斷而返回時,返回值為-ERESTARTSYS;否則返回0
void complete(struct completion *c);
void complete_all(struct completion *c);
這兩個函數可用于喚醒在c上等待的任務。區別在于complete只喚醒一個等待的任務,而complete_all喚醒所有的。
completion 機制的典型使用是在模塊退出時與內核線程的終止一起. 在這個原型例子里,
void complete_and_exit(struct completion *c, long retval);
喚醒在c上等待的任務,并且以retval退出本任務。
?
?
五、關閉本地中斷
關閉中斷是一種設置臨界區的方法。當關閉了中斷時,即便是硬件中斷也無法打斷代碼的運行,因而它可以保護同時被中斷服務程序訪問的數據結構。但是需要注意的是關閉本地中斷并不能保護可能被多個CPU訪問的數據結構。因此在SMP架構下,往往需要用關閉中斷和自旋鎖想結合的方式來保護被中斷服務程序使用的共享資源。local_irq_disable() 宏用來在本地CPU關閉中斷
local_irq_enable() 宏用來在本地CPU打開中斷, which makes use of the of the sti assembly language instruction, enables them. As stated in the 使用這兩個函數的問題在于,在需要的時候我們可以簡單的關閉中斷,但是簡單粗暴的打開中斷不一定是正確的,因為在我們關閉中斷時,中斷可能已經是關閉的,這時如果我們簡單的打開了中斷就可能導致問題。
local_irq_save:關閉中斷并且保存中斷狀態字
local_irq_restore:以指定的中斷狀態字恢復中斷
這兩個宏就很好的解決了問題,因為local_irq_restore只是將中斷恢復到了我們調用local_irq_save時的狀態。
六、使能和關閉可延遲函數
由于可延遲函數在不可預期的時間點被執行,因而被可延遲函數訪問的數據結構也需要進行保護。禁止可延遲函數執行的最簡單的辦法是關閉本地中斷,因為這樣中斷服務程序就沒辦法執行了,也就沒辦法啟動可延遲函數。
由于軟中斷在處于中斷狀態時不會執行,而tasklet是基于軟中斷實現的,因而只要禁止本地的軟中斷就可以在本地CPU上禁止可延遲函數。
local_bh_disable宏用于將本地CPU的軟中斷計數加1,因而就禁止了本地的軟中斷。
local_bh_enable宏用于將本地CPU的軟中斷計數減1,用于打開本地軟中斷
這兩個函數可以都可以被重復調用,但是調用了多少次local_bh_disable,就要相應的調用多少次local_bh_enable才能打開軟中斷
七、選擇同步技術
有很多技術都可用于避免訪問共享的數據時出現競態的手段。但是各種手段對系統性能的影響是不同的。但是作為一條原則,應該使用在該場景下能獲得最大并發等級或者說并發數量的技術手段。系統的并發等級取決于:
- 可以并發操作的I/O設備數
- 做有效工作的CPU數
?
為了提高CPU效率,應該盡可能避免使用自旋鎖。因為它不僅導致自旋的CPU處于忙等狀態,而且會對高速緩存造成不利的影響。
取決于訪問共享數據的內核任務的類型,需要采用的同步技術也會有區別:
| 任務 | 單處理器環境下使用的同步技術 | 多處理器環境下使用的額外的同步技術 |
| 可休眠任務(內線線程,系統調用) | 信號量 | 不需要額外的同步技術 |
| 中斷 | 關閉本地中斷 | 自旋鎖 |
| 可延遲函數 | 不需要 | 不需要或者使用自旋鎖(取決于不同的tasklet是否會訪問相同的數據結構) |
| 可休眠任務+中斷 | 關閉本地中斷 | 自旋鎖 |
| 可休眠任務+可延遲函數 | 關閉本地軟中斷 | 自旋鎖 |
| 中斷+可延遲函數 | 關閉本地中斷 | 自旋鎖 |
| 可休眠任務+中斷+可延遲函數 | 關閉本地中斷 | 自旋鎖 |
本文轉自張昺華-sky博客園博客,原文鏈接:http://www.cnblogs.com/sky-heaven/p/5413996.html,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的linux内核同步之信号量、顺序锁、RCU、完成量、关闭中断【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 模拟课----需求文本
- 下一篇: linux磁盘、分区、设备简单介绍