# 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」的情形。

> FreeRTOS 沒有提供 privileged bit 的設置!
補充:

> RTOS 如 REXC、Nucleus 是有 kernel / user space 的架構觀的。
### Idle task

鑒於 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 的典型例子:

若不透過 Queue 如何操作?
使用 **polling**-設 `x = -1`,y 不斷 polling x,直到 x 不為 `-1`,就馬上取 x 值。
> 假設不考慮同步等問題。
### IPC Function
實際上,兩 Tasks 之間可透過 **IPC 函式**交流即可。
如圖:

> 值得注意的是,Semaphore API 也是透過 Queue 來達成。
額外相關服務如下圖:
