1 說明
本文僅作為學習FreeRTOS的記錄文檔,作為初學者肯定很多理解不對甚至錯誤的地方,望網友指正。
1.1 簡介
FreeRTOS是一個RTOS(實時操作系統)系統,支持搶占式、合作式和時間片調度。適用於微處理器或小型微處理器的實時應用。
本文檔使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
參考文檔:《FreeRTOS_Reference_Manual_V10.0.0.pdf》、《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》、《STM32F4 FreeRTOS開發手冊_V1.1.pdf》
參考視頻:正點原子FreeRTOS手把手教學-基於STM32_嗶哩嗶哩_bilibili
1.2 測試工程說明
測試環境為在Linux進行仿真跑FreeRTOS,關於如何實現在Linux下的FreeRTOS仿真說明如下:
網上在linux下跑FreeRTOS的教程很多,這里我直接check了一份別人已經創建了的代碼。
代碼路徑:freertos-simulator: FreeRTOS simulator on linux platform (gitee.com)
或者去官網,按照官網說明創建也可以。
官網:FreeRTOS simulator for Posix/Linux
編譯之前需要先安裝pcap開發包,不同的系統安裝方式也不同,官網也給出了安裝方式。
To install on ubuntu run
$ sudo apt-get install libpcap-dev
To install on rpm based system run
$ sudo yum install libpcap-devel
or
$ sudo dnf install libpcap-devel
To install on MacOS run
$ brew install libpcap
編譯方式:
進入simulator目錄,直接執行make即可。
運行方式:
$ ./build/freertos-simulator

2 FreeRTOS任務基礎知識
主要介紹任務管理。從以下幾個方面進行介紹:多任務系統、任務狀態、任務優先級、任務實現、任務控制塊、任務堆棧。
2.1 多任務系統
FreeRTOS是一個搶占式的實時多任務系統。高優先級的任務可以打斷低優先級的任務而取得CPU的使用權。

2.2 任務的特性
任務是獨立的,每個任務都有自己的運行環境,不依賴於系統中的其他任務或者RTOS調度器。任何一個時間點只能有一個任務運行,具體運行哪個任務由調度器決定。RTOS調度器的職責是確保當一個任務開始執行的時候其上下文環境(寄存器、堆棧內容等)和任務上一次退出的時候相同。所以每個任務都有堆棧,當任務切換的時候將上下文環境存放在堆棧中,這樣當任務再次執行的時候可以從堆棧中取出上下文環境,任務恢復運行。
任務有以下特性:簡單、沒有使用限制、支持搶占、支持優先級、任務必須有堆棧、使用搶占必須考慮重入的問題。
2.3 任務狀態
FreeRTOS中的任務永遠處在下面幾種狀態中的某一種。
1)運行態:當一個任務運行時,則該任務處於運行態,處於運行態的任務就是當前使用處理器的任務。單核處理器在任何時刻只有一個任務處於運行態。
2)就緒態:任務已經准備就緒可以運行,但還沒有運行,因為有一個同優先級或者更高優先級的任務正在運行。
3)阻塞態:任務當前正在等待某個外部事件,則任務處於阻塞態,比如說某個任務使用了vTaskDelay()函數的話就會進入阻塞態,直到延時周期完成。任務在等待隊列、信號量、事件組、通知或互斥信號量的時候也會進入阻塞態。任務進入阻塞態會有一個超時時間,當超過這個時間任務會退出阻塞態,即使所等待的事件還沒有來臨。
4)掛起態:任務進入掛起態后不能被調度器調用進入運行態,但是進入掛起態的任務沒有超時時間。
任務狀態之間的轉換如下圖所示:

2.4 任務優先級
每個任務都可以分配一個從0~configMAX_PRIORITIES-1(定義在FreeRTOSConfig.h)的優先級。優先級數字越低表示任務的優先級越低,0的優先級最低,configMAX_PRIORITIES-1的優先級最高。空閑任務的優先級最低,為0。
FreeRTOS調度器確保處於就緒態或運行態最高優先級的任務獲取處理器使用權。當宏configUSE_TIME_SLICING(默認在FreeRTOS.h中定義)定義為1的時候多個任務可以共用一個優先級,數量不限。此時處於就緒態的優先級相同的任務就會使用時間片輪轉調度器獲取運行時間。
2.5 任務控制塊
FreeRTOS的每個任務都有一些屬性需要存儲,這些屬性集合在一個結構體來表示,這個結構體就叫任務控制塊:TCB_t,這個結構體定義在task.c,如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
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; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
2.6 任務堆棧
FreeRTOS 之所以能正確的恢復一個任務的運行就是因為有任務堆棧在保駕護航,任務調度器在進行任務切換的時候會將當前任務的現場(CPU 寄存器值等)保存在此任務的任務堆棧中,等到此任務下次運行的時候就會先用堆棧中保存的值來恢復現場,恢復現場以后任務就會接着從上次中斷的地方開始運行。
創建任務的時候需要給任務指定堆棧,如果使用的函數 xTaskCreate()創建任務(動態方法)的話那么任務堆棧就會由函數 xTaskCreate()自動創建,后面分析 xTaskCreate()的時候會講解 。
堆棧大小:任務堆棧的數據類型為 StackType_t, StackType_t 本質上是 unsigned long,在 portmacro.h 中有定義。
#define portSTACK_TYPE unsigned long
typedef portSTACK_TYPE StackType_t;
可以看出 StackType_t 類型的變量為 4 個字節,那么任務的實際堆棧大小就應該是我們所定義的 4 倍(32位處理器)或8倍(64位處理器) 。
