臨界區、互斥
轉:https://www.cnblogs.com/jan5/articles/3351186.html
一、需要了解的概念知識
概念1:臨界區
(1)維基百科對臨界區的定義:
在同步的程式設計中,臨界區段(Critical section)指的是一個訪問共用資源(例如:共用裝置或是共用儲存器)的程式片段,而這些共用資源又無法同時被多個執行緒訪問的特性。
當有執行緒進入臨界區段時,其他執行緒或是程序必須等待(例如:bounded waiting 等待法),有一些同步的機制必須在臨界區段的進入點與離開點實現,以確保這些共用資源是被互斥或的使用,例如:semaphore。
(2)形象點的解釋:
對某一程式碼段A來說,在程式中可能被多次執行,把A的一次執行過程稱為A的程式碼執行路徑(簡稱程式碼路徑)。
當兩個或兩個以上的程式碼路徑要競爭共享資源(變數/緩衝區/裝置)時,此時程式碼段A就是臨界區。
概念2:互斥機制
前面我們知道,訪問共享資源的程式碼區叫做臨界區,這裡的共享資源可能被多個執行緒需要,但這些共享資源又不能被同時訪問,因此臨界區需要以某種互斥機制加以保護,以確保共享資源被互斥訪問。
我們可以編造一個故事,這樣也方便大家理解:
很久很久以前,有一個神醫,他的醫術非常高明,以至於每個人都想親自上門拜訪他,找他看病。
但這人脾氣特別不好,還有個特別的習慣,就是一次只能給一個病人看病,其他人想要得到他的醫治必須得安安靜靜的排隊,要是多一個傻逼闖進來,他就破罐破摔,撒手不幹了。
於是,為了防止其他病人的干擾,讓神醫可以專心為眼前的病人醫治,人們預設的排起隊來,還給他提供了一種保護機制,免得一群愣頭愣腦的傻逼突然闖進去觸怒神醫的天威,這種保護就叫做互斥機制。
概念3:使用者空間與核心空間
為了安全考慮,Linux系統分為核心態和使用者態,分別執行在核心空間和使用者空間。核心態的程式可以執行特權指令,作業系統本身也在其中執行;使用者態則不允許直接訪問作業系統的核心資料、裝置等關鍵資源,必須先通過系統呼叫或者中斷進入核心態才可以訪問,當系統呼叫或中斷返回時,重新回到使用者空間執行。
這就像皇帝(核心態)和平民(使用者態),皇帝在上層社會(核心空間),手中握著帝國的大權,平民則安安分分的在底層世界(使用者空間)努力工作。突然有一天,平民遇見了一件實在不得已只能拜託皇帝給予他特權才敢去做的大事,那他就需要上書請求覲見皇帝。
但是皇帝日理萬機,怎麼可能接受那麼多平民的請求?!於是,這裡皇帝設定了一種機制,就是平民要想覲見皇帝,必須要通過官員(系統呼叫和中斷)的稽核和批准,然後才被允許等待上殿覲見。
當然,等解決了問題之後,平民還是平民,還是要安安分分的回到底層世界(使用者空間)努力工作。
二、Linux 互斥機制
如上所述,Linux 為保證共享資源的互斥訪問,提供了一種互斥機制,這裡的互斥機制有四種方式,分別是:中斷遮蔽、原子操作、自旋鎖及其各種衍生鎖和訊號量。
『使用者空間的互斥方式:訊號量
核心空間的互斥方式:中斷遮蔽、原子操作、自旋鎖及其各種衍生鎖』
(1)訊號量
在使用者空間只有程序的概念。當一個臨界區有多個使用者態程序競爭時,最好的方法是用訊號量保護這個臨界區。只有得到訊號量程序才能執行臨界區程式碼,當獲取不到訊號量時,程序進入休眠狀態。
因此,我們可以說,訊號量是程序級的互斥機制,它代表程序來爭奪共享資源,如果競爭失敗,就會發生程序上下文切換,當前程序進入睡眠狀態,CPU執行其他程序。由於程序上下文切換的開銷很大,因此,只有當程序佔用資源時間較長時,用訊號量才是最好的選擇。
此外,訊號量在SMP(對稱多處理器)系統同樣起作用。
(2)中斷遮蔽
中斷是一個完全非同步的事件,它的發生與正在執行的程序沒有任何關係,它沒有程序上下文切換。
CPU具備遮蔽中斷和開啟中斷的功能,這項功能可以保證正在執行的核心執行路徑不被中斷處理程式搶佔,防止竟態的產生。
但是,核心的正常執行依賴於中斷機制。在遮蔽中斷期間,任何中斷都無法得到處理,而必須等待遮蔽解除。因此長時間遮蔽中斷對核心的執行起到很大的影響,其後果可能導致資料丟失,甚至系統崩潰。
實際情況是:在中斷服務全過程遮蔽中斷會丟失中斷;如果開中斷,又容易引起互斥問題。為了解決這個問題,Linux 把中斷分為前半段TH(Top Half)和後半段BH(Bottom Half)。TH 遮蔽中斷,執行一些少量的關鍵性動作;BH 可以開中斷,允許中斷延遲執行。
(3)原子操作
指在執行過程中不會被別的程式碼路徑所中斷的操作,晶片級的原子操作一般都表現為一條彙編指令。
Linux 核心提供了兩類函式來實現核心中的原子操作,分別是整型原子操作和位原子操作。它們的共同點是所有的操作都是原子的,核心可以安全的呼叫它們而不被中斷,而且它們都依賴底層CPU的原子操作實現,因此所有的這些函式都是與CPU架構相關的。
(4)自旋鎖
自旋鎖是為實現保護共享資源而提出一種鎖機制。
自旋鎖的原理:一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,並且在任何時刻最多隻能有一個執行單元獲得鎖;而在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將一直迴圈在那裡,直到該自旋鎖的保持者釋放了鎖,"自旋"一詞就是因此而得名。
事實上,自旋鎖的初衷是:在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的執行緒在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖被持有的時間不應該過長。如果需要長時間鎖定的話, 最好使用訊號量。
三、訊號量與自旋鎖比較
訊號量和讀寫訊號量適合於保持時間較長的情況,它們會導致呼叫者睡眠,因此只能在程序上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。
如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。當然,如果一定要使用訊號量,則只能通過down_trylock()方式進行,不能獲取就立即返回以避免阻塞。
自旋鎖保持期間是搶佔失效的,而訊號量和讀寫訊號量保持期間是可以被搶佔的。自旋鎖只有在核心可搶佔或SMP的情況下才真正需要,在單CPU且不可搶佔的核心下,自旋鎖的所有操作都是空操作。
訊號量所保護的臨界區可包含可能引起阻塞的程式碼,而自旋鎖則絕對要避免用來保護包含這樣程式碼的臨界區。因為阻塞意味著要發生程序上下文切換,而如果程序被切換出去後,另一個程序企圖獲得本自旋鎖,就會造成死鎖。