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