可搶佔核心下的正確加鎖:保持核心程式碼的搶佔安全¶
- 作者:
Robert Love <rml@tech9.net>
簡介¶
可搶佔核心會產生新的加鎖問題。這些問題與 SMP 下的問題相同:併發和重入。幸運的是,Linux 可搶佔核心模型利用了現有的 SMP 加鎖機制。因此,核心只需要在極少數情況下進行顯式的額外加鎖。
本文件適用於所有核心駭客。在核心中開發程式碼需要保護這些情況。
規則 #1:每個 CPU 的資料結構需要顯式保護¶
會出現兩個類似的問題。一個示例程式碼片段
struct this_needs_locking tux[NR_CPUS];
tux[smp_processor_id()] = some_value;
/* task is preempted here... */
something = tux[smp_processor_id()];
首先,由於資料是每個 CPU 的,因此可能沒有顯式的 SMP 加鎖,但需要它。其次,當一個被搶佔的任務最終被重新排程時,smp_processor_id 的先前值可能不等於當前值。您必須透過在這些情況周圍停用搶佔來保護它們。
您也可以使用 put_cpu() 和 get_cpu(),它們會停用搶佔。
規則 #2:CPU 狀態必須受到保護。¶
在搶佔下,CPU 的狀態必須受到保護。這取決於體系結構,但包括 CPU 結構和在上下文切換期間未保留的狀態。例如,在 x86 上,進入和退出 FPU 模式現在是一個臨界區,必須在停用搶佔的情況下進行。想想如果核心正在執行浮點指令然後被搶佔會發生什麼。請記住,核心除了使用者任務之外不儲存 FPU 狀態。因此,在搶佔時,FPU 暫存器將被出售給最低出價者。因此,必須在此類區域周圍停用搶佔。
請注意,某些 FPU 函式已經明確地是搶佔安全的。例如,kernel_fpu_begin 和 kernel_fpu_end 將停用和啟用搶佔。
規則 #3:鎖的獲取和釋放必須由同一任務執行¶
在一個任務中獲取的鎖必須由同一任務釋放。這意味著您不能做一些奇怪的事情,例如獲取一個鎖然後離開去玩,而另一個任務釋放它。如果您想做類似的事情,請在同一程式碼路徑中獲取和釋放該鎖,並讓呼叫者等待另一個任務的事件。
解決方案¶
搶佔下的資料保護是透過在臨界區期間停用搶佔來實現的。
preempt_enable() decrement the preempt counter
preempt_disable() increment the preempt counter
preempt_enable_no_resched() decrement, but do not immediately preempt
preempt_check_resched() if needed, reschedule
preempt_count() return the preempt counter
這些函式是可巢狀的。換句話說,您可以在程式碼路徑中呼叫 preempt_disable n 次,並且在第 n 次呼叫 preempt_enable 之前不會重新啟用搶佔。如果未啟用搶佔,則搶佔語句定義為空。
請注意,如果您持有任何鎖或停用了中斷,則無需顯式防止搶佔,因為在這些情況下,搶佔會被隱式停用。
但請記住,“中斷已停用”是一種從根本上不安全的停用搶佔的方式 - 如果 preempt count 為 0,任何 cond_resched() 或 cond_resched_lock() 都可能觸發重新排程。一個簡單的 printk() 可能會觸發重新排程。因此,只有當您知道受影響的程式碼路徑不執行任何這些操作時,才使用此隱式搶佔停用屬性。最佳策略是僅將其用於您編寫的小型原子程式碼,並且不呼叫任何複雜函式。
示例
cpucache_t *cc; /* this is per-CPU */
preempt_disable();
cc = cc_data(searchp);
if (cc && cc->avail) {
__free_block(searchp, cc_entry(cc), cc->avail);
cc->avail = 0;
}
preempt_enable();
return 0;
請注意,搶佔語句必須包含對關鍵變數的每次引用。另一個例子
int buf[NR_CPUS];
set_cpu_val(buf);
if (buf[smp_processor_id()] == -1) printf(KERN_INFO "wee!\n");
spin_lock(&buf_lock);
/* ... */
此程式碼不是搶佔安全的,但請注意我們如何透過簡單地將 spin_lock 向上移動兩行來輕鬆修復它。
使用中斷停用來防止搶佔¶
可以使用 local_irq_disable 和 local_irq_save 來防止搶佔事件。請注意,這樣做時,您必須非常小心,不要引起會設定 need_resched 並導致搶佔檢查的事件。如有疑問,請依賴加鎖或顯式搶佔停用。
請注意,在 2.5 中,中斷停用現在僅限於每個 CPU(例如,本地)。
另一個需要關注的問題是 local_irq_disable 和 local_irq_save 的正確使用。這些可以用於防止搶佔,但是,在退出時,如果可以啟用搶佔,則應進行測試以檢視是否需要搶佔。如果這些是從 spin_lock 和 read/write lock 宏呼叫的,則會完成正確的事情。它們也可以在 spin-lock 保護區域內呼叫,但是,如果它們在此上下文之外呼叫,則應測試搶佔。請注意,來自中斷上下文或 bottom half/tasklet 的呼叫也受到搶佔鎖的保護,因此可以使用不檢查搶佔的版本。