單處理器系統上的 RCU

一個常見的誤解是,在 UP 系統上,call_rcu() 原語可能會立即呼叫其函式。這種誤解的基礎是,因為只有一個 CPU,所以沒有必要等待任何事情完成,因為沒有其他 CPU 可以執行任何其他操作。雖然這種方法在很多時候有點有效,但總的來說這是一個非常糟糕的主意。本文件提供了三個示例,說明了這有多麼糟糕。

示例 1:軟中斷自殺

假設一個基於 RCU 的演算法在程序上下文中掃描一個包含元素 A、B 和 C 的連結串列,並且可以在軟中斷上下文中從此列表中刪除元素。假設程序上下文掃描正在引用元素 B,此時被軟中斷處理中斷,軟中斷處理刪除了元素 B,然後在寬限期後呼叫 call_rcu() 來釋放元素 B。

現在,如果 call_rcu() 直接呼叫其引數,那麼從軟中斷返回後,列表掃描會發現自己正在引用一個新釋放的元素 B。這種情況會大大降低核心的預期壽命。

如果在硬體中斷處理程式中呼叫 call_rcu(),也會發生同樣的問題。

示例 2:函式呼叫致命

當然,可以僅僅在從程序上下文中呼叫 call_rcu() 時才直接呼叫其引數,從而避免前面示例中描述的自殺。但是,這可能會以類似的方式失敗。

假設一個基於 RCU 的演算法再次在程序上下文中掃描一個包含元素 A、B 和 C 的連結串列,但在掃描時會在每個元素上呼叫一個函式。進一步假設此函式從列表中刪除元素 B,然後將其傳遞給 call_rcu() 以進行延遲釋放。這可能有點非常規,但它是完全合法的 RCU 用法,因為 call_rcu() 必須等待寬限期過去。因此,在這種情況下,允許 call_rcu() 立即呼叫其引數會導致它無法實現 RCU 的基本保證,即 call_rcu() 會延遲呼叫其引數,直到所有當前正在執行的 RCU 讀取側關鍵部分完成。

快速測驗 #1

為什麼在這種情況下呼叫 synchronize_rcu()合法的?

快速測驗的答案

示例 3:死鎖致死

假設在持有鎖時呼叫 call_rcu(),並且回撥函式必須獲取同一個鎖。在這種情況下,如果 call_rcu() 直接呼叫回撥,結果將是自死鎖,即使此呼叫發生在完整寬限期後的稍後 call_rcu() 呼叫中。

在某些情況下,可以重構程式碼,以便將 call_rcu() 延遲到釋放鎖之後。但是,在某些情況下,這可能會非常難看。

  1. 如果需要在同一關鍵部分中將多個項傳遞給 call_rcu(),則程式碼需要建立一個列表,然後在釋放鎖後遍歷該列表。

  2. 在某些情況下,鎖將在某些核心 API 中持有,因此延遲 call_rcu() 直到釋放鎖需要透過通用 API 將資料項傳遞上去。保證回撥在不持有任何鎖的情況下被呼叫,比必須修改此類 API 以允許任意資料項透過它們傳遞回來的做法要好得多。

如果 call_rcu() 直接呼叫回撥,則需要痛苦的鎖定限制或 API 更改。

快速測驗 #2

RCU 回撥必須遵守什麼鎖定限制?

快速測驗的答案

重要的是要注意,使用者空間 RCU 實現允許 call_rcu() 直接呼叫回撥,但前提是自這些回撥排隊以來已經過去了一個完整的寬限期。這是因為某些使用者空間環境受到極大的限制。然而,強烈建議編寫使用者空間 RCU 實現的人員避免從 call_rcu() 呼叫回撥,從而獲得上述避免死鎖的好處。

總結

允許 call_rcu() 立即呼叫其引數會破壞 RCU,即使在 UP 系統上也是如此。所以不要這樣做!即使在 UP 系統上,RCU 基礎設施必須尊重寬限期,並且必須從已知的環境中呼叫回撥,在該環境中不持有任何鎖。

請注意,對於 UP 系統,包括在 UP 系統上執行的 PREEMPT SMP 構建,synchronize_rcu() 立即返回是安全的。

快速測驗 #3

為什麼 synchronize_rcu() 不能在執行可搶佔 RCU 的 UP 系統上立即返回?

快速測驗 #1 的答案

為什麼在這種情況下呼叫 synchronize_rcu()合法的?

因為呼叫函式正在掃描受 RCU 保護的連結串列,因此位於 RCU 讀取側關鍵部分內。因此,被呼叫函式已在 RCU 讀取側關鍵部分內被呼叫,並且不允許阻塞。

快速測驗 #2 的答案

RCU 回撥必須遵守什麼鎖定限制?

在 RCU 回撥中獲取的任何鎖都必須使用自旋鎖原語的 _bh 變體在其他地方獲取。例如,如果 RCU 回撥獲取了“mylock”,則此鎖的程序上下文獲取必須使用諸如 spin_lock_bh() 之類的東西來獲取鎖。請注意,也可以使用自旋鎖的 _irq 變體,例如 spin_lock_irqsave()。

如果程序上下文程式碼只是使用 spin_lock(),那麼,由於可以從軟中斷上下文中呼叫 RCU 回撥,因此回撥可能會從中斷程序上下文關鍵部分的軟中斷中呼叫。這將導致自死鎖。

這種限制似乎是無謂的,因為很少有 RCU 回撥直接獲取鎖。但是,許多 RCU 回撥間接獲取鎖,例如,透過 kfree() 原語。

快速測驗 #3 的答案

為什麼 synchronize_rcu() 不能在執行可搶佔 RCU 的 UP 系統上立即返回?

因為某個其他任務可能在 RCU 讀取側關鍵部分的中間被搶佔。如果 synchronize_rcu() 只是立即返回,它會過早地發出寬限期結束的訊號,當該其他執行緒再次開始執行時,這將給它帶來令人討厭的震驚。