為了避免併發,防止競爭。核心提供了一組同步方法來提供對共享資料的保護。 我們的重點不是介紹這些方法的詳細用法,而是強調為什麼使用這些方法和它們之間的差別。
linux使用的同步機制可以說從2.0到2.6以來不斷發展完善。從最初的原子操作,到後來的訊號量,從大核心鎖到今天的自旋鎖。這些同步機制的發展伴隨linux從單處理器到對稱多處理器的過度;伴隨著從非搶占核心到搶占核心的過度。鎖機制越來越有效,也越來越複雜。
目前來說核心中原子操作多用來做計數使用,其它情況最常用的是兩種鎖以及它們的變種:乙個是自旋鎖,另乙個是訊號量。我們下面就來著重介紹一下這兩種鎖機制。
自旋鎖是專為防止多處理器併發而引入的一種鎖,它在核心中大量應用於中斷處理等部分(對於單處理器來說,防止中斷處理中的併發可簡單採用關閉中斷的方式,不需要自旋鎖)。
自旋鎖最多只能被乙個核心任務持有,如果乙個核心任務試圖請求乙個已被爭用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙迴圈——旋轉——等待鎖重新可用。要是鎖未被爭用,請求它的核心任務便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於乙個的核心任務同時進入臨界區,因此這種鎖可有效地避免多處理器上併發執行的核心任務競爭共享資源。
事實上,自旋鎖的初衷就是:在短期間內進行輕量級的鎖定。乙個被爭用的自旋鎖使得請求它的執行緒在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用訊號量。
自旋鎖的基本形式如下:
spin_lock(&mr_lock);
// ...
... 臨界區
spin_unlock(&mr_lock);
因為自旋鎖在同一時刻只能被最多乙個核心任務持有,所以乙個時刻只有乙個執行緒允許存在於臨界區中。這點很好地滿足了對稱多處理機器需要的鎖定服務。在單處理器上,自旋鎖僅僅當作乙個設定核心搶占的開關。如果核心搶占也不存在,那麼自旋鎖會在編譯時被完全剔除出核心。
簡單的說,自旋鎖在核心中主要用來防止多處理器中併發訪問臨界區,防止核心搶占造成的競爭。另外自旋鎖不允許任務睡眠(持有自旋鎖的任務睡眠會造成自死鎖——因為睡眠有可能造成持有鎖的核心任務被重新排程,而再次申請自己已持有的鎖),它能夠在中斷上下文中使用。
死鎖:假設有乙個或多個核心任務和乙個或多個資源,每個核心都在等待其中的乙個資源,但所有的資源都已經被占用了。這便會發生所有核心任務都在相互等待,但它們永遠不會釋放已經占有的資源,於是任何核心任務都無法獲得所需要的資源,無法繼續執行,這便意味著死鎖發生了。自死瑣是說自己占有了某個資源,然後自己又申請自己已占有的資源,顯然不可能再獲得該資源,因此就自縛手腳了。
linux中的訊號量是一種睡眠鎖。如果有乙個任務試圖獲得乙個已被持有的訊號量時,訊號量會將其推入等待佇列,然後讓其睡眠。這時處理器獲得自由去執行其它**。當持有訊號量的程序將訊號量釋放後,在等待佇列中的乙個任務將被喚醒,從而便可以獲得這個訊號量。
訊號量的睡眠特性,使得訊號量適用於鎖會被長時間持有的情況;只能在程序上下文中使用,因為中斷上下文中是不能被排程的;另外當**持有訊號量時,不可以再持有自旋鎖。
訊號量基本使用形式為:
static declare_mutex(mr_sem); // 宣告互斥訊號量
if(down_interruptible(&mr_sem))
// 可被中斷的睡眠,當訊號來到,睡眠的任務被喚醒
// ...
... 臨界區
up(&mr_sem);
雖然聽起來兩者之間的使用條件複雜,其實在實際使用中訊號量和自旋鎖並不易混淆。注意以下原則:
如果**需要睡眠——這往往是發生在和使用者空間同步時——使用訊號量是唯一的選擇。由於不受睡眠的限制,使用訊號量通常來說更加簡單一些。如果需要在自旋鎖和訊號量中作選擇,應該取決於鎖被持有的時間長短。理想情況是所有的鎖都應該盡可能短的被持有,但是如果鎖的持有時間較長的話,使用訊號量是更好的選擇。另外,訊號量不同於自旋鎖,它不會關閉核心搶占,所以持有訊號量的**可以被搶占。這意味者訊號量不會對影響排程反應時間帶來負面影響。
所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位,因此這裡的原子實際是使用了物理學裡的物質微粒的概念。
原子操作需要硬體的支援,因此是架構相關的,其api和原子型別的定義都定義在核心原始碼樹的include/asm/atomic.h檔案中,它們都使用組合語言實現,因為c語言並不能實現這樣的操作。
原子操作通常用於實現資源的引用計數,在tcp/ip協議棧的ip碎片處理中,就使用了引用計數,碎片佇列結構struct ipq描述了乙個ip碎片,欄位refcnt就是引用計數器,它的型別為atomic_t,當建立ip碎片時(在函式ip_frag_create中),使用atomic_set函式把它設定為1,當引用該ip碎片時,就使用函式atomic_inc把引用計數加1。
自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在那裡看是否該自旋鎖的保持者已經釋放了鎖,」自旋」一詞就是因此而得名。其作用是為了解決某項資源的互斥使用。因為自旋鎖不會引起呼叫者睡眠,所以自旋鎖的效率遠高於互斥鎖。雖然它的效率比互斥鎖高,但是它也有些不足之處:
自旋鎖一直占用cpu,他在未獲得鎖的情況下,一直執行--自旋,所以占用著cpu,如果不能在很短的時間內獲得鎖,這無疑會使cpu效率降低。
在用自旋鎖時有可能造成死鎖,當遞迴呼叫時有可能造成死鎖,呼叫有些其他函式也可能造成死鎖,如 copy_to_user()、copy_from_user()、kmalloc()等。
互斥鎖主要用於實現核心中的互斥訪問功能。核心互斥鎖是在原子 api 之上實現的,但這對於核心使用者是不可見的。對它的訪問必須遵循一些規則:同一時間只能有乙個任務持有互斥鎖,而且只有這個任務可以對互斥鎖進行 解鎖。互斥鎖不能進行遞迴鎖定或解鎖。乙個互斥鎖物件必須通過其api初始化,而不能使用memset或複製初始化。乙個任務在持有互斥鎖的時候是不能結束的。互斥鎖所使用的記憶體區域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。並且互斥鎖不能用於中斷上下文。但是互斥鎖比當前的核心訊號量選項更快,並且更加緊湊,因此如果它們滿足您的需 求,那麼它們將是您明智的選擇。
需求 建議的加鎖方法
低開銷加鎖 優先使用自旋鎖
短期鎖定 優先使用自旋鎖
長期加鎖 優先使用訊號量
中斷上下文中加鎖 使用自旋鎖
持有鎖是需要睡眠、排程 使用訊號量
自旋鎖,互斥鎖,訊號量
自旋鎖,互斥鎖,訊號量 樂觀鎖和悲觀鎖只是一種理論,是從思想上劃分的。自旋鎖和互斥鎖是應用層確確實實的鎖,用於同步訪問控制 如果一定要劃分,從只有乙個執行緒可以擁有鎖來說,我覺得自旋鎖和互斥鎖應該都屬於悲觀鎖,因為一般的應用不需要支援事物回滾的操作。但是沈詢的直播中說,互斥鎖屬於悲觀鎖 sleep ...
訊號量,互斥鎖,自旋鎖
個人理解 訊號量 程序間的通訊機制 單一個數的訊號 與訊息郵箱,訊息佇列,機理類同,量不同,用訊號量肯定掉cpu 自旋鎖 保護區域不掉cpu,持續查詢,等待 不可用時域長狀態 切記 時域範圍 在程序間的通訊機制函式狀態 鎖 0 互斥鎖與自旋鎖 互斥鎖 執行緒會從sleep 加鎖 running 解鎖...
訊號量,互斥鎖
注 摘自 程式設計師的自我修養 相關章節。關鍵字 執行緒同步 原子操作 鎖 二元訊號量 訊號量 互斥量 臨界區 讀寫鎖 條件變數 原子操作 共享資料 全域性變數或堆變數 的自增 操作在多執行緒環境下會出現錯誤是因為這個操作 一條c語句 被編譯為彙編 後不止一條指令,因此在執行的時候可能執行了一半就被...