FreeRTOS 任務(wù)調(diào)度 系統(tǒng)節(jié)拍

@(嵌入式)

Freertos

FreeRtos

簡述

文章 < FreeRTOS 任務(wù)調(diào)度 任務(wù)切換 > 記錄了 FreeRTOS 中任務(wù)切換的過程展姐, 提到觸發(fā)任務(wù)切換的兩種情況 : 高優(yōu)先級任務(wù)就緒搶占和同優(yōu)先級任務(wù)時間共享(包括提前掛起)呜叫。 系統(tǒng)中橄唬,時間延時和任務(wù)阻塞纳鼎,時間片都以 Systick 為單位。

通過設(shè)置文件 FreeRTOSConfig.hconfigTICK_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 指向 xDelayedTaskList1pxOverflowDelayedTaskList 指向 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é)拍 !

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毡琉,一起剝皮案震驚了整個濱河市铁瞒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桅滋,老刑警劉巖精拟,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異虱歪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)栅表,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門笋鄙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怪瓶,你說我怎么就攤上這事萧落。” “怎么了洗贰?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵找岖,是天一觀的道長。 經(jīng)常有香客問我敛滋,道長许布,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任绎晃,我火速辦了婚禮蜜唾,結(jié)果婚禮上杂曲,老公的妹妹穿的比我還像新娘。我一直安慰自己袁余,他們只是感情好擎勘,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著颖榜,像睡著了一般棚饵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掩完,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天噪漾,我揣著相機(jī)與錄音,去河邊找鬼藤为。 笑死怪与,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缅疟。 我是一名探鬼主播分别,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼存淫!你這毒婦竟也來了耘斩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤桅咆,失蹤者是張志新(化名)和其女友劉穎括授,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岩饼,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荚虚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了籍茧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片版述。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寞冯,靈堂內(nèi)的尸體忽然破棺而出渴析,到底是詐尸還是另有隱情,我是刑警寧澤吮龄,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布俭茧,位于F島的核電站,受9級特大地震影響漓帚,放射性物質(zhì)發(fā)生泄漏母债。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一胰默、第九天 我趴在偏房一處隱蔽的房頂上張望场斑。 院中可真熱鬧漓踢,春花似錦、人聲如沸漏隐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽青责。三九已至挺据,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脖隶,已是汗流浹背扁耐。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留产阱,地道東北人婉称。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像构蹬,于是被迫代替她去往敵國和親王暗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容

  • @(嵌入式) 簡述 前面文章 < FreeRTOS 任務(wù)調(diào)度 任務(wù)創(chuàng)建 > 介紹了 FreeRTOS 中如何創(chuàng)建任...
    orientlu閱讀 8,980評論 1 3
  • 1 嵌入式操作系統(tǒng) 為什么要用嵌入式操作系統(tǒng) 普通的單片機(jī)編程:程序(軟件)——單片機(jī)硬件庄敛; 嵌入式操作系統(tǒng)開發(fā):...
    安安zoe閱讀 786評論 0 1
  • 每一個喜劇演員都是偉大的俗壹,因為他們讓這個世界有了更多的歡樂。 每一個喜劇演員也是悲哀的藻烤,因為我們無法了解他們背后的...
    洛漾熙_0d2a閱讀 1,579評論 0 12
  • #畢業(yè)一周年#專題,第二期兴猩。 高中開始棺禾,我就養(yǎng)成了一個習(xí)慣,就是每到夏天來臨的時候峭跳,總是要用力的回憶起過去一年發(fā)生...
    去做一件事閱讀 240評論 0 0
  • 好想有這樣的一個小院,生活在這方寸天地之間缺前,一磚一瓦都這么美麗有韻味蛀醉,春天在院子里種種花草,栽個小樹衅码;夏天躺在涼椅...
    簡之如素閱讀 194評論 0 0