@(嵌入式)
FreeRtos
簡述
文章 < FreeRTOS 任務(wù)調(diào)度 任務(wù)切換 > 記錄了 FreeRTOS 中任務(wù)切換的過程展姐, 提到觸發(fā)任務(wù)切換的兩種情況 : 高優(yōu)先級任務(wù)就緒搶占和同優(yōu)先級任務(wù)時間共享(包括提前掛起)呜叫。 系統(tǒng)中橄唬,時間延時和任務(wù)阻塞纳鼎,時間片都以 Systick 為單位。
通過設(shè)置文件 FreeRTOSConfig.h
中 configTICK_RATE_HZ
設(shè)置任務(wù)節(jié)拍中斷頻率生兆, 在啟動任務(wù)調(diào)度器時廊佩,系統(tǒng)會根據(jù)另一個變量, CPU 的頻率 configCPU_CLOCK_HZ
計算對應(yīng)寫入節(jié)拍計數(shù)器的值牲平,啟動定時器中斷堤框。
系統(tǒng)在每一次節(jié)拍計數(shù)器中斷服務(wù)程序xPortSysTickHandler
(平臺實現(xiàn) port.c 中) 中調(diào)用處理函數(shù) xTaskIncrementTick, 依據(jù)該函數(shù)返回值判斷是否需要觸發(fā) PendSV 異常, 進(jìn)行任務(wù)切換。
涉及任務(wù)時間片輪循蜈抓, 任務(wù)阻塞超時启绰, 以及結(jié)束以此實現(xiàn)的延時函數(shù)。
分析的源碼版本是 v9.0.0
xTaskIncrementTick()
系統(tǒng)每次節(jié)拍中斷服務(wù)程序中主要任務(wù)由函數(shù) xTaskIncrementTick
完成沟使。
在任務(wù)調(diào)度器沒有掛起的情況下( xTaskIncrementTick 委可!= pdFALSE ),該函數(shù)主要完成 :
- 判斷節(jié)拍計數(shù)器xTickCount 是否溢出腊嗡, 溢出輪換延時函數(shù)隊列
- 判斷是否有阻塞任務(wù)超時着倾,取出插入就緒鏈表
- 同優(yōu)先級任務(wù)時間片輪
而當(dāng)任務(wù)調(diào)度器被掛起時, 該函數(shù)累加掛起時間計數(shù)器 uxPendedTicks
燕少, 調(diào)用用戶鉤子函數(shù)卡者, 此時,正在運(yùn)行的任務(wù)不會被切換客们, 一直運(yùn)行崇决。
當(dāng)恢復(fù)調(diào)度時, 系統(tǒng)會先重復(fù)調(diào)用 xTaskIncrementTick
補(bǔ)償 (uxPendedTicks
次)底挫。
不管, 系統(tǒng)調(diào)度器是否掛起恒傻, 每次節(jié)拍中斷都會調(diào)用用戶的鉤子函數(shù) vApplicationTickHook
。 由于函數(shù)是中斷中調(diào)用建邓,不要在里面處理太復(fù)雜的事情S濉!
節(jié)拍計數(shù)器溢出
涉及的變量, 定義在 task.c
開頭官边。
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
初始化時沸手, pxDelayedTaskList
指向 xDelayedTaskList1
, pxOverflowDelayedTaskList
指向 pxOverflowDelayedTaskList
拒逮,一開始我還在郁悶延時鏈表為什么要兩個罐氨,到這里才明白。
當(dāng)任務(wù)由于等待事件(延時滩援,消息隊列什么的堵塞)時栅隐,會設(shè)置一個時間,這時候玩徊,響應(yīng)的任務(wù)會被掛到延時鏈表中租悄,如果超過設(shè)置時間沒有事件響應(yīng),則系統(tǒng)會從延時鏈表中取出任務(wù)恢復(fù)就緒恩袱。
系統(tǒng)任務(wù)延時參考系統(tǒng)節(jié)拍計數(shù)器 xTickCount
泣棋, 加入鏈表前依據(jù)當(dāng)前計數(shù)器的值計算出超時的值 ( xTickCount+ xTicksToDelay ), 順序插入到延時鏈表中畔塔。
對不同平臺xTickCount
表示的位數(shù)不同潭辈,但是每次節(jié)拍中斷加一鸯屿,總會溢出。 上述計算任務(wù)延時時間把敢,如果系統(tǒng)發(fā)現(xiàn)計算出來的時間已經(jīng)溢出寄摆,則會將該任務(wù)加入到 pxOverflowDelayedTaskList
這個鏈表中。
在系統(tǒng)節(jié)拍中斷時修赞, 節(jié)拍計數(shù)器每次加一婶恼, 系統(tǒng)判斷是否溢出,如果溢出柏副, 調(diào)用宏 taskSWITCH_DELAYED_LISTS()
切換上述的鏈表指針勾邦。
宏主要實現(xiàn)如下 :
pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskList;
pxOverflowDelayedTaskList = pxTemp;
xNumOfOverflows++;
prvResetNextTaskUnblockTime();
這就是設(shè)置兩個鏈表的原因,輪流倒應(yīng)對計數(shù)器的溢出割择。
喚醒超時任務(wù)
全局變量 xNextTaskUnblockTime
記錄下一個需要退出延時鏈表的任務(wù)時間眷篇, 因此, 接下來判斷當(dāng)前時間锨推,延時鏈表中是否有任務(wù)需要推出阻塞狀態(tài)铅歼。
if( xConstTickCount >= xNextTaskUnblockTime )
{
for ( ;; ) {
// 取出喚醒任務(wù), 推進(jìn)就緒鏈表
}
}
對應(yīng)的换可, 把所有阻塞時間達(dá)到的任務(wù)取出, 推入到就緒鏈表厦幅,更新下一個任務(wù)解除時間給變量 xNextTaskUnblockTime
沾鳄。
任務(wù)時間片輪循
處理完延時任務(wù)后, 開始判斷當(dāng)前運(yùn)行任務(wù)确憨, 對應(yīng)優(yōu)先級鏈表中是否有其他任務(wù)就緒译荞, 如果有,需要保證每個任務(wù)都能獲得運(yùn)行時間休弃, 標(biāo)記需要任務(wù)切換吞歼, 作為函數(shù)返回。
完整函數(shù)
完整函數(shù)注釋如下塔猾,
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );
// 調(diào)度器正在運(yùn)行
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
// 節(jié)拍計數(shù)器遞增 1
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
// 節(jié)拍計數(shù)器溢出
// 比如32位 0xFFFFFFFF + 1 -> 0
// 延時鏈表切換
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
// 沒有任務(wù)延時篙骡, 時間設(shè)置"無窮大" 退出循環(huán)
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
// 取出延時鏈表頭任務(wù) TCB
pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
// 取該任務(wù)延時值
xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));
// 判斷任務(wù)是否超時
if( xConstTickCount < xItemValue )
{
// 任務(wù)還沒到時間,更新全局變量
// 直接退出
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 任務(wù)恢復(fù)就緒丈甸, 從堵塞的鏈表中刪除
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 插入就緒鏈表等待被執(zhí)行
prvAddTaskToReadyList( pxTCB );
// 如果系統(tǒng)允許搶占
#if ( configUSE_PREEMPTION == 1 )
{
// 如果新就緒任務(wù)優(yōu)先級高于當(dāng)前任務(wù)
// 標(biāo)記需要切換任務(wù)
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
// 同優(yōu)先級任務(wù) 時間輪
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
// 用戶鉤子函數(shù)
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else
{
// 記錄調(diào)度器被掛起器件節(jié)拍中斷次數(shù)
// 恢復(fù)后用于補(bǔ)償糯俗, 執(zhí)行本函數(shù) uxPendedTicks 次先
++uxPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
// 函數(shù)返回值, 如果為 pdTRUE睦擂,
// 則調(diào)用的系統(tǒng)節(jié)拍中斷會觸發(fā) PendSV 異常得湘, 任務(wù)切換
#if ( configUSE_PREEMPTION == 1 ) // 允許搶占
{
// 其他地方標(biāo)記需要執(zhí)行一次任務(wù)切換
// 所以不管前面需不需要 這里都會返回需要切換
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired;
}
系統(tǒng)延時函數(shù)
任務(wù)執(zhí)行過程中需要使用到延時函數(shù)進(jìn)行延時, 使用系統(tǒng)提供的延時函數(shù)可以將當(dāng)前任務(wù)掛起顿仇,讓出CPU 使用時間淘正,當(dāng)時間到達(dá)的時候摆马, 有系統(tǒng)恢復(fù)任務(wù)運(yùn)行。 FreeRTOS 提供兩種類型的延時函數(shù)
普通延時函數(shù) vTaskDelay
一般情況下鸿吆,需要延時一定時間囤采,就調(diào)用此函數(shù),將需要的延時時間轉(zhuǎn)換為對應(yīng)系統(tǒng)節(jié)拍數(shù)傳遞(如宏pdMS_TO_TICKS())伞剑, 之后斑唬,當(dāng)前任務(wù)會從就緒鏈表移除, 加入到延時鏈表中黎泣,系統(tǒng)會在節(jié)拍中斷中檢查是否到達(dá)延時時間恕刘, 重新恢復(fù)任務(wù)就緒。
void vTaskDelay( const TickType_t xTicksToDelay );
該函數(shù)調(diào)用到另一個函數(shù)是 prvAddCurrentTaskToDelayedList
抒倚, 將任務(wù)加入到延時鏈表中褐着, 函數(shù)中會判斷設(shè)定時間是否溢出, 選擇加入到對應(yīng)的延時鏈表托呕, 同上提到計數(shù)器溢出的問題含蓉。
**循環(huán)延時函數(shù) vTaskDelayUntil **
相比上面的普通延時函數(shù), 這個函數(shù)適用于任務(wù)周期性執(zhí)行的项郊。
舉個例子說明下馅扣, 有一個任務(wù), 需要周期性 500ms 讀取一次傳感器數(shù)據(jù)着降, 用上例子可以這么寫 :
void vTASKReadSensor(void *pvParameters)
{
// 500ms 轉(zhuǎn)換為 節(jié)拍
const portTickType xDelay = pdMS_TO_TICKS(500);
for( ;; )
{
readSensor();
vTaskDelay( xDelay );
}
}
看起來是周期性 500 ms 執(zhí)行差油, 但是考慮, 如果任務(wù)由于優(yōu)先級比較低之類的問題任洞, 在延時返回就緒狀態(tài)后沒有及時被運(yùn)行蓄喇,那么實際時間就開始飄了。
如果使用函數(shù) vTaskDelayUntil
交掏,
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
多了一個參數(shù) pxPreviousWakeTime
妆偏, 就不會有這個問題了
先看以下如何使用 :
void vTASKReadSensor(void *pvParameters)
{
const portTickType xDelay = pdMS_TO_TICKS(500);
static portTickType xLastWakeTime;
// 記錄第一次調(diào)用函數(shù)的時間 , 后續(xù)該變量由延時函數(shù)自己疊加
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
readSensor();
vTaskDelayUntil( xDelay );
}
}
周前性執(zhí)行前調(diào)用一個變量盅弛, 獲取當(dāng)前節(jié)拍計數(shù)器 钱骂,簡單認(rèn)為是第一次調(diào)用的時間, 而后開始周期性執(zhí)行熊尉, 傳入的變量第一次由我們設(shè)置后罐柳, 后續(xù)會由函數(shù)自動更新。
比如狰住, 我們在SystickCount 為 0 開始延時张吉, 在500 返回讀取數(shù)據(jù), 再延時催植, 和上一個例子一樣肮蛹, 當(dāng) 500 延時后返回勺择, 調(diào)度原因延遲, 等到 600 才讀取數(shù)據(jù)并開始下一次延時伦忠, 這里省核, 這個函數(shù)不同地方在于, 他會考慮這延遲的 100昆码, 而第二次延時的時間气忠, 其實還是從 500 開始算的, 也就是赋咽, 1000 的時候旧噪, 任務(wù)延時第二次就結(jié)束了, 而不是等到 1100 脓匿。
由于涉及到任務(wù)調(diào)度淘钟, 所以, 理論上來說陪毡, 兩個函數(shù)定時都是"不住確"的米母。 時間單位是系統(tǒng)節(jié)拍 !