本文是為那些希望非常深層次的理解RCU的骨灰級黑客准備的。這些黑客應當首先閱讀《深入理解RCU》系列文章的第1~6篇。骨灰級代碼狂也可能有興趣直接看看本文。
本文分別描述如下內容:
1、數據結構和內核參數
2、外部函數接口
3、初始化過程
4、CPU熱插撥接口
5、一些雜項函數
6、優雅周期檢測機制
7、dynticks-idle接口
8、處理離線及dynticks-idle CPU的函數
9、報告CPU卡頓的函數
10、報告可能的設計缺陷和問題。
1. 數據結構及內核參數
全面的理解分級RCU數據結構,對於理解其算法而言是十分重要的。具體包含如下數據結構:
1、用來跟蹤每一個CPU的dyntick-idle 狀態的數據結構
2、per-node數據結構的每一個字段
3、每CPU rcu_data 結構
4、全局rcu_state 數據結構
5、控制分級RCU的內核參數。
1.1. 跟蹤 dyn-tick 狀態的數據結構
每CPUrcu_dynticks數據結構使用下面的字段跟蹤dynticks 狀態:
1. dynticks_nesting:這個整型值是嵌套計數,表示在相應的CPU中,應當被監控的RCU讀端臨界區的數量。如果CPU處於dynticks-idle模式,那么就不應當存在RCU讀端臨界區。此時這個值是中斷嵌套級別,否則它比irq中斷嵌套級別更大。
2. dynticks:如果相應的CPU處於dynticks-idle模式,並且沒有中斷處理函數正在該CPU上運行,則這個計數值是偶數,否則是奇數。換句話說,如果計數值是奇數,那么相應的CPU可能處於RCU讀端臨界區中。
3. dynticks_nmi:如果相應的CPU 處於NMI處理函數中,則這個整型計數器的值是奇數。否則,該計數器是偶數。
rcu和rcu_bh共享這個值。
1.2. 分級RCU實現中的節點
rcu_node是被放到 rcu_state結構內的。每一個rcu_node有下面的字段:
1. lock:這個spinlock保護這個結構中的不是常量的字段。這個鎖在軟中斷上下文中獲取,因此必須禁止中斷。
根rcu_node的lock字段還有另外的作用:
ü 串行化CPU卡頓檢測,這樣僅僅只會有一個CPU報告CPU卡頓事件。這在上千個CPU的系統中是很重要的!
ü 串行化啟動一個新的優雅周期,這樣多個CPU不會同時開始相沖突的優雅周期。
ü 在代碼需要限制在單個優雅周期中運行時,防止開始一個新的優雅周期。
ü 將狀態機中強制產生靜止狀態的行為串行化,這樣可以將重新調度IPI降低到適當的數目。
2. qsmask:這個位圖掩碼跟蹤哪些CPU(rcu_node 葉子節點)或者CPU組(rcu_node 非葉子節點)仍然需要經歷一個靜止狀態,以結束當前優雅周期。
3. qsmaskinit:這個位圖掩碼跟蹤哪些CPU (rcu_node 葉子節點)或者CPU組(rcu_node 非葉子節點)仍然需要經歷一個靜止狀態,以結束后續的優雅周期。CPU熱插拔代碼維護qsmaskinit 字段,在開始每一個優雅周期時,將它們復制到相應的qsmask字段。這個復制操作,是優雅周期初始化過程需要與CPU熱插撥代碼互斥的原因之一。
4. grpmask:這個位圖掩碼中的位,與這個rcu_node結構在父rcu_node結構中qsmask和qsmaskinit中的位置是一致的。使用這個字段簡化了靜止狀態處理,這是 Manfred Spraul建議的。
5. grplo:這個字段表示這個rcu_node包含的編號最小的CPU或者組。
6. grphi:這個字段表示這個rcu_node包含的編號最大的CPU或者組。
7. grpnum:這個字段包含與這個rcu_node對應的父 rcu_node結構的qsmask和qsmaskinit字段的位編號。換句話說,給定一個rcu_node結構指針rnp,總有1UL<< rnp->grpnum == rnp->grpmask。grpnum字段用於調試目的。
8. level:對根rcu_node結構來說,這個字段是0。根的子結點是1,依此類推。
9. parent:這個字段指向父rcu_node結構,對根結點來說,其值為NULL。
1.3. 每CPU數據
rcu_data 數據結構包含RCU的每CPU狀態。它包含管理優雅周期和靜止狀態(completed, gpnum, passed_quiesc_completed, passed_quiesc,qs_pending, beenonline, mynode, 和 grpmask)的控制變量。rcu_data數據結構也包含關於RCU回調的控制變量 (nxtlist, nxttail, qlen,和 blimit)。打開dynticks的內核在rcu_data數據結構中也有相關的控制變量(dynticks, dynticks_snap, 和dynticks_nmi_snap)。rcu_data數據結構包含用於跟蹤的事件計數器 (dynticks_fqs, offline_fqs, resched_ipi)。最后,還有一對字段,對調用rcu_pending()進行計數,以確定何時強制進行靜止狀態(n_rcu_pending),以及一個cpu字段標識哪一個CPU與該rcu_data結構對應。
每一個字段描述如下:
1. completed:這個字段包含本CPU已經完成的優雅周期編號。
2. gpnum:這個字段包含本CPU最近啟動的優雅周期編號。
3. passed_quiesc_completed:本字段包含本CPU最近經過靜止狀態時,已經完成的優雅周期編號。最近完成的編號,要從該CPU經歷靜止狀態的角度來看:如果CPU沒有觀察到優雅周期42完成,它將記錄其值為41。這是對的,因為優雅周期能夠完成的唯一方法就是這個CPU已經經歷過一次靜止狀態。這個字段被初始化為一個虛構的次數,以避免在boot和CPU上線時產生競爭條件。
4. passed_quiesc:這個字段表示自從存儲在passed_quiesc_completed的優雅周期完成以來,本CPU是否經歷過一次靜止狀態。
5. qs_pending:這個字段表示本CPU已經注意到RCU核心機制正在等待它經歷一次靜止狀態。當CPU檢測到一個新的優雅周期,或者一個CPU上線時,將這個字段設置為1。
6. beenonline:這個字段初始化為0,當相應的CPU上線時被設置為1,這用於避免處理從沒有上線的CPU,當NR_CPUS大大超過實際的CPU數量時,這是有用的。
7. mynode:這個字段指向處理相應CPU的rcu_node葉子節點。
8. grpmask:這個掩碼字段只有一個位,表示mynode->qsmask 中哪一位與相應的CPU對應。
9. nxtlist:這個字段指向本CPU最老的RCU回調(rcu_head 結構),如果本CPU最近沒有這樣的回調,就設置為NULL。其他的回調可能通過它們的next指針鏈接在一起。
10. nxttail:這是一個二次間接指針數組,每個元素指向nxtlist 回調鏈表尾部。如果nxtlist是空的,那么所有nxttail 指針直接指向 nxtlist 字段。每一個nxttail數組節點有如下意義:
ü RCU_DONE_TAIL=0:這個元素是CPU在它經歷優雅周期時最后調用的回調函數的->next 字段。如果沒有這樣的回調函數,則它指向nxtlist 字段。
ü RCU_WAIT_TAIL=1:等待當前優雅周期結束時,最后一個回調函數的->next指針,如果沒有這樣的回調函數,就等於RCU_DONE_TAIL 元素。
ü RCU_NEXT_READY_TAIL=2:等待下一個優雅周期時的回調函數的next字段,如果沒有這樣的回調函數,則等於RCU_WAIT_TAIL 元素。
ü RCU_NEXT_TAIL=3:鏈表中的最后一個回調函數的next指針,如果鏈表為空,就是nxtlist字段。
11. qlen:在nxtlist鏈表中排隊的回調函數數量。
12. blimit:在某一個時刻可以調用的回調函數最大值。在高負載情況下,這個限制增強了系統響應性能。
13. dynticks:與cpu對應的rcu_dynticks結構。
14. dynticks_snap:dynticks->dynticks曾經經歷過的值,用於中斷處理函數中檢查CPU何時經歷過一次dynticks idle狀態。
15. dynticks_nmi_snap:dynticks->dynticks_nmi曾經經過的值,在CPU NMI處理函數中檢查CPU何時經歷過一次dynticks idle狀態。
16. dynticks_fqs:其他CPU由於dynticksidle而標記一次靜止狀態的次數。
17. offline_fqs:其他CPU由於離線而標記一次靜止狀態的次數。
18. resched_ipi:向相應CPU發送的重新調度IPI次數。這些IPI被發向那些沒有及時報告自己經歷過靜止狀態的CPU,但既不向離線CPU,也不向處於dynticksidle 狀態的CPU發送這樣的IPI。
19. n_rcu_pending:調用rcu_pending()的次數,在一個非dynticks-idle 的CPU上,每一個jiffy將調用一次這個函數。
20. n_rcu_pending_force_qs:n_rcu_pending上限。如果 n_rcu_pending達到此值,表示當前優雅周期延遲太久,則調用force_quiescent_state()。本字段在2.6.32版本中已經取消,但是不影響我們理解RCU代碼。
1.4. RCU全局狀態
rcu_state 結構包含每一個RCU實例(rcu 和 rcu_bh)的全局狀態。包括與分級rcu_node相關的字段,包括結點數組本身,分級數組包含指向分級級別的指針,levelcnt數組包含每一級結點計數,levelspread數組包含每一個分級的子結點數量。rda 數組是每一個CPU的rcu_data結構指針。rcu_state也包含一定數量的,與當前優雅周期相關的字段,以及與其他機制交互的字段(signaled、gpnum、completed、onofflock、fqslock、jiffies_force_qs、n_force_qs、n_force_qs_lh、n_force_qs_ngp、gp_start、jiffies_stall、and dynticks_completed)。
每一個字段描述如下:
1. node:這個字段是rcu_node結構數組,根節點位於->node[0]。其長度由 NUM_RCU_NODES C-預處理宏指定,根據 NR_CPUS 和 CONFIG_RCU_FANOUT計算確定。注意,從元素0開始遍歷->node 數組可以達到寬度優先搜索rcu_node分級樹的效果。
2. level:指向node數組的指針數組。分級樹的根節點由->level[0]引用,第二級節點的第一個節點(如果有的話)由->level[1]引用,依此類推。第一個葉子節點由 ->level[NUM_RCU_LVLS-1]引用,level 數組的長度由 NUM_RCU_LVLS指定。->level字段通常與->node 字段一起使用,用於分級掃描rcu_node,例如,掃描所有葉子節點。->level由啟動時的rcu_init_one()函數填充。
3. levelcnt:這是一個數組,包含每一層rcu_node 結構的數量,包括引用rcu_node 葉子結構的rcu_data數據結構的數量,因此這個數組的元素比->level數組多。注意 ->levelcnt[0]總是包含值1,表示分級結構的根rcu_node 只有一個。這個數組的值被初始化為NUM_RCU_LVL_0,NUM_RCU_LVL_1,NUM_RCU_LVL_2和 NUM_RCU_LVL_3。->levelcnt 字段用於初始化分級結構的其他部分,並用於調試目的。
4. levelspread:這個字段的每一個元素包含rcu_node分級結構每一層期望的子節點數量。這個數組的值由兩個rcu_init_levelspread()函數中的其中一個在運行時計算,具體選擇哪一個函數由CONFIG_RCU_FANOUT_EXACT 內核參數確定。
5. rda:該字段的每一個元素包含相應CPU的rcu_data指針。這個數組在啟動時由 RCU_DATA_PTR_INIT()宏初始化。
6. signaled:這個字段用於維護force_quiescent_state() 函數所使用的狀態。這個字段可以有如下值:
ü RCU_GP_INIT:這個值表示當前優雅周期仍然在初始化過程中。因此force_quiescent_state()不應采取任何動作。當然,在與該函數產生競爭前,優雅周期初始化有三個jiffies的時間避免產生競爭,如果你有大量的CPU,這個競爭才可能會真實的發生。一旦完成優雅周期初始化,這個值要么設置成 RCU_SAVE_DYNTICK (如果配置了 CONFIG_NO_HZ) 要么設置成 RCU_FORCE_QS。
ü RCU_SAVE_DYNTICK:這個值表示force_quiescent_state()應當檢查所有還沒有為當前優雅周期報告靜止狀態的CPU的dynticks狀態。這些CPU以dyntick-idle 模式表示其靜止狀態。
ü RCU_FORCE_QS:這個值表示force_quiescent_state() 應當與在線、離線狀態一起,重新檢查還沒有報告靜止狀態的CPU的dynticks 狀態。重新檢測dynticks狀態可以處理這樣一種情況:一個特定CPU可能處於dynticks-idle 狀態,但是在檢測時,它正處於中斷和NMI處理函數中。此時,將會發送一個重新調度IPI給這些CPU。
這個字段由根rcu_node的鎖保護。
7. gpnum:當前優雅周期的值,如果當前沒有優雅周期,則是上一個優雅周期的值。該字段由根rcu_node結構的鎖保護。但是頻繁的在沒有這個鎖保護的情況下進行訪問(但是不修改)。
8. completed:上一次已經完成的優雅周期的值。當前沒有優雅周期正在處理時,它等於->gpnum。如果當前有優雅周期在處理,則比->gpnum少1。在某些LINUX版本的經典RCU中,這一對字段可能是一個布爾變量。這個字段由根rcu_node 結構的鎖進行保護。但是頻繁的在沒有這個鎖保護的情況下訪問(但是不修改)。
9. onofflock:防止在優雅周期初始化時,並發的處理上線、離線。但是有一個例外:如果rcu_node分級結構僅僅由一個單一結構組成,那么由單個結構的鎖來代替這個鎖。
10. fqslock:這個字段用於在force_quiescent_state()中,防止多個任務同時調用此函數強制產生靜止狀態。
11. jiffies_force_qs:這是一個 以jiffies計算的時間, 當需要調用force_quiescent_state()以強制CPU進入靜止狀態,或者報告一個靜止狀態時使用。這個字段由根rcu_node結構的鎖保護。但是頻繁的在沒有這個鎖保護的情況下訪問(但是不修改)。
12. n_force_qs:調用force_quiescent_state() ,並真正進行強制產生靜止狀態的次數。如果相應的CPU已經完成靜止周期,而導致force_quiescent_state()過早退出,是不統計在這個字段中的。這個字段用於跟蹤和調試,以 ->fqslock鎖進行保護。
13. n_force_qs_lh:由於->fqslock 被其他CPU獲得而導致force_quiescent_state() 過早返回的次數的近似值。這個字段用於跟蹤和調試,由於它是近似值,因此沒有用任何鎖進行保護。
14. n_force_qs_ngp:force_quiescent_state() 成功獲得->fqslock 鎖,但是隨后發現沒有正在處理的優雅周期,然后該函數的退出次數。用於調試和跟蹤,由->fqslock進行保護。
15. gp_start:記錄最近的優雅周期開始時間,以jiffies計數。這用於檢測卡頓的CPU,但是僅僅在CONFIG_RCU_CPU_STALL_DETECTOR 內核參數選中時才有效。這個字段由根rcu_node的->lock鎖進行保護,但是有時不使用鎖訪問它(但是不修改它)。
16. jiffies_stall:這個時間值以 jiffies計算,表示當前優雅周期什么時候會變得太長,此時將開始檢查CPU卡頓。與->gp_start一樣,僅僅在配置了 CONFIG_RCU_CPU_STALL_DETECTOR 內核參數時,這個字段才存在。這個字段由根rcu_node保護,但是有時不使用這個鎖而直接訪問(但不修改)。
17. dynticks_completed:當force_quiescent_state()對dyntick 進行快照時,這個字段記錄->completed 的值。在每一個優雅周期開始時,這個字段被初始化為前一個優雅周期。這個字段用於防止前一個優雅周期的dyntick-idle 靜止狀態應用到當前優雅周期。同樣的,這個字段僅僅在配置了CONFIG_NO_HZ內核參數時才存在。這個字段由根rcu_node的鎖進行保護,但有時也在沒有鎖保護的情況下進行訪問(但不修改)。
1.5. 內核參數
以下內核參數將影響RCU:
l NR_CPUS,系統中最大的CPU數量。
l CONFIG_RCU_FANOUT,在rcu_node分級體系中,期望的每一個節點的子節點數量。
l CONFIG_RCU_FANOUT_EXACT,一個布爾值,防止rcu_node分組體系進行平衡操作。
l CONFIG_HOTPLUG_CPU,允許 CPU動態上線、離線。
l CONFIG_NO_HZ,表示支持 dynticks-idle 模式。
l CONFIG_SMP,表示是否多核CPU。
l CONFIG_RCU_CPU_STALL_DETECTOR,表示 RCU 將在優雅周期太長時檢查CPU卡頓。
l CONFIG_RCU_TRACE,表示 RCU 將在debugfs中提供跟蹤信息。
1 #define MAX_RCU_LVLS 3
2 #define RCU_FANOUT (CONFIG_RCU_FANOUT)
3 #define RCU_FANOUT_SQ (RCU_FANOUT * RCU_FANOUT)
4 #define RCU_FANOUT_CUBE (RCU_FANOUT_SQ * RCU_FANOUT)
5
6 #if NR_CPUS <= RCU_FANOUT
7 # define NUM_RCU_LVLS 1
8 # define NUM_RCU_LVL_0 1
9 # define NUM_RCU_LVL_1 (NR_CPUS)
10 # define NUM_RCU_LVL_2 0
11 # define NUM_RCU_LVL_3 0
12 #elif NR_CPUS <= RCU_FANOUT_SQ
13 # define NUM_RCU_LVLS 2
14 # define NUM_RCU_LVL_0 1
15 # define NUM_RCU_LVL_1 (((NR_CPUS) + RCU_FANOUT - 1) / RCU_FANOUT)
16 # define NUM_RCU_LVL_2 (NR_CPUS)
17 # define NUM_RCU_LVL_3 0
18 #elif NR_CPUS <= RCU_FANOUT_CUBE
19 # define NUM_RCU_LVLS 3
20 # define NUM_RCU_LVL_0 1
21 # define NUM_RCU_LVL_1 (((NR_CPUS) + RCU_FANOUT_SQ - 1) / RCU_FANOUT_SQ)
22 # define NUM_RCU_LVL_2 (((NR_CPUS) + (RCU_FANOUT) - 1) / (RCU_FANOUT))
23 # define NUM_RCU_LVL_3 NR_CPUS
24 #else
25 # error "CONFIG_RCU_FANOUT insufficient for NR_CPUS"
26 #endif /* #if (NR_CPUS) <= RCU_FANOUT */
27
28 #define RCU_SUM (NUM_RCU_LVL_0 + NUM_RCU_LVL_1 + NUM_RCU_LVL_2 + NUM_RCU_LVL_3)
29 #define NUM_RCU_NODES (RCU_SUM - NR_CPUS)
RCU的宏 |
---|
CONFIG_RCU_FANOUT和 NR_CPUS 參數用於在編譯時確定rcu_node分級體系的形態。第1行定義rcu_node分級體系的最大深度,最大為3級。注意增加最大深度需要修改其他地方,如,添加另外一個路徑到第6-26行的#if語句中。第2-4行計算fanout,fanout的平方,fanout的立方。
然后將這些值與NR_CPUS 進行比較,以確定rcu_node需要的深度,將其賦給NUM_RCU_LVLS,用於rcu_state結構的數組長度。在根一層,總是只有一個節點,並且總有NUM_CPUS個rcu_data結構屬於葉子節點。如果僅僅只比根層多一層,則葉子層的節點數是用RCU_FANOUT除NR_CPUS(向上對齊)。其他層的節點使用類似的方法進行計算,但是使用RCU_FANOUT_SQ代替 RCU_FANOUT。
隨后第28行計算所有層的總和,結果是rcu_node結構的數量加上rcu_data的數量。最后,第29行從總和中減去 NR_CPUS (是rcu_data 結構的數量) ,結果是rcu_node結構的數量,將其值保存在NUM_RCU_NODES。這個值用於rcu_state結構的->nodes數組的長度。
2. 外部接口
RCU的外部接口不僅僅包含標准的RCU API,也包含RCU向其他內核模塊提供的內部接口。這些接口是rcu_read_lock()、rcu_read_unlock()、 rcu_read_lock_bh()、rcu_read_unlock_bh()、call_rcu() (是對__call_rcu()的封裝)、call_rcu_bh() (ditto)、rcu_check_callbacks()、rcu_process_callbacks()(是對 __rcu_process_callbacks()的封裝)、rcu_pending() (是對__rcu_pending()的封裝)、rcu_needs_cpu()、rcu_cpu_notify()和__rcu_init()。注意synchronize_rcu()和 rcu_barrier()通常用於所有RCU 實現,並且以call_rcu()的形式定義。類似的, rcu_barrier_bh()通常用於所有RCU實現,並且以call_rcu_bh()的形式定義。
2.1. 讀端臨界區
1 void __rcu_read_lock(void)
2 {
3 preempt_disable();
4 __acquire(RCU);
5 rcu_read_acquire();
6 }
7
8 void __rcu_read_unlock(void)
9 {
10 rcu_read_release();
11 __release(RCU);
12 preempt_enable();
13 }
14
15 void __rcu_read_lock_bh(void)
16 {
17 local_bh_disable();
18 __acquire(RCU_BH);
19 rcu_read_acquire();
20 }
21
22 void __rcu_read_unlock_bh(void)
23 {
24 rcu_read_release();
25 __release(RCU_BH);
26 local_bh_enable();
27 }
RCU讀端臨界區
上圖顯示了RCU讀端臨界區的函數。請讀者注意:這些函數與源代碼略有差異。第1-6 行顯示了__rcu_read_lock(),它開始一個“rcu”讀端臨界區。第3行禁止搶占,第4行是一個弱的標記,表示開始一個RCU讀端臨界區,第5行更新lockdep狀態。第8-13行顯示了__rcu_read_unlock(),它是與__rcu_read_lock()相對的函數。第15-20顯示了 __rcu_read_lock_bh(),第22-27行顯示了__rcu_read_unlock_bh(),它們與前兩個函數類似,但是它們禁止、打開下半部分而不是而不是禁止打開搶占。
2.2. call_rcu()
1 static void
2 __call_rcu(struct rcu_head *head,
3 void (*func)(struct rcu_head *rcu),
4 struct rcu_state *rsp)
5 {
6 unsigned long flags;
7 struct rcu_data *rdp;
8
9 head->func = func;
10 head->next = NULL;
11 smp_mb();
12 local_irq_save(flags);
13 rdp = rsp->rda[smp_processor_id()];
14 rcu_process_gp_end(rsp, rdp);
15 check_for_new_grace_period(rsp, rdp);
16 *rdp->nxttail[RCU_NEXT_TAIL] = head;
17 rdp->nxttail[RCU_NEXT_TAIL] = &head->next;
18 if (ACCESS_ONCE(rsp->completed) ==
19 ACCESS_ONCE(rsp->gpnum)) {
20 unsigned long nestflag;
21 struct rcu_node *rnp_root = rcu_get_root(rsp);
22
23 spin_lock_irqsave(&rnp_root->lock, nestflag);
24 rcu_start_gp(rsp, nestflag);
25 }
26 if (unlikely(++rdp->qlen > qhimark)) {
27 rdp->blimit = LONG_MAX;
28 force_quiescent_state(rsp, 0);
29 } else if ((long)(ACCESS_ONCE(rsp->jiffies_force_qs) -
30 jiffies) < 0 ||
31 (rdp->n_rcu_pending_force_qs -
32 rdp->n_rcu_pending) < 0)
33 force_quiescent_state(rsp, 1);
34 local_irq_restore(flags);
35 }
36
37 void call_rcu(struct rcu_head *head,
38 void (*func)(struct rcu_head *rcu))
39 {
40 __call_rcu(head, func, &rcu_state);
41 }
42
43 void call_rcu_bh(struct rcu_head *head,
44 void (*func)(struct rcu_head *rcu))
45 {
46 __call_rcu(head, func, &rcu_bh_state);
47 }
call_rcu()代碼
上圖顯示了__call_rcu(),call_rcu()和call_rcu_bh()函數的代碼。注意 call_rcu()和call_rcu_bh()是對__call_rcu()的簡單封裝,因此這里並不過多考慮它們。
將注意力轉到__call_rcu(),第9-10行初始化指定的rcu_head。這里假設讀者知道call_rcu的作用,並且明白rcu_head的含義。第11 行確保更新RCU保護數據結構的操作,在注冊回調函數之前完成,這里也假設讀者smp_mb的含義,並且明白這里“完成”一詞的“真正”含義,這里需要強調一點,所有認為smp_mb會刷新cache,或者認為smp_mb會阻止隨后的指令執行,這兩種潛意識都是不正確的。第12、34行禁止並重新打開中斷,以防止在一個中斷處理函數中調用__call_rcu()而產生災難性的沖突。第13行得到當前CPU的rcu_data數據結構的引用,第14行調用rcu_process_gp_end(),其目的是在當前優雅周期已經結束時,將回調函數提前執行。如果新的優雅周期已經開始,則第15行調用check_for_new_grace_period()記錄狀態。
第16、17行將新的回調函數加入隊列。第18、19行檢查是否有一個優雅周期正在處理中,如果沒有,則第23行獲得根rcu_node結構的鎖,並在第24行調用rcu_start_gp() 開始一個新的優雅周期(也釋放鎖)。
第 26行檢查是否有太多的RCU回調在本CPU中等待,如果是這樣,第27行遞增->blimit,目的是提高處理回調函數的速度。第28行立即調用force_quiescent_state() ,其目的是試圖使相應CPU盡快經歷一次靜止狀態。否則,第29-32行檢查自優雅周期開始以來(或者自從上一次調用force_quiescent_state()以來),是否已經經過太長時間,如果是這樣,第33行調用非緊急的force_quiescent_state(),也是為了使相應的CPU經歷一次靜止狀態。
2.3. rcu_check_callbacks()
1 static int __rcu_pending(struct rcu_state *rsp,
2 struct rcu_data *rdp)
3 {
4 rdp->n_rcu_pending++;
5
6 check_cpu_stall(rsp, rdp);
7 if (rdp->qs_pending)
8 return 1;
9 if (cpu_has_callbacks_ready_to_invoke(rdp))
10 return 1;
11 if (cpu_needs_another_gp(rsp, rdp))
12 return 1;
13 if (ACCESS_ONCE(rsp->completed) != rdp->completed)
14 return 1;
15 if (ACCESS_ONCE(rsp->gpnum) != rdp->gpnum)
16 return 1;
17 if (ACCESS_ONCE(rsp->completed) !=
18 ACCESS_ONCE(rsp->gpnum) &&
19 ((long)(ACCESS_ONCE(rsp->jiffies_force_qs) -
20 jiffies) < 0 ||
21 (rdp->n_rcu_pending_force_qs -
22 rdp->n_rcu_pending) < 0))
23 return 1;
24 return 0;
25 }
26
27 int rcu_pending(int cpu)
28 {
29 return __rcu_pending(&rcu_state,
30 &per_cpu(rcu_data, cpu)) ||
31 __rcu_pending(&rcu_bh_state,
32 &per_cpu(rcu_bh_data, cpu));
33 }
34
35 void rcu_check_callbacks(int cpu, int user)
36 {
37 if (user ||
38 (idle_cpu(cpu) && !in_softirq() &&
39 hardirq_count() <= (1 << HARDIRQ_SHIFT))) {
40 rcu_qsctr_inc(cpu);
41 rcu_bh_qsctr_inc(cpu);
42 } else if (!in_softirq()) {
43 rcu_bh_qsctr_inc(cpu);
44 }
45 raise_softirq(RCU_SOFTIRQ);
46 }
rcu_check_callbacks()代碼
上圖顯示的代碼,在每一個CPU的每jiffy中,都會在調度中斷處理函數中調用。rcu_pending() 函數(是__rcu_pending()的一個封裝函數)被調用,如果它返回非0,那么調用rcu_check_callbacks()。 (注意有想法將 rcu_pending()合並到rcu_check_callbacks())
從__rcu_pending()開始,第4行記錄本調用次數,用於確定何時強制產生靜止狀態。第6行調用check_cpu_stall(),目的是報告在內核中自旋的CPU,那些CPU也許是存在硬件問題。如果配置了CONFIG_RCU_CPU_STALL_DETECTOR,第7-23執行一系列的檢查,如果RCU需要當前CPU做一些事情,就返回非0值。第7行檢查當前CPU是否還缺少了一次靜止狀態,則在第9行調用cpu_has_callbacks_ready_to_invoke()檢查當前CPU是否有回調需要處理,並准備調用它們。這些回調是在已經結束的優雅周期中產生的,現在准備調用它們了。第11 行調用cpu_needs_another_gp() 檢查當前CPU是否有回調需要在另外的RCU優雅周期結束。第13行檢查當優前雅周期是否已經結束,第15行檢查一個新優雅周期是否已經開始,最后,第17-22行檢查是否應當強制其他CPU經歷一次靜止狀態。最后進行如下一些檢查: (1) 第17-18行檢查是否一個優雅周期正在處理,如果是,則第19-22行檢查是否經歷了足夠的jiffies時間(第 19-20行) 或者調用rcu_pending()次數足夠多(第21-22),以確定是否應當調用 force_quiescent_state()。如果這一系列的檢查都沒有觸發,則第 24 行返回0,表示不必調用rcu_check_callbacks()。
第27-33行展示了rcu_pending(),它簡單的調用__rcu_pending() 兩次,一次是為“rcu”,另一次是為“rcu_bh”。
第35-48行展示了rcu_check_callbacks(),它檢查調度中斷是否中止了一個擴展靜止狀態,然后開始RCU軟中斷處理 (rcu_process_callbacks())。第37-41行為“rcu” 執行這個檢查,而第42-43行為“rcu_bh”執行這個檢查。
第37-39行檢查調度時鍾中斷是否打斷了用戶態執行 (第37行),或者打斷了idle循環 (第38行的idle_cpu()),並且沒有影響中斷層次(第38行的剩余部分及整個第39行)。如果這個檢查成功,則調度時鍾中斷來自於擴展靜止狀態,由於任何rcu的靜止狀態也是rcu_bh的靜止狀態,那么第40、41行報告兩種靜止狀態。
類似於“rcu_bh”,第 42 行檢查調度時鍾中斷是否來自於打開軟中斷的代碼區,如果是,則第43行報告“rcu_bh”靜止狀態。
其他情況下,第45行觸發一個RCU 軟中斷,將導致在將來的某個時刻,本CPU上的 rcu_process_callbacks()將被調用。
2.4. rcu_process_callbacks()
1 static void
2__rcu_process_callbacks(struct rcu_state *rsp,
3struct rcu_data *rdp)
4 {
5 unsigned long flags;
6
7 if((long)(ACCESS_ONCE(rsp->jiffies_force_qs) -
8 jiffies)< 0 ||
9 (rdp->n_rcu_pending_force_qs-
10 rdp->n_rcu_pending) < 0)
11 force_quiescent_state(rsp,1);
12 rcu_process_gp_end(rsp, rdp);
13 rcu_check_quiescent_state(rsp, rdp);
14 if (cpu_needs_another_gp(rsp, rdp)) {
15 spin_lock_irqsave(&rcu_get_root(rsp)->lock,flags);
16 rcu_start_gp(rsp,flags);
17 }
18 rcu_do_batch(rdp);
19 }
20
21static void
22rcu_process_callbacks(struct softirq_action *unused)
23 {
24 smp_mb();
25 __rcu_process_callbacks(&rcu_state,
26 &__get_cpu_var(rcu_data));
27 __rcu_process_callbacks(&rcu_bh_state,
28 &__get_cpu_var(rcu_bh_data));
29 smp_mb();
30 }
rcu_process_callbacks()代碼
上圖展示了rcu_process_callbacks()的代碼,它是對__rcu_process_callbacks()函數的封裝。這些函數是調用raise_softirq(RCU_SOFTIRQ)函數的結果。例如,當有某種原因使得RCU核心認為:需要當前CPU做某些事情的時候,就會觸發此函數。
第7-10行檢查自當前優雅周期啟動以來,是否已經經歷了一段時間。如果是這樣,第11行調用 force_quiescent_state(),其目的是試圖使相應的CPU經歷一次靜止狀態。
在任何情況下,第 12行調用 rcu_process_gp_end(),它檢查其他CPU是否結束了本CPU關注的優雅周期。如果是,標記優雅周期結束並且加快調用本CPU的RCU回調。第13行調用rcu_check_quiescent_state(),該函數檢查其他CPU是否啟動了一個新的優雅周期,並檢查當前CPU是否已經為這個優雅周期經歷了一次靜止狀態。如果是這樣,就更新相應的狀態。第14行檢查是否沒有正在處理的優雅周期,並且檢查是否有另外的優雅周期所需要的回調函數,如果是這樣,第15行獲得根rcu_node的鎖,第17行調用rcu_start_gp(),開始一個新的優雅周期(並且也釋放根rcu_node 的鎖)。其他情況下,第18行調用 rcu_do_batch(),它調用本CPU的回調,這些回調對應的優雅周期已經完成。
第21-30行是 rcu_process_callbacks(),也是一個對__rcu_process_callbacks()的封裝函數。第 24行執行一個內存屏障,以確保前面的RCU讀端臨界區在隨后的RCU處理過程之前完成。第25-26行和第27-28行分別為“rcu”和“rcu_bh”調用__rcu_process_callbacks(),最后,第29行執行一個內存屏障,以確保任何 __rcu_process_callbacks()執行的操作,看起來都早於隨后的RCU讀端臨界區。
2.5. rcu_needs_cpu() 和 rcu_cpu_notify()
1 intrcu_needs_cpu(int cpu)
2 {
3 return per_cpu(rcu_data, cpu).nxtlist ||
4 per_cpu(rcu_bh_data, cpu).nxtlist;
5 }
6
7static int __cpuinit
8rcu_cpu_notify(struct notifier_block *self,
9unsigned long action, void *hcpu)
10 {
11 long cpu = (long)hcpu;
12
13 switch (action) {
14 caseCPU_UP_PREPARE:
15 caseCPU_UP_PREPARE_FROZEN:
16 rcu_online_cpu(cpu);
17 break;
18 caseCPU_DEAD:
19 caseCPU_DEAD_FROZEN:
20 caseCPU_UP_CANCELED:
21 caseCPU_UP_CANCELED_FROZEN:
22 rcu_offline_cpu(cpu);
23 break;
24 default:
25 break;
26 }
27 return NOTIFY_OK;
28 }
rcu_needs_cpu()和rcu_cpu_notify代碼
上圖展示了rcu_needs_cpu()和rcu_cpu_notify()的代碼,它們被LINUX內核調用,以檢查 dynticks-idle 模式轉換並處理CPU熱插撥。
第1-5行顯示了rcu_needs_cpu()的代碼,它簡單的檢查特定的CPU是否有“rcu”(第3行)或者“rcu_bh”(第4行)回調。
第7-28顯示了rcu_cpu_notify(),它是一個非常典型的、使用switch語句的CPU熱插撥通知函數。如果特定的CPU上線,則在第16行調用rcu_online_cpu(),如果特定CPU離線,則在第22行調用rcu_offline_cpu。請特別注意:CPU熱插撥操作不是原子的,因此可能穿越多個優雅周期。因此必須小心的處理CPU熱插撥事件。
....
(由於篇幅關系,后面的無法貼下,關心原文的,請直接聯系謝寶友老師)