---
# System prepended metadata

title: 'RTOS - FreeRTOS Configuration, Task & Queue Management'
tags: [RTOS]

---

# 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)
