本文將基于RT-Thread,結(jié)合 RT-Trace 調(diào)試器細化到實際任務(wù)調(diào)度的粒度,來調(diào)試并逐步講解“優(yōu)先級反轉(zhuǎn)”的調(diào)度和運行邏輯。如果對 RT-Trace 感興趣的可以看這篇文章:
國產(chǎn)嵌入式調(diào)試器之光? RT-Trace 初體驗!
廢話不多說,我們直接開始。本文基于 RT-Thread 來編寫測試代碼。在此之前我們先捋一下代碼流程:
優(yōu)先級反轉(zhuǎn)問題的本質(zhì)是高優(yōu)先級任務(wù)因等待低優(yōu)先級任務(wù)釋放資源而被阻塞,同時又有其他中優(yōu)先級任務(wù)搶占了 CPU,使得低優(yōu)先級任務(wù)得不到執(zhí)行機會,進而導(dǎo)致高優(yōu)先級任務(wù)長時間無法繼續(xù)運行。
因此我們需要建立三個不同優(yōu)先級的任務(wù),然后需要有一個資源鎖來模擬實際場景下對共享資源的獲取與釋放。一般在 RTOS 中我們會使用互斥鎖來對資源進行上鎖,因此絕大多數(shù)的 RTOS 已經(jīng)為互斥鎖這個組件加入了優(yōu)先級繼承等應(yīng)對優(yōu)先級反轉(zhuǎn)問題的功能,包括 RT-Thread(RTT 的互斥鎖還具備優(yōu)先級天花板功能),所以如果我們就是想觀察優(yōu)先級反轉(zhuǎn)的現(xiàn)象,直接使用互斥鎖是不行的。
幸運的是,在 RT-Thread 中,還有一個線程間同步組件,其是不帶優(yōu)先級繼承等功能的,并且在我們目前的場景中也可以作為資源鎖來使用,這就是二值信號量。(特別注意,這邊使用二值信號量作為資源鎖只是用于觀察優(yōu)先級反轉(zhuǎn)這種異常現(xiàn)象,并且也正是因為其存在這個問題,實際項目中并不推薦這么使用!)
綜上,我們的測試代碼主體就是三個不同優(yōu)先級的任務(wù),加上一個資源鎖。且我們要測試三種情況,分別是用信號量實現(xiàn)優(yōu)先級反轉(zhuǎn),用互斥鎖實現(xiàn)優(yōu)先級繼承,在互斥鎖的基礎(chǔ)上通過設(shè)置天花板優(yōu)先級來實現(xiàn)優(yōu)先級天花板機制,整體測試代碼如下:
#include"rtdef.h"#include"rttypes.h"#include#include#include#include#ifndefRT_USING_NANO#include#endif/* RT_USING_NANO *//* defined the LED0 pin: PB1 */#defineTHREAD_PRIORITY 10#defineUSE_MUTEX 0#defineUSE_PRIORITY_CEILING 0#ifUSE_MUTEXrt_mutex_tmutex;#elsert_sem_tsem;#endifstaticvoidworking(uint32_tnms){ for(volatileuint32_ti =0; i < nms; i++) {? ? ? ? for?(volatileuint32_t?j =?0; j 16777; j++) {? ? ? ? ? ? __NOP();?// No Operation, just a delay? ? ? ? }? ? }}void?thread_high(void?*param)?{? ? /* 讓低優(yōu)先級線程優(yōu)先獲取資源 */? ? rt_thread_mdelay(2);#if?USE_MUTEX? ? rt_mutex_take(mutex, RT_WAITING_FOREVER);#else? ? rt_sem_take(sem, RT_WAITING_FOREVER);#endif? ? working(5);?// 模擬高優(yōu)先級線程對資源的操作#if?USE_MUTEX? ? rt_mutex_release(mutex);#else? ? rt_sem_release(sem);#endif}void?thread_medium(void?*param)?{? ? /* 讓高優(yōu)先級線程有機會阻塞 */? ? rt_thread_mdelay(4);? ? working(20);?// 模擬中優(yōu)先級線程的工作}void?thread_low(void?*param)?{#if?USE_MUTEX? ? rt_mutex_take(mutex, RT_WAITING_FOREVER);#else? ? rt_sem_take(sem, RT_WAITING_FOREVER);#endif? ? working(5);?//模擬低優(yōu)先級線程對資源的操作#if?USE_MUTEX? ? rt_mutex_release(mutex);#else? ? rt_sem_release(sem);#endif? ? working(2);?// 模擬低優(yōu)先級線程的其他工作}int?main(void)?{? ? rt_thread_t?tid = RT_NULL;? ? rt_thread_mdelay(500);#if?USE_MUTEX? ? mutex =?rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);? ? if?(mutex == RT_NULL) {? ? ? ? rt_kprintf("Failed to create mutex\n");? ? ? ? return-1;? ? }#if?USE_PRIORITY_CEILING? ? // 設(shè)置優(yōu)先級天花板為最高優(yōu)先級? ? rt_mutex_setprioceiling(mutex, THREAD_PRIORITY -?2);#endif#else? ? sem =?rt_sem_create("sem",?1, RT_IPC_FLAG_PRIO);? ? if?(sem == RT_NULL) {? ? ? ? rt_kprintf("Failed to create semaphore\n");? ? ? ? return-1;? ? }#endif? ? tid =?rt_thread_create("th", thread_high, RT_NULL,?2048, THREAD_PRIORITY -?1,?10);? ? if?(tid != RT_NULL) {?rt_thread_startup(tid); }? ? tid = RT_NULL;? ? tid =?rt_thread_create("tm", thread_medium, RT_NULL,?2048, THREAD_PRIORITY,?10);? ? if?(tid != RT_NULL) {?rt_thread_startup(tid); }? ? tid = RT_NULL;? ? tid =?rt_thread_create("tl", thread_low, RT_NULL,?2048, THREAD_PRIORITY +?1,?10);? ? if?(tid != RT_NULL) {?rt_thread_startup(tid); }? ? while?(1) {?rt_thread_mdelay(100); }}
以上代碼動態(tài)創(chuàng)建了三個任務(wù):th、tm和tl,分別對應(yīng)高優(yōu)先級(9),中優(yōu)先級(10)和低優(yōu)先級(11),在 RT-Thread 中優(yōu)先級數(shù)字越小越高。同時還基于條件編譯定義了一個資源鎖,當(dāng)USE_MUTEX為0時使用信號量作為鎖,用于制造優(yōu)先級反轉(zhuǎn),為1且USE_PRIORITY_CEILING為0時使用互斥鎖實現(xiàn)優(yōu)先級繼承,USE_MUTEX以及USE_PRIORITY_CEILING都為1時實現(xiàn)優(yōu)先級天花板策略。
working 函數(shù)本質(zhì)上是制造一段時間的延遲用于模擬任務(wù)的工作流程,此處沒有使用 rt_thread_mdelay 延時的原因是 rt_thread_mdelay 會造成任務(wù)本身主動讓出,從而無法實現(xiàn)真實情況下高優(yōu)先級在低優(yōu)先級任務(wù)運行中的搶占過程。
th 任務(wù)首先使用 rt_thread_mdelay 延遲主動讓出使得低優(yōu)先級任務(wù)能夠先運行從而獲取到資源,緊接著嘗試獲取資源,獲取到后使用 working 函數(shù)模擬對資源進行的操作,最后釋放資源。
tm 任務(wù)首先使用 rt_thread_mdelay 延遲主動讓出使得高優(yōu)先級任務(wù)能夠有時間運行到嘗試獲取資源并阻塞,從而能夠成功制造出中優(yōu)先級對低優(yōu)先級任務(wù)的搶占。緊接著使用working函數(shù)模擬其運行。注意,該任務(wù)中沒有任何對資源的操作,也不會獲取與釋放鎖。
tl 任務(wù)開始運行后立馬嘗試獲取資源,進而使用working函數(shù)模擬對資源進行的操作,接著釋放資源,最后再次使用working函數(shù)模擬該任務(wù)的其他操作。
在開啟了優(yōu)先級天花板機制的情況下,互斥鎖創(chuàng)建后會使用 rt_mutex_setprioceiling 為其設(shè)置一個天花板優(yōu)先級,在這里也就是 THREAD_PRIORITY - 2,雖然我們所有任務(wù)的最高優(yōu)先級為 THREAD_PRIORITY - 1,由于 RT-Thread 存在時間片輪轉(zhuǎn)調(diào)度功能,同優(yōu)先級任務(wù)在時間片用完之后也能互相搶占。此處也可以設(shè)置一個比較大的時間片來臨時防止搶占,我這里就直接將該優(yōu)先級設(shè)置為更高一級,從根本上避免搶占的發(fā)生。
OK,所有功能點都講解完成,接下來我們就借助 RT-Trace 的運行跟蹤功能,進入 CPU 視角,來看看底層的真實運行情況。
首先是使用信號量實現(xiàn)的優(yōu)先級反轉(zhuǎn)場景,我們重點關(guān)注三個任務(wù)之間的運行,屏蔽掉中斷,空閑線程等其他信息:

可以看到 tl 在開始運行了一段時間后 th 發(fā)生了搶占并嘗試獲取資源,但由于當(dāng)前資源被 tl 持有,于是 th 立馬阻塞讓出了 CPU 等待 tl 釋放資源:

然而還沒等 tl 釋放資源,中等優(yōu)先級的 tm 就搶占了 tl 的運行,此時 th 雖有著最高優(yōu)先級,但也只能眼睜睜等著 tm 執(zhí)行完,因為要等 tl 釋放資源它才能運行,但 tl 優(yōu)先級沒有 tm 高,當(dāng)前情況下 tm 不運行完, tl 根本不會運行:

等 tm 終于運行完了,把 CPU 給到 tl,并且 tl 釋放了資源后,本應(yīng)是最高優(yōu)先級的 th 才得以運行:

由于低優(yōu)先級任務(wù)持有了高優(yōu)先級任務(wù)運行所需的共享資源,從而導(dǎo)致高優(yōu)先級任務(wù)不可避免地進入等待,而當(dāng)系統(tǒng)比較復(fù)雜,任務(wù)較多時,低優(yōu)先級任務(wù)又特別容易被其他中優(yōu)先級任務(wù)搶占,最終導(dǎo)致高優(yōu)先級任務(wù)被無限延后,直至所有搶占的任務(wù)運行完并且低優(yōu)先級任務(wù)釋放資源才得以運行,而這個過程需要等待多久是未知且不可控的。在很多工業(yè)、醫(yī)療、軍事領(lǐng)域中,一個任務(wù)之所以要定義為高優(yōu)先級,就是需要其盡快執(zhí)行,并且運行周期嚴(yán)格可控,否則很有可能造成不可挽回的后果,如果這個過程中遇到優(yōu)先級反轉(zhuǎn),那系統(tǒng)異常甚至崩潰幾乎是必然的!
現(xiàn)在我們將 USE_MUTEX 置 1 來使用帶有優(yōu)先級繼承功能的互斥鎖,此時的任務(wù)調(diào)度過程如下:

很明顯,此時中優(yōu)先級任務(wù)沒有能搶占低優(yōu)先級任務(wù)在共享資源處理階段的運行,雖然高優(yōu)先級任務(wù)第一次嘗試獲取資源會失敗,但這個過程也臨時抬升了tl的優(yōu)先級:

而后 tl 得以不被中優(yōu)先級的 tm 打斷,一路運行到釋放資源,緊接著 th 獲取資源執(zhí)行,而 tm 自然是等到 th 運行完后才能運行。很明顯,優(yōu)先級反轉(zhuǎn)帶來的影響在這種機制下得到大幅緩解,但這里仍然有一個小瑕疵:

在 tl 對共享資源進行處理的過程中,仍然發(fā)生了一次搶占,這期間還會動態(tài)修改任務(wù)的優(yōu)先級,稍許拉長了tl 對資源的處理時間。如果頻繁操作資源,那么這對系統(tǒng)調(diào)度來說也是消耗比較大的。如果想要保證高優(yōu)先級任務(wù)能夠盡快執(zhí)行,那么這一段操作一定是要去除的。如何去除?這就引出了下面這個機制。
我們再將 USE_PRIORITY_CEILING 置 1 打開優(yōu)先級天花板,注意優(yōu)先級天花板的值是預(yù)設(shè)且靜態(tài)的,所以我們在使用這個特性的時候需要提前設(shè)置好值:
rt_mutex_setprioceiling(mutex, THREAD_PRIORITY -2);
這個值一般會設(shè)置成使用資源中所有任務(wù)的最高優(yōu)先級,當(dāng)然文章開頭也講過由于 RT-Thread 相同優(yōu)先級可以通過輪轉(zhuǎn)調(diào)度機制相互搶占,我這邊為了避免這種情況,所以設(shè)置為最高任務(wù)優(yōu)先級的更高一級。
我們來看下加入了優(yōu)先級天花板后任務(wù)的調(diào)度情況:

乍一看好像與優(yōu)先級繼承的調(diào)度情況沒有區(qū)別,接下來我將 tl 操作共享資源的時間軸放大:

對比優(yōu)先級繼承機制下的這部分:

可以看到中間 th 對 tl 的搶占過程消失了!

正是由于我們設(shè)置了天花板優(yōu)先級,tl 在獲取資源的那一刻就將其優(yōu)先級提升到了天花板,因此在這過程中即使是高優(yōu)先級的 th 也無法對其進行搶占,真正做到了最快的資源處理速度,自然 th 在這種情況下也得到了最快的響應(yīng)運行速度。
通過上述內(nèi)容,相信大家已經(jīng)對優(yōu)先級反轉(zhuǎn),優(yōu)先級繼承以及優(yōu)先級天花板有了非常直觀的理解,在實際的項目中也能夠按照實際需求去選擇合適的機制。
最后提一點,以上兩種方式都是緩解優(yōu)先級反轉(zhuǎn)帶來的影響,并不能解決,因為低優(yōu)先級任務(wù)獲取到資源后高優(yōu)先級任務(wù)不可避免地要等待,否則就會造成更為致命的數(shù)據(jù)同步問題,上述解決方案都只是盡可能讓低優(yōu)先級任務(wù)更快速地處理完釋放資源從而讓高優(yōu)先級任務(wù)及時獲取。如果你的系統(tǒng)就不能接受這種情況,那么或許你要從程序設(shè)計角度,根本上去避免低優(yōu)先級任務(wù)與高優(yōu)先級任務(wù)共享資源,或是通過精密的設(shè)計規(guī)避高優(yōu)先級任務(wù)想要獲取資源時低優(yōu)先級任務(wù)正在處理資源的情況。
-
嵌入式
+關(guān)注
關(guān)注
5185文章
20131瀏覽量
328208 -
調(diào)試器
+關(guān)注
關(guān)注
1文章
325瀏覽量
24913 -
RT-Thread
+關(guān)注
關(guān)注
32文章
1532瀏覽量
44211
發(fā)布評論請先 登錄
RTOS應(yīng)用中的優(yōu)先級反轉(zhuǎn)問題
深度剖析 RT-Thread 線程調(diào)度流程
請問UCOS如果把一個優(yōu)先級為5的任務(wù)刪除后,還能創(chuàng)建另一個優(yōu)先級為5的任務(wù)嗎?
干貨 | RTOS應(yīng)用中的優(yōu)先級反轉(zhuǎn)問題
RT-Thread與UCOS的簡單比較
RT-Thread的內(nèi)核調(diào)度算法實現(xiàn)
RT-Thread基于優(yōu)先級的全搶占式調(diào)度算法的實現(xiàn)
探討一下RT-Thread任務(wù)調(diào)度的啟動順序
rt-thread高優(yōu)先級的線程可以調(diào)度執(zhí)行嗎?
RT-Thread線程優(yōu)先級鏈表與位圖算法的介紹
RT-Thread系統(tǒng)線程調(diào)度器的設(shè)計實現(xiàn)
如何去處理RT-Thread線程優(yōu)先級的問題呢
RT-Thread的互斥量優(yōu)先級問題求解
多任務(wù)的優(yōu)先級分配該遵循什么樣的原則?
什么是優(yōu)先級反轉(zhuǎn)

揭秘!基于RT-Thread探究“優(yōu)先級反轉(zhuǎn)”下的任務(wù)調(diào)度究竟是什么樣的?| 技術(shù)集結(jié)
評論