Linux內核設計與實現——內核同步方法

主要內容

  1. 原子操作
  2. 自旋鎖
  3. 讀寫自旋鎖
  4. 信號量
  5. 讀寫信號量
  6. 互斥鎖
  7. 完成變量
  8. 大內核鎖
  9. 順序鎖
  10. 禁止搶占
  11. 順序和屏障

1. 原子操作

原子操作可以保證指令以原子的方式執(zhí)行,不會被打斷。
內核提供了對整數,對位的原子操作接口
特殊的atomic_t類型,32位int的低8位嵌入了一個鎖

2. 自旋鎖

原子操作只能用于臨界區(qū)只有一個變量的情況,實際應用中,臨界區(qū)的情況要復雜的多。
對于復雜的臨界區(qū),linux內核中也提供了多種同步方法,自旋鎖就是其中一種。

自旋鎖的特點就是當一個線程獲取了鎖之后,其他試圖獲取這個鎖的線程一直在循環(huán)等待獲取這個鎖,直至鎖重新可用。
由于線程實在一直循環(huán)的獲取這個鎖,所以會造成CPU處理時間的浪費,因此最好將自旋鎖用于能很快處理完的臨界區(qū)。

注意:

  1. 自旋鎖是不可遞歸的,遞歸的請求同一個自旋鎖會自己鎖死自己。
  2. 線程獲取自旋鎖之前,要禁止當前處理器上的中斷。(防止獲取鎖的線程和中斷形成競爭條件,導致死鎖)
    比如:當前線程獲取自旋鎖后,在臨界區(qū)中被中斷處理程序打斷,中斷處理程序正好也要獲取這個鎖,
    于是中斷處理程序會等待當前線程釋放鎖,而當前線程也在等待中斷執(zhí)行完后再執(zhí)行臨界區(qū)和釋放鎖的代碼。

中斷處理下半部:

  1. 下半部處理和進程上下文共享數據時,由于下半部的處理可以搶占進程上下文的代碼,
    所以進程上下文在對共享數據加鎖前要禁止下半部的執(zhí)行,解鎖時再允許下半部的執(zhí)行。
  2. 中斷處理程序(上半部)和下半部處理共享數據時,由于中斷處理(上半部)可以搶占下半部的執(zhí)行,
    所以下半部在對共享數據加鎖前要禁止中斷處理(上半部),解鎖時再允許中斷的執(zhí)行。
  3. 同一種tasklet不能同時運行,所以同類tasklet中的共享數據不需要保護。
  4. 不同類tasklet中共享數據時,其中一個tasklet獲得鎖后,不用禁止其他tasklet的執(zhí)行,因為同一個處理器上不會有tasklet相互搶占的情況
  5. 同類型或者非同類型的軟中斷在共享數據時,也不用禁止下半部,因為同一個處理器上不會有軟中斷互相搶占的情況

3. 讀寫自旋鎖

用于生產者消費者類型的讀寫自旋鎖

讀鎖之間共享,寫鎖之間互斥

4.信號量

信號量也是一種鎖,和自旋鎖不同的是,線程獲取不到信號量的時候,不會像自旋鎖一樣循環(huán)的去試圖獲取鎖,而是進入睡眠,直至有信號量釋放出來時,才會喚醒睡眠的線程,進入臨界區(qū)執(zhí)行。

由于使用信號量時,線程會睡眠,所以等待的過程不會占用CPU時間。所以信號量適用于等待時間較長的臨界區(qū)。
信號量消耗的CPU時間的地方在于使線程睡眠和喚醒線程,如果 (使線程睡眠 + 喚醒線程)的CPU時間 > 線程自旋等待的CPU時間,那么可以考慮使用自旋鎖。

信號量有二值信號量和計數信號量2種,其中二值信號量比較常用。

  1. 二值信號量表示信號量只有2個值,即0和1。信號量為1時,表示臨界區(qū)可用,信號量為0時,表示臨界區(qū)不可訪問。
    二值信號量表面看和自旋鎖很相似,區(qū)別在于爭用自旋鎖的線程會一直循環(huán)嘗試獲取自旋鎖,
    而爭用信號量的線程在信號量為0時,會進入睡眠,信號量可用時再被喚醒。
  2. 計數信號量有個計數值,比如計數值為5,表示同時可以有5個線程訪問臨界區(qū)。

5. 讀寫信號量

讀寫信號量和信號量之間的關系 與 讀寫自旋鎖和普通自旋鎖之間的關系 差不多。

6. 互斥鎖

互斥體也是一種可以睡眠的鎖,相當于二值信號量,只是提供的API更加簡單,使用的場景也更嚴格一些,如下所示:

  1. mutex的計數值只能為1,也就是最多只允許一個線程訪問臨界區(qū)
  2. 在同一個上下文中上鎖和解鎖
  3. 不能遞歸的上鎖和解鎖
  4. 持有個mutex時,進程不能退出
  5. mutex不能在中斷或者下半部中使用,也就是mutex只能在進程上下文中使用
  6. mutex只能通過官方API來管理,不能自己寫代碼操作它

如何選擇?

在面對互斥體和信號量的選擇時,只要滿足互斥體的使用場景就盡量優(yōu)先使用互斥體。
在面對互斥體和自旋鎖的選擇時,參見下表:

需求 建議的加鎖方法
低開銷加鎖 優(yōu)先使用自旋鎖
短期鎖定 優(yōu)先使用自旋鎖
長期加鎖 優(yōu)先使用互斥體
中斷上下文中加鎖 使用自旋鎖
持有鎖需要睡眠 使用互斥體

7. 完成變量

如果一個任務要執(zhí)行一些工作時,另一個任務就會在完成變量上等待。當這個任務完成工作后,會使用完成變量去喚醒在等待的任務。
實現機制類似于信號量。

8. 大內核鎖

不再使用,只存在于一些遺留代碼

9. 順序鎖

為讀寫共享數據提供了一種簡單的實現機制。

前提到的讀寫自旋鎖和讀寫信號量,在讀鎖被獲取之后,寫鎖是不能再被獲取的,也就是說,必須等所有的讀鎖釋放后,才能對臨界區(qū)進行寫入操作。
順序鎖則與之不同,讀鎖被獲取的情況下,寫鎖仍然可以被獲取。
使用順序鎖的讀操作在讀之前和讀之后都會檢查順序鎖的序列值,如果前后值不符,則說明在讀的過程中有寫的操作發(fā)生,那么讀操作會重新執(zhí)行一次,直至讀前后的序列值是一樣的。

10. 禁止搶占

其實使用自旋鎖已經可以防止內核搶占了,但是有時候僅僅需要禁止內核搶占,不需要像自旋鎖那樣連中斷都屏蔽掉。

這時候就需要使用禁止內核搶占的方法了:

方法 描述
preempt_disable() 增加搶占計數值,從而禁止內核搶占
preempt_enable() 減少搶占計算,并當該值降為0時檢查和執(zhí)行被掛起的需調度的任務
preempt_enable_no_resched() 激活內核搶占但不再檢查任何被掛起的需調度的任務
preempt_count() 返回搶占計數

11. 順序和屏障

一種同步機制(又稱柵欄,關卡),用于對一組線程進行協(xié)調,所有線程到達一個匯合點后再一起向前推進

總結

同步方法總結.png
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容