1. 程式人生 > >【原創】Linux RCU原理剖析(二)-漸入佳境

【原創】Linux RCU原理剖析(二)-漸入佳境

背景

  • 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. 概述

我會假設你已經看過了《Linux RCU原理剖析(一)-初窺門徑》

本文將進一步去探索下RCU背後的機制。

2. 基礎概念

2.1 Grace Period

繼續貼出《Linux RCU原理剖析(一)-初窺門徑》中的圖片:

  • 中間的黃色部分代表的就是Grace Period
    ,中文叫做寬限期,從RemovalReclamation,中間就隔了一個寬限期;
  • 只有當寬限期結束後,才會觸發回收的工作,寬限期的結束代表著Reader都已經退出了臨界區,因此回收工作也就是安全的操作了;
  • 寬限期是否結束,與處理器的執行狀態檢測有關,也就是檢測靜止狀態Quiescent Status
  • RCU的效能與可擴充套件性依賴於它是否能有效的檢測出靜止狀態(Quiescent Status),並且判斷寬限期是否結束。

來一張圖:

2.2 Quiescent Status

Quiescent Status,用於描述處理器的執行狀態。當某個CPU正在訪問RCU保護的臨界區時,認為是活動的狀態,而當它離開了臨界區後,則認為它是靜止的狀態。當所有的CPU都至少經歷過一次QS後,寬限期將結束並觸發回收工作。

  • 在時鐘tick中檢測CPU處於使用者模式或者idle模式,則表明CPU離開了臨界區;
  • 在不支援搶佔的RCU實現中,檢測到CPU有context切換,就能表明CPU離開了臨界區;

3. 資料結構

  • RCU實際是一個大型的狀態機,它的資料結構維護著狀態,可以讓RCU讀者快速執行,同時也可以高效和靈活的處理RCU寫者請求的寬限期。
  • RCU的效能和可擴充套件性依賴於採用什麼機制來探測寬限期的結束;
  • RCU使用點陣圖cpumask去記錄CPU經歷靜止狀態,在經典RCU(Classic RCU)實現中,由於使用了全域性的cpumask點陣圖,當CPU數量很大時鎖爭用會帶來很大開銷(GP開始時設定對應位,GP結束時清除對應位),因此也促成了Tree RCU
    的誕生;
  • Tree RCU以樹形分層來組織CPU,將CPU分組,本小組的CPU爭用同一個鎖,當本小組的某個CPU經歷了一個靜止狀態QS後,將其對應的位從點陣圖清除,如果該小組最後一個CPU經歷完靜止狀態QS後,表明該小組全部經歷了CPU的QS狀態,那麼將上一層對應該組的位從點陣圖清除;
  • RCU有幾個關鍵的資料結構:struct rcu_statestruct rcu_nodestruct rcu_data

圖來了:

  • struct rcu_state:用於描述RCU的全域性狀態,它負責組織樹狀層級結構,系統中支援不同型別的RCU狀態:rcu_sched_statercu_bh_statercu_preempt_state
  • struct rcu_nodeTree RCU中的組織節點;
  • struct rcu_data:用於描述處理器的RCU狀態,每個CPU都維護一個數據,它歸屬於某一個struct rcu_nodestruct rcu_data檢測靜止狀態並進行處理,對應的CPU進行RCU回撥,__percpu的定義也減少了同步的開銷;

看到這種描述,如果還是在懵逼的狀態,那麼再來一張拓撲圖,讓真相更白一點:

  • 層狀樹形結構由struct rcu_node來組成,這些節點在struct rcu_state結構中是放置在陣列中的,由於struct rcu_node結構有父節點指標,因此可以構造樹形;
  • CPU分組後,對鎖的爭用就會大大減少,比如CPU0/CPU1就不需要和CPU6/CPU7去爭用鎖了,逐級以淘汰賽的形式向上;

關鍵點來了:Tree RCU使用rcu_node節點來構造層級結構,進而管理靜止狀態Quiescent State和寬限期Grace Period,靜止狀態資訊QS是從每個CPU的rcu_data往上傳遞到根節點的,而寬限期GP資訊是通過根節點從上往下傳遞的,當每個CPU經歷過一次QS狀態後,寬限期結束

關鍵欄位還是有必要介紹一下的,否則豈不是耍流氓?

struct rcu_state {
	struct rcu_node node[NUM_RCU_NODES];        // rcu_node節點陣列,組織成層級樹狀
	struct rcu_node *level[RCU_NUM_LVLS + 1];   //指向每層的首個rcu_node節點,陣列加1是為了消除編譯告警
	struct rcu_data __percpu *rda;		                //指向每個CPU的rcu_data例項
	call_rcu_func_t call;			                        //指向特定RCU型別的call_rcu函式:call_rcu_sched, call_rcu_bh等
	int ncpus;				                                // 處理器數量
    
   	unsigned long gpnum;			                //當前寬限期編號,gpnum > completed,表明正處在寬限期內
	unsigned long completed;		                //上一個結束的寬限期編號,如果與gpnum相等,表明RCU空閒 
    ...
        unsigned long gp_max;                                   //最長的寬限期時間,jiffies        
    ...
}

/*
 * Definition for node within the RCU grace-period-detection hierarchy.
 */
struct rcu_node {
    	raw_spinlock_t __private lock;	        //保護本節點的自旋鎖
     	unsigned long gpnum;			        //本節點寬限期編號,等於或小於根節點的gpnum
        unsigned long completed;		        //本節點上一個結束的寬限期編號,等於或小於根節點的completed
        unsigned long qsmask;                       //QS狀態點陣圖,某位為1,代表對應的成員沒有經歷QS狀態
        unsigned long qsmaskinit;                //正常寬限期開始時,QS狀態的初始值
    ...    
	int	grplo;		//該分組的CPU最小編號
	int	grphi;		//該分組的CPU最大編號
	u8	grpnum;		//該分組在上一層分組裡的編號
	u8	level;		//在樹中的層級,Root為0
    ...
    
        struct rcu_node *parent; //指向父節點
}

/* Per-CPU data for read-copy update. */
struct rcu_data {
	unsigned long	completed;	    //本CPU看到的已結束的寬限期編號
	unsigned long	gpnum;		    //本CPU看到的最高寬限期編號
	union rcu_noqs cpu_no_qs;       //記錄本CPU是否經歷QS狀態
	bool core_need_qs;		        //RCU需要本CPU上報QS狀態
	unsigned long grpmask;		//本CPU在分組的點陣圖中的掩碼
	struct rcu_segcblist;		        //回撥函式連結串列,用於存放call_rcu註冊的延後執行的回撥函式
    ...
}

4. RCU更新介面

從《Linux RCU原理剖析(一)-初窺門徑》的示例中,我們看到了RCU的寫端呼叫了synchronize_rcu/call_rcu兩種型別的介面,事實上Linux核心提供了三種不同型別的RCU,因此也對應了相應形式的介面。

來張圖:

  • RCU寫者,可以通過兩種方式來等待寬限期的結束,一種是呼叫同步介面等待寬限期結束,一種是非同步介面等待寬限期結束後再進行回撥處理,分別如上圖的左右兩側所示;
  • 從圖中的介面呼叫來看,同步介面中實際會去呼叫非同步介面,只是同步介面中增加了一個wait_for_completion睡眠等待操作,並且會將wakeme_after_rcu回撥函式傳遞給非同步介面,當寬限期結束後,在非同步介面中回調了wakeme_after_rcu進行喚醒處理;
  • 目前核心中提供了三種RCU:
    1. 可搶佔RCU:rcu_read_lock/rcu_read_unlock來界定區域,在讀端臨界區可以被其他程序搶佔;
    2. 不可搶佔RCU(RCU-sched)rcu_read_lock_sched/rcu_read_unlock_sched來界定區域,在讀端臨界區不允許其他程序搶佔;
    3. 關下半部RCU(RCU-bh)rcu_read_lock_bh/rcu_read_unlock_bh來界定區域,在讀端臨界區禁止軟中斷;
  • 從圖中可以看出來,不管是同步還是非同步介面,最終都是調到__call_rcu介面,它是介面實現的關鍵,所以接下來分析下這個函數了;

5. __call_rcu

函式的呼叫流程如下:

  • __call_rcu函式,第一個功能是註冊回撥函式,而回調的函式的維護是在rcu_data結構中的struct rcu_segcblist cblist欄位中;
  • rcu_accelerate_cbs/rcu_advance_cbs,實現中都是通過操作struct rcu_segcblist結構,來完成回撥函式的移動處理等;
  • __call_rcu函式第二個功能是判斷是否需要開啟新的寬限期GP;

連結串列的維護關係如下圖所示:

  • 實際的設計比較巧妙,通過一個連結串列來連結所有的回撥函式節點,同時維護一個二級指標陣列,用於將該連結串列進行分段,分別維護不同階段的回撥函式,回撥函式的移動方向如圖所示,關於回撥函式節點的處理都圍繞著這個圖來展開;

那麼通過__call_rcu註冊的這些回撥函式在哪裡呼叫呢?答案是在RCU_SOFTIRQ軟中斷中:

  • invoke_rcu_core時,在該函式中呼叫raise_softirq介面,從而觸發軟中斷回撥函式rcu_process_callbacks的執行;
  • 涉及到與寬限期GP相關的操作,在rcu_process_callbacks中會呼叫rcu_gp_kthread_wake喚醒核心執行緒,最終會在rcu_gp_kthread執行緒中執行;
  • 涉及到RCU註冊的回撥函式執行的操作,都在rcu_do_batch函式中執行,其中有兩種執行方式:1)如果不支援優先順序繼承的話,直接呼叫即可;2)支援優先順序繼承,在把回撥的工作放置在rcu_cpu_kthread核心執行緒中,其中核心為每個CPU都建立了一個rcu_cpu_kthread核心執行緒;

6. 寬限期開始與結束

既然涉及到寬限期GP的操作,都放到了rcu_gp_kthread核心執行緒中了,那麼來看看這個核心執行緒的邏輯操作吧:

  • 核心分別為rcu_preempt_state, rcu_bh_state, rcu_sched_state建立了核心執行緒rcu_gp_kthread
  • rcu_gp_kthread核心執行緒主要完成三個工作:1)建立新的寬限期GP;2)等待強制靜止狀態,設定超時,提前喚醒說明所有處理器經過了靜止狀態;3)寬限期結束處理。其中,前邊兩個操作都是通過睡眠等待在某個條件上。

7. 靜止狀態檢測及報告

很顯然,對這種狀態的檢測通常都是週期性的進行,放置在時鐘中斷處理中就是情理之中了:

  • rcu_sched/rcu_bh型別的RCU中,當檢測CPU處於使用者模式或處於idle執行緒中,說明當前CPU已經離開了臨界區,經歷了一個QS靜止狀態,對於rcu_bh的RCU,如果沒有出去softirq上下文中,也表明CPU經歷了QS靜止狀態;
  • rcu_pending滿足條件的情況下,觸發軟中斷的執行,rcu_process_callbacks將會被呼叫;
  • rcu_process_callbacks回撥函式中,對寬限期進行判斷,並對靜止狀態逐級上報,如果整個樹狀結構都經歷了靜止狀態,那就表明了寬限期的結束,從而喚醒核心執行緒去處理;
  • 順便提一句,在rcu_pending函式中,rcu_pending->__rcu_pending->check_cpu_stall->print_cpu_stall的流程中,會去判斷是否有CPU stall的問題,這個在核心中有文件專門來描述,不再分析了;

8. 狀態機變換

如果要觀察整個狀態機的變化,跟蹤一下trace_rcu_grace_period介面的記錄就能發現:

/*
 * Tracepoint for grace-period events.  Takes a string identifying the
 * RCU flavor, the grace-period number, and a string identifying the
 * grace-period-related event as follows:
 *
 *	"AccReadyCB": CPU acclerates new callbacks to RCU_NEXT_READY_TAIL.
 *	"AccWaitCB": CPU accelerates new callbacks to RCU_WAIT_TAIL.
 *	"newreq": Request a new grace period.
 *	"start": Start a grace period.
 *	"cpustart": CPU first notices a grace-period start.
 *	"cpuqs": CPU passes through a quiescent state.
 *	"cpuonl": CPU comes online.
 *	"cpuofl": CPU goes offline.
 *	"reqwait": GP kthread sleeps waiting for grace-period request.
 *	"reqwaitsig": GP kthread awakened by signal from reqwait state.
 *	"fqswait": GP kthread waiting until time to force quiescent states.
 *	"fqsstart": GP kthread starts forcing quiescent states.
 *	"fqsend": GP kthread done forcing quiescent states.
 *	"fqswaitsig": GP kthread awakened by signal from fqswait state.
 *	"end": End a grace period.
 *	"cpuend": CPU first notices a grace-period end.
 */

大體流程如下:

9. 總結

  • 本文提綱挈領的捋了一下RCU的大體流程,主要涉及到RCU狀態機的輪轉,從開啟寬限期GP,到寬限期GP的初始化、靜止狀態QS的檢測、寬限期結束、回撥函式的呼叫等,而這部分主要涉及到軟中斷RCU_SOFTIRQ和核心執行緒rcu_gp_kthread的動態執行及互動等;
  • 內部的狀態組織是通過rcu_state, rcu_node, rcu_data組織成樹狀結構來維護,此外回撥函式是通過rcu_data中的分段連結串列來批處理,至於這些結構中相關欄位的處理(比如gpnum, completed欄位的設定來判斷寬限期階段等),以及連結串列的節點移動等,都沒有進一步去分析跟進了;
  • RCU的實現機制很複雜,很多其他內容都還未涉及到,比如SRCU(可睡眠RCU)、可搶佔RCU,中斷/NMI對RCU的處理等,只能說是蜻蜓點水了;
  • 在閱讀程式碼過程中,經常會發現一些巧妙的設計,有時會有頓悟的感覺,這也是其中的樂趣之一了;

漸入佳境篇就此打住,是否還會有登堂入室篇呢?想啥呢,歇歇吧。

參考

Verification of the Tree-Based Hierarchical Read-Copy Update in the Linux Kernel
Documentation/RCU
What is RCU, Fundamentally?
What is RCU? Part 2: Usage
RCU part 3: the RCU API
Introduction to RCU

歡迎關注公眾號:

相關推薦

原創Linux RCU原理剖析-漸入佳境

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5

原創Linux RCU原理剖析-初窺門徑

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5

原創Spring-boot快速入門JPA資料來源--轉載請註明出處

Spring-boot快速入門(二)JPA資料來源 宣告:本篇部落格一切程式碼基於 Spring-boot快速入門(一)進行。 一、JPA介紹 Spring Data JPA,是一款直接整合了hibernate的資料庫資源訪問的Spring Data下的子專案,通過JPA對資料庫進

原創IP攝像頭技術縱覽---linux 核心編譯,USB攝像頭裝置識別

IP攝像頭技術縱覽(一)— linux 核心編譯,USB攝像頭裝置識別 開始正文之前先來認識一下我的開發環境: 系統:ubuntu 10.04 開發板:AT91SAM9260 + Linux-2.6.30 USB攝像頭:UVC無驅攝像頭(著手開發時只

原創Spring-Cloud快速入門微服務入門--轉載請註明出處

一、什麼是微服務? 有時候,會有的人存在誤解,所謂微服務就是SpringCloud。這種思想本身是不正確的,微服務是一種系統架構上面的設計風格,而SpringCloud則是一種較為適用於微服務架構的框架。 在java體系中,我們通常需要將一個大的類,拆分成若干個的小的類,每個類都具有自己獨立

原創Spring-boot快速入門HelloWord!--轉載請註明出處

Spring-boot快速入門(一)HelloWord! 一、Spring-boot簡介 1. Spring-boot介紹 Spring-boot是一款將Spring4.X版本Spring族群進行整合的一款框架,繼承了來自於Spring族群的絕大部分功能,在Spring4.

原創IP攝像頭技術縱覽---P2P技術—UDP打洞實現內網NAT穿透

【原創】IP攝像頭技術縱覽(七)—P2P技術—UDP打洞實現內網NAT穿透 本文屬於《IP攝像頭技術縱覽》系列文章之一: Author: chad Mail: [email protected] 本文可以自由轉載,但轉載請務必註明

TINY4412LINUX移植筆記:24裝置樹EEPROM驅動

【TINY4412】LINUX移植筆記:(24)裝置樹 EEPROM驅動 宿主機 : 虛擬機器 Ubuntu 16.04 LTS / X64 目標板[底板]: Tiny4412SDK - 1506 目標板[核心板]:

原創IP攝像頭技術縱覽---通過internet訪問攝像頭

【原創】IP攝像頭技術縱覽(六)—通過internet訪問攝像頭 本文屬於《IP攝像頭技術縱覽》系列文章之一: Author: chad Mail: [email protected] 本文可以自由轉載,但轉載請務必註明出處以及本

TINY4412LINUX移植筆記:27裝置樹LCD驅動

【TINY4412】LINUX移植筆記:(27)裝置樹 LCD驅動 宿主機 : 虛擬機器 Ubuntu 16.04 LTS / X64 目標板[底板]: Tiny4412SDK - 1506 目標板[核心板]: Ti

linux下殺死程序kill的N種方法

轉載一篇,最原始的出處已不可考,望見諒! 常規篇:  首先,用ps檢視程序,方法如下: $ ps -ef …… smx       1822     1  0 11:38 ?        00:00:49 gnome-terminal smx       1823  1

原創IP攝像頭技術縱覽---網路攝像頭初試—mjpg-streamer移植與部署

【原創】IP攝像頭技術縱覽(五)—網路攝像頭初試—mjpg-streamer移植與部署 本文屬於《IP攝像頭技術縱覽》系列文章之一: Author: chad Mail: [email protected] 1、vgrabbj、spacv

TINY4412LINUX移植筆記:23裝置樹LCD觸控式螢幕驅動

【TINY4412】LINUX移植筆記:(23)裝置樹 LCD觸控式螢幕驅動 宿主機 : 虛擬機器 Ubuntu 16.04 LTS / X64 目標板[底板]: Tiny4412SDK - 1506 目標板[核心板]

Linux平臺下使用者基本管理機制及原理剖析

Linux平臺下使用者基本管理機制及原理剖析(二) 在前面的部落格中,我們對虛擬環境下使用者的新建、檢視、刪除以及使用者組的新建和管理有了一定的瞭解,這篇文章我們 接著瞭解虛擬環境下的使用者資訊的更改、使用者認證資訊的檢視,使用者密碼的管理以及使用者授權。   1.使用者資

原創MySql 資料庫匯入匯出備份

啥不說了,兩週前剛剛做過mysql匯入匯出的結果現在又忘了。。 更可悲的是竟然同樣的三篇blog,現在看起來還是如當初一樣費勁,裡面的內容。。所以自己寫個記錄一下 環境:*nix 許可權:有相關表的寫讀許可權。 命令:mysql 和 myslqdump 匯出: /usr/bin/mysql

TINY4412LINUX移植筆記:22裝置樹LCD按鍵驅動

【TINY4412】LINUX移植筆記:(22)裝置樹 LCD按鍵驅動 宿主機 : 虛擬機器 Ubuntu 16.04 LTS / X64 目標板[底板]: Tiny4412SDK - 1506 目標板[核心板]:

原創Selenium學習系列之—ConnectDB和複用測試方法

一篇來說一下Webdriver中連線DB合複用測試方法。 兩個完全不搭邊的東西怎麼說明呢,既然不好說那就不多說,通過例子來理解。 需求我們要實現一個這樣的測試情境: 登入系統時,若loginID正確,但密碼錯誤,連續三次密碼輸入錯誤後,系統會lock user。 怎麼實現呢

原創Spring-Cloud快速入門微服務入門

一、什麼是微服務? 有時候,會有的人存在誤解,所謂微服務就是SpringCloud。這種思想本身是不正確的,微服務是一種系統架構上面的設計風格,而SpringCloud則是一種較為適用於微服務架構的框

原創大叔問題定位分享30mesos agent啟動失敗:Failed to perform recovery: Incompatible agent info detected

cpp 方法 fail mesos perf mes inf for cut mesos agent啟動失敗,報錯如下: Feb 15 22:03:18 server1.bj mesos-slave[1190]: E0215 22:03:18.622994 1192 sl

雷電源代碼分析-- 進入遊戲攻擊

engine 場景 aud 初始 cto onf 不變 addchild ems 效果圖: 程序分析: 初始化GameLayer場景觸摸。背景、音樂、UI及定時間器 bool GameLayer::init() { if (!CCLayer::init())