使用 RCU 保護動態 NMI 處理程式¶
雖然 RCU 通常用於保護主要用於讀取的資料結構,但也可以使用 RCU 來提供動態非遮蔽中斷處理程式,以及動態 irq 處理程式。本文件描述瞭如何做到這一點,大致借鑑了 Zwane Mwaikambo 在舊版本“arch/x86/kernel/traps.c”中的 NMI-timer 工作。
相關的程式碼片段如下所示,每個程式碼片段後都有簡要說明
static int dummy_nmi_callback(struct pt_regs *regs, int cpu)
{
return 0;
}
dummy_nmi_callback() 函式是一個“虛擬” NMI 處理程式,它什麼也不做,但返回零,從而表示它什麼也沒做,允許 NMI 處理程式採取預設的機器特定操作
static nmi_callback_t nmi_callback = dummy_nmi_callback;
nmi_callback 變數是一個指向當前 NMI 處理程式的全域性函式指標
void do_nmi(struct pt_regs * regs, long error_code)
{
int cpu;
nmi_enter();
cpu = smp_processor_id();
++nmi_count(cpu);
if (!rcu_dereference_sched(nmi_callback)(regs, cpu))
default_do_nmi(regs);
nmi_exit();
}
do_nmi() 函式處理每個 NMI。它首先以與硬體 irq 相同的方式停用搶佔,然後增加每個 CPU 的 NMI 計數。然後,它呼叫儲存在 nmi_callback 函式指標中的 NMI 處理程式。如果此處理程式返回零,則 do_nmi() 呼叫 default_do_nmi() 函式來處理機器特定的 NMI。最後,恢復搶佔。
理論上,不需要 rcu_dereference_sched(),因為此程式碼僅在 i386 上執行,理論上不需要 rcu_dereference_sched()。但是,實際上它是一個很好的文件輔助工具,特別是對於任何試圖在 Alpha 或具有激進最佳化編譯器的系統上做類似事情的人。
- 快速測驗
考慮到指標引用的程式碼是隻讀的,為什麼 Alpha 上可能需要
rcu_dereference_sched()?
回到關於 NMI 和 RCU 的討論
void set_nmi_callback(nmi_callback_t callback)
{
rcu_assign_pointer(nmi_callback, callback);
}
set_nmi_callback() 函式註冊一個 NMI 處理程式。請注意,回撥要使用的任何資料都必須在呼叫 set_nmi_callback() -之前- 初始化。對於不排序寫入的架構,rcu_assign_pointer() 確保 NMI 處理程式看到初始化的值
void unset_nmi_callback(void)
{
rcu_assign_pointer(nmi_callback, dummy_nmi_callback);
}
此函式登出 NMI 處理程式,恢復原始的 dummy_nmi_handler()。但是,很可能有些其他 CPU 上當前正在執行 NMI 處理程式。因此,在舊 NMI 處理程式在所有其他 CPU 上完成執行之前,我們無法釋放它使用的任何資料結構。
實現此目的的一種方法是透過 synchronize_rcu(),可能如下所示
unset_nmi_callback();
synchronize_rcu();
kfree(my_nmi_data);
這是可行的,因為(從 v4.20 開始)synchronize_rcu() 會阻塞,直到所有 CPU 完成它們正在執行的任何停用搶佔的程式碼段。由於 NMI 處理程式停用搶佔,因此保證 synchronize_rcu() 不會返回,直到所有正在進行的 NMI 處理程式退出。因此,在 synchronize_rcu() 返回後立即釋放處理程式的資料是安全的。
重要提示:為此目的生效,所討論的架構必須分別在 NMI 進入和退出時呼叫 nmi_enter() 和 nmi_exit()。
- 快速測驗的答案
考慮到指標引用的程式碼是隻讀的,為什麼 Alpha 上可能需要
rcu_dereference_sched()?set_nmi_callback() 的呼叫者可能已經初始化了一些將由新的 NMI 處理程式使用的資料。在這種情況下,將需要
rcu_dereference_sched(),因為否則在設定新處理程式後立即收到 NMI 的 CPU 可能會看到指向新 NMI 處理程式的指標,但卻看到處理程式資料的舊的預初始化版本。當使用具有激進指標值推測最佳化的編譯器時,其他 CPU 上也可能發生同樣的悲慘故事。(但是請不要這樣做!)
更重要的是,
rcu_dereference_sched()向閱讀程式碼的人清楚地表明該指標正在受到 RCU-sched 的保護。