# RTOS 學習筆記 - 3 > 參考資源:[科技下午茶 - RTOS-03](https://www.youtube.com/watch?v=gMarR7J5Pkc&list=PLJvU73y7BpTsNAHgz0KoRtcyq16xflK9K&index=3)。 ## FreeRTOS Configuration ### About FreeRTOS - 2003 found by Richard Barry, 當時任職於 WHIS。 - v1.2.3 (2003) change from Open Software license to GNU **GPL**。 - V10.0.0 (2017) change to **MIT** licensed, AWS 為主要管理者。 - v10.5.1 (2022 Nov.) 僅 GCC 部分可支援 49 個架構(**portable** / GCC)。 下載: - [Lastest and TLS](https://www.freertos.org/) - [GitHub](https://github.com/FreeRTOS/FreeRTOS) - [FreeRTOS Kernel](https://github.com/FreeRTOS/FreeRTOS-Kernel):僅 6 個 .c 檔(扣除 *croutine.c*)。 ### Development Environment - C Compiler for target CPU:**.c -> .o**;**.s -> .o**。 - GNU:gcc / as - convention:arch [-vender] [-os] -eabi -X - eg. `arm-none-eabi-gcc` - Linker / Archive:**.o -> .elf, .axf, .a**。 - GNU:ld / gcc, ar - Binary Translate:**.axf -> .bin, .hex**。 - GNU:objcopy - GNU Toolchain:`strip`, `objdump`, `ranlib`, `nm`, `readelf`, `size`, `gdb`。 - Download / Debug:LinkServer(CMSIS-DAP), PEmicro, SEGGER J-Link, (OpenSDA)。 - 可用 IDE 整合:Toolchain + Editor + Debugger + SDK + GUI support + ....。 ### FreeRTOS Configuration - Kernel Source:6 *.c* + 18 *.h*。 - 使用者定義:*FreeRTOSConfig.h*(置頂於 *FreeRTOS.h*)。 - 記憶體管理:*portable/MemMang/**heap_[1-5]**.c*。 - portable 資料夾配置:**Toolchain/Platform/**;eg. *portable/GCC/ARM_CM4F/...*。 ## FreeRTOS Task Management ### Kernel / User space FreeRTOS 「無區分」內核(Micro Kernel)及 User space,全部的動作都在 User space 執行,無所謂「控制權還給 Kernel」的情形。 ![圖片](https://hackmd.io/_uploads/Sy70SqwCA.png =80%x) > FreeRTOS 沒有提供 privileged bit 的設置! 補充: ![圖片](https://hackmd.io/_uploads/HktdIcDRA.png =80%x) > RTOS 如 REXC、Nucleus 是有 kernel / user space 的架構觀的。 ### Idle task ![圖片](https://hackmd.io/_uploads/SJfJKcP00.png =50%x) 鑒於 FreeRTOS 無階層概念,以上 Task 交換流程是由「**最低優先權且總是 Ready 的 Idle task**」來協助維持。 > 本來該是由 Kernel Thread 做的事情,交給 Idle Task 做。 撰寫一 main 程式,建立一 task 並執行 `vTaskStartScheduler`,於 [task.c](https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/tasks.c#L3658) 中,觀察該函式如下: ```c=3658 void vTaskStartScheduler( void ) { BaseType_t xReturn; traceENTER_vTaskStartScheduler(); #if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 ) { /* Sanity check that the UBaseType_t must have greater than or equal to * the number of bits as confNUMBER_OF_CORES. */ configASSERT( ( sizeof( UBaseType_t ) * taskBITS_PER_BYTE ) >= configNUMBER_OF_CORES ); } #endif /* #if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 ) */ xReturn = prvCreateIdleTasks(); #if ( configUSE_TIMERS == 1 ) { if( xReturn == pdPASS ) { xReturn = xTimerCreateTimerTask(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TIMERS */ /*...*/ traceRETURN_vTaskStartScheduler(); } ``` 首先動態配置記憶體(預設),並於 3672 行執行 `prvCreateIdleTasks` 函式,處理 Idle Task 相關操作。 觀察 Idle Task 相關函式 `static portTASK_FUNCTION( prvIdleTask, pvParameters )` 如下: ```c=5760 static portTASK_FUNCTION( prvIdleTask, pvParameters ) { /* Stop warnings. */ ( void ) pvParameters; /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE * SCHEDULER IS STARTED. **/ /* In case a task that has a secure context deletes itself, in which case * the idle task is responsible for deleting the task's secure context, if * any. */ portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE ); #if ( configNUMBER_OF_CORES > 1 ) { /* SMP all cores start up in the idle task. This initial yield gets the application * tasks started. */ taskYIELD(); } #endif /* #if ( configNUMBER_OF_CORES > 1 ) */ for( ; configCONTROL_INFINITE_LOOP(); ) { /* See if any tasks have deleted themselves - if so then the idle task * is responsible for freeing the deleted task's TCB and stack. */ prvCheckTasksWaitingTermination(); #if ( configUSE_PREEMPTION == 0 ) { /* If we are not using preemption we keep forcing a task switch to * see if any other task has become available. If we are using * preemption we don't need to do this as any task becoming available * will automatically get the processor anyway. */ taskYIELD(); } #endif /* configUSE_PREEMPTION */ #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) { /* When using preemption tasks of equal priority will be * timesliced. If a task that is sharing the idle priority is ready * to run then the idle task should yield before the end of the * timeslice. * * A critical region is not required here as we are just reading from * the list, and an occasional incorrect value will not matter. If * the ready list at the idle priority contains one more task than the * number of idle tasks, which is equal to the configured numbers of cores * then a task other than the idle task is ready to execute. */ if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) configNUMBER_OF_CORES ) { taskYIELD(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */ #if ( configUSE_IDLE_HOOK == 1 ) { /* Call the user defined function from within the idle task. */ vApplicationIdleHook(); } #endif /* configUSE_IDLE_HOOK */ /* This conditional compilation should use inequality to 0, not equality * to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when * user defined low power mode implementations require * configUSE_TICKLESS_IDLE to be set to a value other than 1. */ #if ( configUSE_TICKLESS_IDLE != 0 ) { TickType_t xExpectedIdleTime; /* It is not desirable to suspend then resume the scheduler on * each iteration of the idle task. Therefore, a preliminary * test of the expected idle time is performed without the * scheduler suspended. The result here is not necessarily * valid. */ xExpectedIdleTime = prvGetExpectedIdleTime(); if( xExpectedIdleTime >= ( TickType_t ) configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { vTaskSuspendAll(); { /* Now the scheduler is suspended, the expected idle * time can be sampled again, and this time its value can * be used. */ configASSERT( xNextTaskUnblockTime >= xTickCount ); xExpectedIdleTime = prvGetExpectedIdleTime(); /* Define the following macro to set xExpectedIdleTime to 0 * if the application does not want * portSUPPRESS_TICKS_AND_SLEEP() to be called. */ configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime ); if( xExpectedIdleTime >= ( TickType_t ) configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { traceLOW_POWER_IDLE_BEGIN(); portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); traceLOW_POWER_IDLE_END(); } else { mtCOVERAGE_TEST_MARKER(); } } ( void ) xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TICKLESS_IDLE */ #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_PASSIVE_IDLE_HOOK == 1 ) ) { /* Call the user defined function from within the idle task. This * allows the application designer to add background functionality * without the overhead of a separate task. * * This hook is intended to manage core activity such as disabling cores that go idle. * * NOTE: vApplicationPassiveIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, * CALL A FUNCTION THAT MIGHT BLOCK. */ vApplicationPassiveIdleHook(); } #endif /* #if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_PASSIVE_IDLE_HOOK == 1 ) ) */ } } ``` 可在 *FreeRTOSConfig.h* 觀察各參數的預設值,搭配註解即可大致了解其運作。 值得注意的是關於 Power-saving 的操作:觀察第 5831 行開始,運作邏輯是,若 Idle task 發現無事情可做,則會於 5844 行透過 SysTick 進行休眠。 ### Daemon Task 另有額外常駐的 Timer Task,負責 Software Timer API 的運作。 觀察 `vTaskStartScheduler` 第 3678 行,即透過 `xTimerCreateTimerTask` 函式,執行 Timer Task 相關操作;亦可觀察 *FreeRTOSConfig.h* 對 Timer Task 之優先權預設為 2。 於 [timer.c](https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/timers.c#L748) 觀察 `static portTASK_FUNCTION( prvTimerTask, pvParameters )` 函式: ```c=748 static portTASK_FUNCTION( prvTimerTask, pvParameters ) { TickType_t xNextExpireTime; BaseType_t xListWasEmpty; /* Just to avoid compiler warnings. */ ( void ) pvParameters; #if ( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 ) { /* Allow the application writer to execute some code in the context of * this task at the point the task starts executing. This is useful if the * application includes initialisation code that would benefit from * executing after the scheduler has been started. */ vApplicationDaemonTaskStartupHook(); } #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */ for( ; configCONTROL_INFINITE_LOOP(); ) { /* Query the timers list to see if it contains any timers, and if so, * obtain the time at which the next timer will expire. */ xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty ); /* If a timer has expired, process it. Otherwise, block this task * until either a timer does expire, or a command is received. */ prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty ); /* Empty the command queue. */ prvProcessReceivedCommands(); } } ``` 所謂 Daemon Task 即創建來處理「Software API」操作。 766 行 `for` 迴圈中,首先透過 `prvGetNextExpireTime` 檢查有無註冊後的 Callback function「時間到了」,若確定,則透過 `prvProcessTimerOrBlockTask` 來執行該 Callback function。 > `prvProcessTimerOrBlockTask` 中會執行 `prvProcessExpiredTimer` 函式,函式中會透過 `pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );` 完成 Callback 的動作。 其實 Timer 的運作是搭配 Queue 來完成(詳細可參考 Timer 相關 API),Timer 會把我們給它的所有工作丟到 Queue 中,即我們給它 command,API 就只是照順序將該 command 透過 `xQueueSendToBack` 函式放入 Queue 中。 最後使用 777 行的 `prvProcessReceivedCommands` 函式獲取這些 Queue,並進一步執行「調整 Timer 的觸發時間、Delete、Stop」等動作。 ### 3 種 Queue 管理 Tasks 可於 [tasks.c](https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/tasks.c#L455) 中觀察: ```c=455 /* Lists for ready and blocked tasks. -------------------- * xDelayedTaskList1 and xDelayedTaskList2 could be moved to function scope but * doing so breaks some kernel aware debuggers and debuggers that rely on removing * the static qualifier. */ PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /**< Prioritised ready tasks. */ PRIVILEGED_DATA static List_t xDelayedTaskList1; /**< Delayed tasks. */ PRIVILEGED_DATA static List_t xDelayedTaskList2; /**< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */ PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /**< Points to the delayed task list currently being used. */ PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /**< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */ PRIVILEGED_DATA static List_t xPendingReadyList; /**< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */ ``` - **Ready**:`ReadyTasksLists[configMAX_PRIORITIES]`。 - **Suspend**:`xSuspendedTaskList`。 - **Blocked**:`xDelayedTaskList1`, `xDelayedTaskList2`。 > 注意,**Running** 的 Task 是透過 **Ready** 中的 `pxCurrentPCB` 來操作,不會透過 Queue 來管理。 ### About MPU *mpu_xx.h* 可搭配 MPU 提供進階的保護,但仍與真正的「特權模式的啟用」無關。 ### Summary FreeRTOS 的 Task Management 設計簡易且堪用(**複雜度**、**效能**、安全性、擴展性等)。 > 複雜度低、效能佳;然安全性、管理方面則有疑慮。 ## FreeRTOS Queue Management 簡易運作流程如下圖,同時也是 IPC 的典型例子: ![圖片](https://hackmd.io/_uploads/HJT1CowRC.png =80%x) 若不透過 Queue 如何操作? 使用 **polling**-設 `x = -1`,y 不斷 polling x,直到 x 不為 `-1`,就馬上取 x 值。 > 假設不考慮同步等問題。 ### IPC Function 實際上,兩 Tasks 之間可透過 **IPC 函式**交流即可。 如圖: ![圖片](https://hackmd.io/_uploads/rJKz1hP0C.png =40%x) > 值得注意的是,Semaphore API 也是透過 Queue 來達成。 額外相關服務如下圖: ![圖片](https://hackmd.io/_uploads/rkuaynPCA.png =40%x)