天天看點

五、FreeRTOS 任務基礎知識什麼是多任務系統?FreeRTOS  任務與協程1.任務(Task) 的特性任務狀态任務優先級任務實作任務控制塊 任務堆棧

RTOS 系統的核心就是任務管理,FreeRTOS 也不例外,而且大多數學習 RTOS 系統的工程師或者學生主要就是為了使用 RTOS 的多任務處理功能,初步上手 RTOS 系統首先必須掌握的也是任務的建立、删除、挂起和恢複等操作,由此可見任務管理的重要性。

什麼是多任務系統?

回想一下我們以前在使用 51、AVR、STM32 單片機裸機(未使用系統)的時候一般都是在main 函數裡面用 while(1)做一個大循環來完成所有的處理,即應用程式是一個無限的循環,循環中調用相應的函數完成所需的處理。有時候我們也需要中斷中完成一些處理。相對于多任務系統而言,這個就是單任務系統,也稱作前背景系統,中斷服務函數作為前台程式,大循環while(1)作為背景程式

五、FreeRTOS 任務基礎知識什麼是多任務系統?FreeRTOS  任務與協程1.任務(Task) 的特性任務狀态任務優先級任務實作任務控制塊 任務堆棧

前背景系統的實時性差,前背景系統各個任務(應用程式)都是排隊等着輪流執行,不管你這個程式現在有多緊急,沒輪到你就隻能等着!相當于所有任務(應用程式)的優先級都是一樣的。但是前背景系統簡單啊,資源消耗也少啊!在稍微大一點的嵌入式應用中前背景系統就明顯力不從心了,此時就需要多任務系統出馬了。

多任務系統會把一個大問題(應用)“分而治之”,把大問題劃分成很多個小問題,逐漸的把小問題解決掉,大問題也就随之解決了,這些小問題可以單獨的作為一個小任務來處理。這些小任務是并發處理的,注意,并不是說同一時刻一起執行很多個任務,而是由于每個任務執行的時間很短,導緻看起來像是同一時刻執行了很多個任務一樣。多個任務帶來了一個新的問題,究竟哪個任務先運作,哪個任務後運作呢?完成這個功能的東西在 RTOS 系統中叫做任務排程器。不同的系統其任務排程器的實作方法也不同,比如 FreeRTOS 是一個搶占式的實時多任務系統,那麼其任務排程器也是搶占式的。

五、FreeRTOS 任務基礎知識什麼是多任務系統?FreeRTOS  任務與協程1.任務(Task) 的特性任務狀态任務優先級任務實作任務控制塊 任務堆棧

高優先級的任務可以打斷低優先級任務的運作而取得 CPU 的使用權,這樣就保證了那些緊急任務的運作。這樣我們就可以為那些對實時性要求高的任務設定一個很高的優先級,比如自動駕駛中的障礙物檢測任務等。高優先級的任務執行完成以後重新把 CPU 的使用權歸還給低優先級的任務,這個就是搶占式多任務系統的基本原理。

FreeRTOS  任務與協程

FreeRTOS 中應用既可以使用任務,也可以使用協程(Co-Routine),或者兩者混合使用。但是任務和協程使用不同的API函數,是以不能通過隊列(或信号量)将資料從任務發送給協程,反之亦然。協程是為那些資源很少的 MCU 準備的,其開銷很小,但是 FreeRTOS 官方已經不打算再更新協程了,是以本教程隻講解任務。

1.任務(Task) 的特性

在使用 RTOS 的時候一個實時應用可以作為一個獨立的任務。每個任務都有自己的運作環境,不依賴于系統中其他的任務或者 RTOS 排程器。任何一個時間點隻能有一個任務運作,具體運作哪個任務是由 RTOS 排程器來決定的,RTOS 排程器是以就會重複的開啟、關閉每個任務。任務不需要了解 RTOS 排程器的具體行為,RTOS 排程器的職責是確定當一個任務開始執行的時候其上下文環境(寄存器值,堆棧内容等)和任務上一次退出的時候相同。為了做到這一點,每個任務都必須有個堆棧,當任務切換的時候将上下文環境儲存在堆棧中,這樣當任務再次執行的時候就可以從堆棧中取出上下文環境,任務恢複運作。

任務特性:

  • 簡單。
  • 沒有使用限制。
  • 支援搶占
  • 支援優先級
  • 每個任務都擁有堆棧導緻了 RAM 使用量增大。
  • 如果使用搶占的話的必須仔細的考慮重入的問題。

2.協程(Co-routine)的特性

協程是為那些資源很少的 MCU 而做的,但是随着 MCU 的飛速發展,性能越來越強大,現

在協程幾乎很少用到了!但是 FreeRTOS 目前還沒有把協程移除的計劃,但是 FreeRTOS 是絕對

不會再更新和維護協程了,是以協程大家了解一下就行了。在概念上協程和任務是相似的,但

是有如下根本上的不同:

  1. 堆棧使用:所有的協程使用同一個堆棧(如果是任務的話每個任務都有自己的堆棧),這樣就比使用任務消耗更少的 RAM。
  2. 排程器和優先級:協程使用合作式的排程器,但是可以在使用搶占式的排程器中使用協程。
  3. 宏實作:協程是通過宏定義來實作的。
  4. 使用限制:為了降低對 RAM 的消耗做了很多的限制。

任務狀态

FreeRTOS 中的任務永遠處于下面幾個狀态中的某一個:

運作态

當一個任務正在運作時,那麼就說這個任務處于運作态,處于運作态的任務就是目前正在使用處理器的任務。如果使用的是單核處理器的話那麼不管在任何時刻永遠都隻有一個任務處于運作态。

就緒态

處于就緒态的任務是那些已經準備就緒(這些任務沒有被阻塞或者挂起),可以運作的任務,但是處于就緒态的任務還沒有運作,因為有一個同優先級或者更高優先級的任務正在運作!

阻塞态

如果一個任務目前正在等待某個外部事件的話就說它處于阻塞态,比如說如果某個任務調用了函數 vTaskDelay()的話就會進入阻塞态,直到延時周期完成。任務在等待隊列、信号量、事件組、通知或互斥信号量的時候也會進入阻塞态。任務進入阻塞态會有一個逾時時間,當超過這個逾時時間任務就會退出阻塞态,即使所等待的事件還沒有來臨!

挂起态

像阻塞态一樣,任務進入挂起态以後也不能被排程器調用進入運作态,但是進入挂起态的任務沒有逾時時間。任務進入和退出挂起态通過調用函數 vTaskSuspend()和 xTaskResume()。

五、FreeRTOS 任務基礎知識什麼是多任務系統?FreeRTOS  任務與協程1.任務(Task) 的特性任務狀态任務優先級任務實作任務控制塊 任務堆棧

任務優先級

每 個 任 務 都 可 以 分 配 一 個 從 0~(configMAX_PRIORITIES-1) 的 優 先 級 ,configMAX_PRIORITIES 在檔案 FreeRTOSConfig.h 中有定義,前面我們講解 FreeRTOS 系統配置的時候已經講過了。如果所使用的硬體平台支援類似計算前導零這樣的指令(可以通過該指令選 擇 下 一 個 要 運 行 的 任 務 , Cortex-M 處 理 器 是 支 持 該 指 令 的 ) , 并 且 configUSE_PORT_OPTIMISED_TASK_SELECTION 也 設 置 為 了 1 , 那 麼 宏configMAX_PRIORITIES 不能超過 32!也就是優先級不能超過 32 級。其他情況下宏configMAX_PRIORITIES 可以為任意值,但是考慮到 RAM 的消耗,宏 configMAX_PRIORITIES最好設定為一個滿足應用的最小值。

優先級數字越低表示任務的優先級越低,0 的優先級最低,configMAX_PRIORITIES-1 的優先級最高。空閑任務的優先級最低,為 0。

FreeRTOS 排程器確定處于就緒态或運作态的高優先級的任務擷取處理器使用權,換句話說就是處于就緒态的最高優先級的任務才會運作。當宏 configUSE_TIME_SLICING 定義為 1 的時候多個任務可以共用一個優先級,數量不限。預設情況下宏configUSE_TIME_SLICING 在檔案FreeRTOS.h 中已經定義為 1。此時處于就緒态的優先級相同的任務就會使用時間片輪轉排程器擷取運作時間。

任務實作

在使用 FreeRTOS 的過程中,我們要使用函數 xTaskCreate()或 xTaskCreateStatic()來建立任務,這兩個函數的第一個參數pxTaskCode,就是這個任務的任務函數。什麼是任務函數?任務函數就是完成本任務工作的函數。我這個任務要幹嘛?要做什麼?要完成什麼樣的功能都是在這個任務函數中實作的。 比如我要做個任務,這個任務要點個流水燈,那麼這個流水燈的程式就是任務函數中實作的。FreeRTOS 官方給出的任務函數模闆如下:

void vATaskFunction(void *pvParameters) 
{
    for( ; ; ) 
    {
        //--任務應用程式--
        vTaskDelay(); 
        /*此處不一定要用延時函數,其他隻要能讓 FreeRTOS 發生任務切換的 API 函數都可以,
        比如請求信号量、隊列等,甚至直接調用任務排程器。隻不過最常用的就是 FreeRTOS 的延時函數。*/
    }
    /*不能從任務函數中傳回或者退出,從任務函數中傳回或退出的話就會調用
    configASSERT(),前提是你定義了 configASSERT()。如果一定要從任務函數中退出的話那一定
    要調用函數 vTaskDelete(NULL)來删除此任務。*/
    //vTaskDelete(NULL);  (5)
}
           

任務控制塊

FreeRTOS 的每個任務都有一些屬性需要存儲,FreeRTOS 把這些屬性集合到一起用一個結構體來表示,這個結構體叫做任務控制塊:TCB_t,在使用函數 xTaskCreate()建立任務的時候就會自動的給每個任務配置設定一個任務控制塊。在老版本的 FreeRTOS 中任務控制塊叫做 tskTCB,新版本重命名為 TCB_t,但是本質上還是 tskTCB,本教程後面提到任務控制塊的話均用 TCB_t表示,此結構體在檔案 tasks.c 中有定義。 FreeRTOS 的任務控制塊中的成員變量相比 UCOSIII 要少很多,而且大多數與裁剪有關,當不使用某些功能的時候與其相關的變量就不參與編譯,任務控制塊大小就會進一步的減小。

typedef struct tskTaskControlBlock
{
    volatile StackType_t  *pxTopOfStack;  //任務堆棧棧頂
    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS xMPUSettings;  //MPU 相關設定
    #endif
    ListItem_t  xStateListItem;  //狀态清單項
    ListItem_t  xEventListItem;  //事件清單項
    UBaseType_t uxPriority;  //任務優先級
    StackType_t *pxStack;  //任務堆棧起始位址
    char pcTaskName[ configMAX_TASK_NAME_LEN ];//任務名字
    #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack;  //任務堆棧棧底
    #endif
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; //臨界區嵌套深度
    #endif
    #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的時候用到
        UBaseType_t uxTCBNumber; 
        UBaseType_t uxTaskNumber; 
    #endif
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority;  //任務基礎優先級,優先級反轉的時候用到
        UBaseType_t uxMutexesHeld;  //任務擷取到的互斥信号量個數
    #endif
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) //與本地存儲有關
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif
    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t ulRunTimeCounter;  //用來記錄任務運作總時間
    #endif
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        struct  _reent xNewLib_reent; //定義一個 newlib 結構體變量
    #endif
    #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任務通知相關變量
        volatile uint32_t ulNotifiedValue; //任務通知值
        volatile uint8_t ucNotifyState;  //任務通知狀态
    #endif
    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        //用來标記任務是動态建立的還是靜态建立的,如果是靜态建立的此變量就為 pdTURE,
        //如果是動态建立的就為 pdFALSE 
        uint8_t  ucStaticallyAllocated;
    #endif
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
} tskTCB;
//新版本的 FreeRTOS 任務控制塊重命名為 TCB_t,但是本質上還是 tskTCB,主要是為了相容
//舊版本的應用。
typedef tskTCB TCB_t;
           

 任務堆棧

FreeRTOS 之是以能正确的恢複一個任務的運作就是因為有任務堆棧在保駕護航,任務排程器在進行任務切換的時候會将目前任務的現場(CPU 寄存器值等)儲存在此任務的任務堆棧中,等到此任務下次運作的時候就會先用堆棧中儲存的值來恢複現場,恢複現場以後任務就會接着從上次中斷的地方開始運作。

建立任務的時候需要給任務指定堆棧,如果使用的函數 xTaskCreate()建立任務(動态方法)的話那麼任務堆棧就會由函數 xTaskCreate()自動建立,後面分析 xTaskCreate()的時候會講解。如果使用函數 xTaskCreateStatic()建立任務(靜态方法)的話就需要程式員自行定義任務堆棧,然後堆棧首位址作為函數的參數 puxStackBuffer 傳遞給函數。

任務堆棧的資料類型為 StackType_t,StackType_t 本質上是 uint32_t,在 portmacro.h 中有定義。是以 StackType_t 類型的變量為 4 個位元組,那麼任務的實際堆棧大小就應該是我們所定義的 4 倍。