本文對比分析:
preempt_disable()
local_irq_disable()/local_irq_save(flags)
spin_lock()
spin_lock_irq()/spin_lock_irqsave(lock, flags)
哪些關閉了搶占?另外,再說清楚,搶占又關閉了誰。
首先,把這幾個API的關系圖再勾勒一下。
我們理解,spin_lock()會調用preempt_disable() 導致本核的搶占調度被關閉(preempt_disable函數實際增加preempt_count來達到此效果),其次我們理解spin_lock_irq()是local_irq_disable()+preempt_disable()的合體。
下面一幅圖描述了幾個函數之間的包含關系如下:
local_irq_disable()/local_irq_save()的disable和save版的唯一區別是,要不要保存CPU對中斷的屏蔽狀態。
spin_lock_irq()/spin_lock_irqsave(lock, flags)的唯一區別是,要不要保存CPU對中斷的屏蔽狀態。
是誰殺了搶占?
Kernel的代碼明確顯示,執行搶占調度的時候,會同時檢測“non-zero preempt_count or interrupts are disabled”:
我們可以進一步展開preemptible():
對于ARM處理器而言,判斷irqs_disabled(),其實就是判斷CPSR中的IRQMASK_I_BIT是否被設置。
所以,我們得出一個結論,前言這一節里面,列出的所有函數,都能關閉本核的搶占調度。因為,無論是preempt_count計數狀態,還是中斷被關閉,都會導致kernel認為無法搶占!
通殺邏輯如下:
殺手的差異在哪里?
既然都關閉了搶占,那么區別在哪里呢?
我們看兩段代碼,假設下面的代碼都發生在NICE為0的普通進程:
preempt_disable()
xxx(1)
preempt_enable()
和
local_irq_disable()
xxx(2)
local_irq_enable()
首先,xxx(1)和xxx(2)里面,都是不可以搶占的。一個搞定了preempt_count,一個搞定了中斷。
但是假設xxx(1)內喚醒了一個高優先級的RT任務,那么在preempt_enable()的時刻,直接就是一個搶占點,這個時候,發生schedule,高優先級RT任務進來跑;假設xxx(2)內喚醒了一個高優先級的RT任務,那么在local_irq_enable()的時刻,不是一個搶占點,高優先級RT的任務必須等待下一個搶占點。下一個搶占點,可能是時鐘tick處理返回、中斷返回、軟中斷結束、yield()等等多種情況。
在preempt_enable()中,會執行一次preempt_schedule():
而local_irq_enable()只是單純的開啟CPU對中斷的響應,對于ARM而言,它就是:
再來看大boss,spin_lock_irq是同時調用了preempt_disable和local_irq_disable:
而對應的spin_unlock_irq()則同時調用了local_irq_enable()和preempt_enable():
大家想一想,為何preempt_enable()比local_irq_enable()后發生呢?如果代碼順序是這樣的:
preempt_enable()
local_irq_enable()
第一句preempt_enable()想執行搶占調度的話,即便調用了preempt_schedule(),但是由于IRQ還是關門的,preempt_schedule()函數會立即返回(詳見《是誰殺了搶占?》一節),所以無法搶占;后一句local_irq_enable()不會執行搶占調度。所以,如果這么干的話,
spin_lock_irq()
xxx(3)
spin_unlock_irq()
如果xxx(3)喚醒了高優先級的RT,在spin_unlock_irq()的時刻,將無法直接搶占!
還好,真正的順序是:
local_irq_enable()
preempt_enable()
所以,在spin_unlock_irq()的時刻,RT進程就換入執行了。
看小一點的boss,spin_lock():
spin_lock()
xxx(4)
spin_unlock()
如果xxx(4)喚醒了RT進程,在spin_unlock()的時刻,會立即搶入。因為spin_unlock()會調用preempt_enable()。
而搶占又殺了誰?
理論上,關搶占,并沒有徹底的關閉調度器,因為進程還是可以主動地sleep:
上述代碼,在spin_lock的區間里面,調用了msleep(),這個時候,不屬于搶占,Linux還是會pick下一個task來跑。
不過這樣的代碼,一般在后期蘊藏著巨大的風險,導致后期的莫名崩潰。所以呢,實際的工程里面,我們是嚴格地禁止的。
建議大家打開Kernel里面的config里面的DEBUG_ATOMIC_SLEEP,一旦出現這種情況,讓kernel去匯報錯誤。
這種情況下,kernel檢測到有人在atomic上下文里面執行可能睡眠的行為,會直接報執行的棧回溯。
-
cpu
+關注
關注
68文章
10829瀏覽量
211198 -
Linux
+關注
關注
87文章
11232瀏覽量
208961 -
API
+關注
關注
2文章
1487瀏覽量
61833
原文標題:宋寶華: 是誰關閉了Linux搶占,而搶占又關閉了誰?
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論