蛻變成蝶:Linux裝置驅動中的併發控制

李輝發表於2016-04-15

併發和競爭發生在兩類體系中:

  •     對稱多處理器(SMP)的多個CPU
  •     核心可搶佔的單CPU系統

訪問共享資源的程式碼區域稱為臨界區(critical sections),臨界區需要以某種互斥機制加以保護。在驅動程式中,當多個執行緒同時訪問相同的資源(critical sections)時(驅動程式中的全域性變數是一種典型的共享資源),可能會引發”競態”,因此我們必須對共享資源進行併發控制。Linux核心中解決併發控制的方法又中斷遮蔽、原子操作、自旋鎖、訊號量。(後面為主要方式)

中斷遮蔽:

使用方法

local_irq_disable/enable只能禁止/使能本CPU內的中斷,不能解決SMP多CPU引發的競態,故不推薦使用,其適宜於自旋鎖聯合使用。

原子操作:

原子操作是一系列的不能被打斷的操作。linux核心提供了一系列的函式來實現核心中的原子操作,這些函式分為2類,分別針對位和整型變數進行原子操作。

實現整型原子操作的步驟如下:

1.定義原子變數並設定變數值

2.獲取原子變數的值

3.原子變數加減操作

4.原子變數自增/自減

5.操作並測試:對原子變數執行自增、自減後(沒有加)測試其是否為0,如果為0返回true,否則返回false。

6.操作並返回

實現 位原子操作如下:

下面來舉一個例項,是原子變數使用例項,使裝置只能被一個程式開啟:

我要著重談一下:

自旋鎖VS訊號量

從嚴格意義上來說,訊號量和自旋鎖屬於不同層次的互斥手段,前者的實現依賴於後者,在多CPU中需要自旋鎖來互斥。訊號量是程式級的,用於多個程式之間對資源的互斥,雖然也在核心中,但是該核心執行路徑是以程式的身份,代表程式來爭奪資源的。如果競爭失敗,會切換到下個程式,而當前程式進入睡眠狀態,因此,當程式佔用資源時間較長時,用訊號量是較好的選擇。

當所要保護的臨界訪問時間比較短時,用自旋鎖是非常方便的,因為它節省了上下文切換的時間。但是CPU得不到自旋鎖是,CPU會原地打轉,直到其他執行單元解鎖為止,所以要求鎖不能在臨界區裡停留時間過長。

自旋鎖的操作步驟:

自旋鎖持有期間核心的搶佔將被禁止。 自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶佔程式打擾,但是得到鎖的程式碼路徑在執行臨界區的時候還可能受到中斷和底半部(BH)的影響。為防止這種影響,需要用到自旋鎖的衍生:

注意:自旋鎖實際上是忙等待,只有在佔用鎖的時間極短的情況下,使用自旋鎖才是合理的自旋鎖可能導致死鎖:遞迴使用一個自旋鎖或程式獲得自旋鎖後阻塞。

例子:

自旋鎖不關心鎖定的臨界區究竟是如何執行的。不管是讀操作還是寫操作,實際上,對共享資源進行讀取的時候是應該可以允許多個執行單元同時訪問的,那麼這樣的話,自旋鎖就有了弊端。於是便衍生出來一個讀寫鎖。它保留了自旋的特性,但在對操作上面可以允許有多個單元程式同時操作。當然,讀和寫的時候不能同時進行。

現在又有問題了,如果我第一個程式寫共享資源,第二個程式讀的話,一旦寫了,那麼就讀不到了,可能寫的東西比較多,但是第二個程式讀很小,那麼能不能第一個程式寫的同時,我第二個程式讀呢?
當然可以,那麼引出了順序鎖的概念。都是一樣的操作。

讀寫自旋鎖(rwlock)允許讀的併發。在寫操作方面,只能最多有一個寫程式,在讀操作方面,同時可以有多個讀執行單元。當然,讀和寫也不能同時進行。

讀寫自旋鎖一般用法:

順序鎖(seqlock):

順序鎖是對讀寫鎖的一種優化,若使用順序鎖,讀與寫操作不阻塞,只阻塞同種操作,即讀與讀/寫與寫操作。

寫執行單元的操作順序如下:

讀執行單元的操作順序如下:

RCU(Read-Copy Update 讀-拷貝-更新)可看作讀寫鎖的高效能版本,既允許多個讀執行單元同時訪問被保護的資料,又允許多個讀執行單元和多個寫執行單元同時訪問被保護的資料。但是RCU不能替代讀寫鎖。因為如果寫操作比較多時,對讀執行單元的效能提高不能彌補寫執行單元導致的損失。因為使用RCU時,寫執行單元之間的同步開銷會比較大,它需要延遲資料結構的釋放,複製被修改的資料結構,它也必須使用某種鎖機制同步並行的其他寫執行單元的修改操作。

具體操作:略

訊號量的使用

訊號量(semaphore)與自旋鎖相同,只有得到訊號量才能執行臨界區程式碼,但,當獲取不到訊號量時,程式不會原地打轉而是進入休眠等待狀態。

相同點:只有得到訊號量的程式才能執行臨界區的程式碼。(linux自旋鎖和訊號量鎖採用的都是“獲得鎖-訪問臨界區-釋放鎖”,可以稱為“互斥三部曲”,實際存在於幾乎所有多工作業系統中)

不同點:當獲取不到訊號量時,程式不會原地打轉而是進入休眠等待狀態。

訊號量的操作:

訊號量用於同步時只能喚醒一個執行單元,而完成量(completion)用於同步時可以喚醒所有等待的執行單元。

自旋鎖與互斥鎖的選擇

  • 當鎖 不能被獲取到時,使用訊號量的開銷是程式上下文切換時間Tsw,使用自旋鎖的開始是等待獲取自旋鎖的時間Tcs,若Tcs比較小,則應使用自旋鎖,否則應使用訊號量
  • 訊號量鎖保護的臨界區可以包含引起阻塞的程式碼,而自旋鎖則卻對要避免使用包含阻塞的臨界區程式碼,否則很可能引發鎖陷阱
  • 訊號量存在於程式上下文,因此,如果被保護的共享資源需要在中斷或軟中斷情況下使用,則在訊號量和自旋鎖之間只能選擇自旋鎖。當然,如果一定要使用訊號量,則只能通過down_trylock()方式進行,不能獲取就立即返回以避免阻塞。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

蛻變成蝶:Linux裝置驅動中的併發控制 蛻變成蝶:Linux裝置驅動中的併發控制

相關文章