【原創】Linux Mutex機制分析

来源:https://www.cnblogs.com/LoyenWang/archive/2020/05/04/12826811.html
-Advertisement-
Play Games

背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 概述 互斥鎖是Linux內核中用於互斥操作的一種同步原語; 互斥鎖是一種休眠鎖,鎖爭用時可能存在進程 ...


背景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

  • Mutex互斥鎖是Linux內核中用於互斥操作的一種同步原語;
  • 互斥鎖是一種休眠鎖,鎖爭用時可能存在進程的睡眠與喚醒,context的切換帶來的代價較高,適用於加鎖時間較長的場景;
  • 互斥鎖每次只允許一個進程進入臨界區,有點類似於二值信號量;
  • 互斥鎖在鎖爭用時,在鎖被持有時,選擇自選等待,而不立即進行休眠,可以極大的提高性能,這種機制(optimistic spinning)也應用到了讀寫信號量上;
  • 互斥鎖的缺點是互斥鎖對象的結構較大,會占用更多的CPU緩存和記憶體空間;
  • 與信號量相比,互斥鎖的性能與擴展性都更好,因此,在內核中總是會優先考慮互斥鎖;
  • 互斥鎖按為了提高性能,提供了三條路徑處理:快速路徑,中速路徑,慢速路徑;

前戲都已經講完了,來看看實際的實現過程吧。

2. optimistic spinning

2.1 MCS鎖

  • 上文中提到過Mutex在實現過程中,採用了optimistic spinning自旋等待機制,這個機制的核心就是基於MCS鎖機制來實現的;
  • MCS鎖機制是由John Mellor CrummeyMichael Scott在論文中《algorithms for scalable synchronization on shared-memory multiprocessors》提出的,並以他倆的名字來命名;
  • MCS鎖機制要解決的問題是:在多CPU系統中,自旋鎖都在同一個變數上進行自旋,在獲取鎖時會將包含鎖的cache line移動到本地CPU,這種cache-line bouncing會很大程度影響性能;
  • MCS鎖機制的核心思想:每個CPU都分配一個自旋鎖結構體,自旋鎖的申請者(per-CPU)在local-CPU變數上自旋,這些結構體組建成一個鏈表,申請者自旋等待前驅節點釋放該鎖;
  • osq(optimistci spinning queue)是基於MCS演算法的一個具體實現,並經過了迭代優化;

2.2 osq流程分析

optimistic spinning,樂觀自旋,到底有多樂觀呢?當發現鎖被持有時,optimistic spinning相信持有者很快就能把鎖釋放,因此它選擇自旋等待,而不是睡眠等待,這樣也就能減少進程切換帶來的開銷了。

看一下數據結構吧:

osq_lock如下:

  • osq加鎖有幾種情況:
    1. 無人持有鎖,那是最理想的狀態,直接返回;
    2. 有人持有鎖,將當前的Node加入到OSQ隊列中,在沒有高優先順序任務搶占時,自旋等待前驅節點釋放鎖;
    3. 自旋等待過程中,如果遇到高優先順序任務搶占,那麼需要做的事情就是將之前加入到OSQ隊列中的當前節點,從OSQ隊列中移除,移除的過程又分為三個步驟,分別是處理prev前驅節點的next指針指向、當前節點Node的next指針指向、以及將prev節點與next後繼節點連接;
  • 加鎖過程中使用了原子操作,來確保正確性;

osq_unlock如下:

  • 解鎖時也分為幾種情況:
    1. 無人爭用該鎖,那直接可以釋放鎖;
    2. 獲取當前節點指向的下一個節點,如果下一個節點不為NULL,則將下一個節點解鎖;
    3. 當前節點的下一個節點為NULL,則調用osq_wait_next,來等待獲取下一個節點,併在獲取成功後對下一個節點進行解鎖;
  • 從解鎖的情況可以看出,這個過程相當於鎖的傳遞,從上一個節點傳遞給下一個節點;

在加鎖和解鎖的過程中,由於可能存在操作來更改osq隊列,因此都調用了osq_wait_next來獲取下一個確定的節點:

3. mutex

3.1 數據結構

終於來到了主題了,先看一下數據結構:

struct mutex {
	atomic_long_t		owner;           //原子計數,用於指向鎖持有者的task struct結構
	spinlock_t		wait_lock;              //自旋鎖,用於wait_list鏈表的保護操作
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */        //osq鎖
#endif
	struct list_head	wait_list;          //鏈表,用於管理所有在該互斥鎖上睡眠的進程
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

在使用mutex時,有以下幾點需要註意的:

  • 一次只能有一個進程能持有互斥鎖;
  • 只有鎖的持有者能進行解鎖操作;
  • 禁止多次解鎖操作;
  • 禁止遞歸加鎖操作;
  • mutex結構只能通過API進行初始化;
  • mutex結構禁止通過memset或者拷貝來進行初始化;
  • 已經被持有的mutex鎖禁止被再次初始化;
  • mutex不允許在硬體或軟體上下文(tasklets, timer)中使用;

3.2 加鎖流程分析

mutex_lock加鎖來看一下大概的流程:

  • mutex_lock為了提高性能,分為三種路徑處理,優先使用快速和中速路徑來處理,如果條件不滿足則會跳轉到慢速路徑來處理,慢速路徑中會進行睡眠和調度,因此開銷也是最大的。

3.2.1 fast-path

  • 快速路徑是在__mutex_trylock_fast中實現的,該函數的實現也很簡單,直接調用atomic_long_cmpxchg_release(&lock->owner, 0UL, curr)函數來進行判斷,如果lock->owner == 0表明鎖未被持有,將curr賦值給lock->owner標識curr進程持有該鎖,並直接返回;
  • lock->owner不等於0,表明鎖被持有,需要進入下一個路徑來處理了;

3.2.2 mid-path

  • 中速路徑和慢速路徑的處理都是在__mutex_lock_common中實現的;
  • __mutex_lock_common的傳入參數為(lock, TASK_INTERRUPTIBLE, 0, NULL, _RET_IP_, false),該函數中很多路徑覆蓋不到,接下來的分析也會剔除掉無效代碼;

中速路徑的核心代碼如下:

  • 當發現mutex鎖的持有者正在運行(另一個CPU)時,可以不進行睡眠調度,而可以選擇自選等待,當鎖持有者正在運行時,它很有可能很快會釋放鎖,這個就是樂觀自旋的原因;

  • 自旋等待的條件是持有鎖者正在臨界區運行,自旋等待才有價值;

  • __mutex_trylock_or_owner函數用於嘗試獲取鎖,如果獲取失敗則返回鎖的持有者。互斥鎖的結構體中owner欄位,分為兩個部分:1)鎖持有者進程的task_struct(由於L1_CACHE_BYTES對齊,低位比特沒有使用);2)MUTEX_FLAGS部分,也就是對應低三位,如下:

    1. MUTEX_FLAG_WAITERS:比特0,標識存在非空等待者鏈表,在解鎖的時候需要執行喚醒操作;
    2. MUTEX_FLAG_HANDOFF:比特1,表明解鎖的時候需要將鎖傳遞給頂部的等待者;
    3. MUTEX_FLAG_PICKUP:比特2,表明鎖的交接準備已經做完了,可以等待被取走了;
  • mutex_optimistic_spin用於執行樂觀自旋,理想的情況下鎖持有者執行完釋放,當前進程就能很快的獲取到鎖。實際需要考慮,如果鎖的持有者如果在臨界區被調度出去了,task_struct->on_cpu == 0,那麼需要結束自旋等待了,否則豈不是傻傻等待了。

    1. mutex_can_spin_on_owner:進入自旋前檢查一下,如果當前進程需要調度,或者鎖的持有者已經被調度出去了,那麼直接就返回了,不需要做接下來的osq_lock/oqs_unlock工作了,節省一些額外的overhead;
    2. osq_lock用於確保只有一個等待者參與進來自旋,防止大量的等待者蜂擁而至來獲取互斥鎖;
    3. for(;;)自旋過程中調用__mutex_trylock_or_owner來嘗試獲取鎖,獲取到後皆大歡喜,直接返回即可;
    4. mutex_spin_on_owner,判斷不滿足自旋等待的條件,那麼返回,讓我們進入慢速路徑吧,畢竟不能強求;

3.2.3 slow-path

慢速路徑的主要代碼流程如下:

  • for(;;)部分的流程可以看到,當沒有獲取到鎖時,會調用schedule_preempt_disabled將本身的任務進行切換出去,睡眠等待,這也是它慢的原因了;

3.3 釋放鎖流程分析

  • 釋放鎖的流程相對來說比較簡單,也分為快速路徑與慢速路徑,快速路徑只有在調試的時候打開;
  • 慢速路徑釋放鎖,針對三種不同的MUTEX_FLAG來進行判斷處理,並最終喚醒等待在該鎖上的任務;

參考

Generic Mutex Subsystem
MCS locks and qspinlocks

歡迎關註個人公眾號,持續分享內核相關文章


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、命令行的開始 首先介紹幾個常用到的命令 dotnet --info :查看電腦上環境。 dotnet new --list:查看腳手架模板列表 dotnet new console -n newHelloworld:創建一個名為newHelloworld的控制台程式 dotnet build:切 ...
  • 上一章分析了WPF元素的內部工作元素——允許每個元素插入到WPF佈局系統的MeasureOverride()和ArrangeOverride()方法中。本章將進一步深入分析和研究元素如何渲染自身。 大多數WPF元素通過組合方式創建可視化外觀。換句話說,典型的元素通過其他更基礎的元素進行構建。例如,使 ...
  • 這個例子來自書上。 記錄過程。 主要是數學上極坐標,WPF中的測量過程 簡單來說在一個具有固定軸的坐標系內,一個由原點射出的向量並與固定軸有一定角度且在向量上確定長度的這麼個東西。 可以參考: 知乎https://www.zhihu.com/question/318613418/answer/640 ...
  • 一、首先去https://www.docker.com/products/docker-desktop下載Windows版本的Docker Desktop並安裝(需要win10專業版以上操作系統,並啟用CPU虛擬化和安裝Hvper-V)。 二、新建一個.NetCore3.1的API項目,在創建的時候 ...
  • 1.目錄操作 基礎目錄操作 linux 沒有類似windows一樣的盤符之分,所有的目錄都是以“根‘ / ’”開始,linux 是多用戶登陸, 在 /home/ 的目錄下,存放著已添加用戶的用戶目錄。每個用戶一個文件夾,文件夾名為用戶名 // 下文中 $ 表示終端的提示符,不包含在命令當中 $ pw ...
  • Network namespace 在邏輯上是網路堆棧的一個副本,它有自己的路由、防火牆規則和網路設備。預設情況下,子進程繼承其父進程的 network namespace。也就是說,如果不顯式創建新的 network namespace,所有進程都從 init 進程繼承相同的預設 network ...
  • 微服務,Kubernetes,用minikube單機部署,運維部署,方便的安裝中間件,本地測試開發,節省時間 ...
  • 在記憶體系統初始化過程中,有如下代碼: 這裡,我們看到了神秘的swapper_pg_dir,全局搜索一下,發現了 在head_32.S中,定義瞭如下的BSS段,BSS段是在內核映像文件中不占空間,但是在內核被載入到記憶體時,會保留相應的空間。 在BSS段,一共保留了4個頁面的空間,分別用initial_ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...