---
# System prepended metadata

title: RTOS - Thread 概念
tags: [RTOS]

---

---
title: 'RTOS - Thread 概念'
disqus: kyleAlien
---

RTOS - Thread 概念
===

## OverView of Content

一個優秀的項目在規劃時就會明確規範了基本的 Thread 功能，還有其調度方法

[TOC]

## RT Thread 概述
RTOS 屬於優先權導向與可搶佔的即時排程：每個任務都依照優先權排序執行，若有高優先任務進入 ready 狀態，會立即中斷低優先任務，確保即時性。

某些 RTOS 也可以設定為非搶佔模式，如下圖中所示，一個任務執行完才切換到下一個

> ![](https://i.imgur.com/RyqR7o0.png)

:::info
而等等我們也會拿 `RT-Thread` 系統來做討論；

`RT-Thread` 系統預設的確啟用了時間片機制，這點與某些強調「完全優先權導向」的 RTOS（例如 `FreeRTOS`）不同

:::

### RTOS Thread 調度

1. **每個 Thread 都有不同任務**：按照實現的功能不能分解成不同的 Thread

2. **CPU 時間片**：該 CPU 時間切片可以使用 Env 工具進行配置，**RTT（`RT-Thread`）默認每秒調度 1000 次，也就是時間切片是 1ms**
    
    :::success
    * **硬體的石英震盪器 SysTick**
        
        SysTick 是位於 ARM 內核 (並非外圍設備)，這個震盪器是針對 OS 做的設計
    :::
    
    :::danger
    * **盡量不要修改時間切片的間隔**
        
        調整太高，OS 會不斷的切換 (**稱為上下文切換**)，有時候反而會導致效率不佳；
        
        調整太低，降低 RTOS 注重的實時性；
    :::

## RTT Thread

* RTT 在啟動時會創建 3 個 Thread
    
    1. Main Thread
    
    2. Idle Thread（優先度低）：負責回收系統資源

    3. 定時器 Thread：石英共振器是硬體時鐘，而 `定時器 Thread` 則是軟體的時鐘

### RTT 創建 Main Thread

* 在裸機的 MCU 裡面：main 函數是透過 **ARM 組語來調用**，最後才進入 main 的入口

* 有個 OS 系統後的單晶片：**main 函數本身就是一個 Thread 來進行處理**，以下分析入口為 `$Sub$$main()` 函數

    ```c=
    // components.c

    int $Sub$$main(void)
    {
        // @ 分析 rtthread_startup 函數
        rtthread_startup();
        return 0;
    }

    int rtthread_startup(void)
    {
        ... 省略部分

        /* create init_thread */
        // @ 分析 rt_application_init 函數
        rt_application_init();

        /* timer thread initialization */
        rt_system_timer_thread_init();

        /* idle thread initialization */
        rt_thread_idle_init();
        
        ... 省略部分
        return 0;
    }
    ```
    1. 從上我們可以看到在執行 main 之前確實看到了類似創建 Thread 的 3 個函數，接著我們來看看 `rt_application_init` 函數
        
        ```c=
        // components.c

        void rt_application_init(void)
        {
            rt_thread_t tid;

        #ifdef RT_USING_HEAP
            // @ 分析 rt_thread_create
            tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                                   RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);

            RT_ASSERT(tid != RT_NULL);
        #else
            ... 省略

        #endif /* RT_USING_HEAP */

            rt_thread_startup(tid);
        }
        ```
        * components#**rt_thread_create 函數只要是創建一個 Thread 物件（對象），並分配 Thread 的記憶體空間**
            > ![](https://i.imgur.com/QFAWAtO.png)

        * `rt_thread_create` 參數功能如下

            | **rt_thread_create** 參數 (上到下) | 功能 |
            | -------- | -------- |
            | `const char *name` | Thread 的名稱 (必須唯一) |
            | `void (*entry)(void *parameter)` | Thread 執行的 Function (從這裡可以看出來 Thread 本質上就是不斷執行某個 Function) |
            | `void *parameter` | 創建 Thread 的參數 |
            | `rt_uint32_t stack_size` | Thread **私有** 的 Stack 大小 |
            | `rt_uint8_t priority` | Thread 優先度 |
            | `rt_uint32_t tick` | Thread 執行 Function 的時間 |

            :::info
            * OS 如何管理、維護 Thread 訊息 ?

                首先 OS 內核相關的東西都稱為 **內核對象** (eg.Thread、定時器、信號、信號量、互斥量... 等等)，這些對象其實都是 struct

                **內核會維護一個內核的全局列表**，把這些對象放置在各自的雙向鏈表裡面

                > ![](https://i.imgur.com/JjjhWRm.png)


            * **`rt_thread_create` 返回值**：創建完 Thread 後會返回一個句柄，這個 **句柄其實就是 Thread 的地址，透過地址可以快速地操控 Thread**，而不用迭代列表來查詢 !
            :::

    2. 從上面我們知道 Thread 就是某一個函數，而 **`rt_application_init` 函數要 Thread 執行的是 `main_thread_entry` 函數** !

        `$Super$$main()` 是由 ARM 編譯擴展接口，目前先不深究，只需知道最終它會調用到 main 函數
        ```c=
        // component.c

        void main_thread_entry(void *parameter)
        {
            extern int main(void);

            ...

        #ifdef RT_USING_SMP
            rt_hw_secondary_cpu_up();
        #endif /* RT_USING_SMP */
            /* invoke system main function */
        #ifdef __ARMCC_VERSION
            {
                extern int $Super$$main(void);
                // 最終執行 main
                $Super$$main(); /* for ARMCC. */
            }
        #elif defined(__ICCARM__) || defined(__GNUC__) || defined(__TASKING__)
            main();
        #endif
        }
        ```

### RTT 內核 Thread 物件

* 創建 Thread 物件最主要分類有兩大類

    1. **動態創建**：透過 OS 從堆中動態分配出來
    
    2. **靜態創建**：在程式中的靜態代碼，是不會變動的
        
        
        以下為 RTT 函數取名習慣，同樣分為 動態、靜態
        
        |  | 初始化 | 刪除 |
        | -------- | -------- | - |
        | 動態 | xxx_init()  | xxx_detach() |
        | 靜態 | xxx_create() | xxx_delete() |

### RTT 創建靜態 Thread

* 創建靜態 Thread，運行 `my_thread` 函數

    ```c=
    #include <rtthread.h>
    #include <rtdevice.h>
    #include "drv_common.h"
    #include "drv_gpio.h"

    /* defined the led2 pin: pd13 */
    #define LED2_PIN    GET_PIN(D, 13)

    void my_thread(void *params) {
        rt_uint32_t speed = 200;

        /* set led2 pin mode to output */
        rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);


        while (1){

            rt_pin_write(LED2_PIN, PIN_LOW);
            rt_thread_mdelay(speed);
            rt_pin_write(LED2_PIN, PIN_HIGH);
            rt_thread_mdelay(speed);

        }

    }

    void create_static_thread(void) {
        rt_thread_t tid;

        tid = rt_thread_create("static_thread", my_thread, RT_NULL, 256, 7, 20);

        RT_ASSERT(tid != RT_NULL);

        rt_thread_startup(tid);
    }

    int main(void)
    {
        create_static_thread();
    }
    ```
    > ![](https://i.imgur.com/buI4Ces.png)

    1. 自動退出 Thread：修改 `my_thread` 函數，在退出時打印

        ```c=
        void my_thread(void *params) {
            rt_uint32_t speed = 200;

            /* set led2 pin mode to output */
            rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

            uint16_t i = 0;

            while (1){
                i++;

                rt_pin_write(LED2_PIN, PIN_LOW);
                rt_thread_mdelay(speed);
                rt_kprintf("LED Open\n");

                rt_pin_write(LED2_PIN, PIN_HIGH);
                rt_thread_mdelay(speed);
                rt_kprintf("LED Close\n");

                if(i == 5) {
                    break;
                }
            }

            rt_kprintf("Thread Finish\n");
        }
        ```

        > ![](https://i.imgur.com/DuiLumd.png)

    2. 手動退出：**手動退出 Thread 就不會按照原本結束的方式執行，較像是強行退出**

        ```c=
        #include <rtthread.h>
        #include <rtdevice.h>
        #include "drv_common.h"
        #include "drv_gpio.h"

        /* defined the led2 pin: pd13 */
        #define LED2_PIN    GET_PIN(D, 13)
        /* defined the led3 pin: pd14 */
        #define LED3_PIN    GET_PIN(D, 14)
        /* defined the led4 pin: pd15 */
        #define LED4_PIN    GET_PIN(D, 15)

        rt_thread_t tid;

        void my_thread(void *params) {
            rt_uint32_t speed = 200;

            /* set led2 pin mode to output */
            rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

            while (1){
                rt_pin_write(LED2_PIN, PIN_LOW);
                rt_thread_mdelay(speed);
                rt_kprintf("LED Open\n");

                rt_pin_write(LED2_PIN, PIN_HIGH);
                rt_thread_mdelay(speed);
                rt_kprintf("LED Close\n");
            }
            rt_kprintf("Thread Finish\n");
        }

        void create_static_thread(void) {
            tid = rt_thread_create("static_thread", my_thread, RT_NULL, 256, 7, 20);

            RT_ASSERT(tid != RT_NULL);

            rt_thread_startup(tid);
        }

        int main(void)
        {
            create_static_thread();

            uint16_t i = 0;

            while(1) {
                rt_kprintf("Test: %d\n", i++);
                rt_thread_mdelay(1000);

                if(i == 5) {
                    rt_thread_delete(tid);
                    rt_kprintf("Handle kill thread finish\n");
                    break;
                }
            }
        }
        ```
        由於強制退出，所以 my_thread 並沒有印出最後的文字 
        > ![](https://i.imgur.com/pFxPBfr.png)

## Thread 休眠

這裡我們創建 3 個 Thread 來測試 CPU 休眠的行為

### 讓出 CPU 資源 - `rt_thread_mdelay`

* RTT OS 所提供的 `rt_thread_mdelay` 可以讓當前正的運行的 Thread 休眠指定時間

    ```c=
    #define LED_1_PRIO 		10        // 設定相同優先度
    #define LED_1_STACK 	256
    #define LED_1_TICKS		1000

    #define LED_2_PRIO 		10        // 設定相同優先度
    #define LED_2_STACK 	256
    #define LED_2_TICKS		1000

    void led_thread_1(void *params) {
        uint32_t speed = 200;
        uint16_t action = 0;

        while(1) {
            action = rt_pin_read(LED2_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED2_PIN, action);

            rt_thread_mdelay(speed);
        }
        rt_kprintf("led thread 1 finish.");
    }

    void led_thread_2(void *params) {
        uint32_t speed = 200;
        uint16_t action = 0;

        while(1) {
            action = rt_pin_read(LED3_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED3_PIN, action);

            rt_thread_mdelay(speed);
        }
        rt_kprintf("led thread 2 finish.");
    }

    int main(void)
    {
        rt_uint32_t speed = 200;
        /* set led2 pin mode to output */
        rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
        /* set led3 pin mode to output */
        rt_pin_mode(LED3_PIN, PIN_MODE_OUTPUT);
        /* set led4 pin mode to output */
        rt_pin_mode(LED4_PIN, PIN_MODE_OUTPUT);

        rt_thread_t tid;

        tid = rt_thread_create("led_thread_1", led_thread_1, RT_NULL, 
                LED_1_STACK, 
                LED_1_PRIO, 
                LED_1_TICKS);

        RT_ASSERT(tid != RT_NULL);
        rt_thread_startup(tid);

        tid = rt_thread_create("led_thread_2", led_thread_2, RT_NULL, 
                LED_2_STACK, 
                LED_2_PRIO, 
                LED_2_TICKS);
        RT_ASSERT(tid != RT_NULL);
        rt_thread_startup(tid);

        uint16_t action = 0;

        while(1) {
            
            rt_kprintf("led main thread doing\n");
            
            action = rt_pin_read(LED4_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED4_PIN, action);

    #if HARD_DELAY == 1
            // TODO: (下個實驗)
    #else
            rt_thread_mdelay(speed);
    #endif

        }
        rt_kprintf("led thread 2 finish.");
    }
    ```

    :::info
    * 執行結果
        
        當 Main Thread 進行休眠後，OS 就會去尋找列表裝是否有另外需要執行的 Thread，如果沒有才會去執行低優先度的 Idle Thread (資源回收)
    :::
    > ![](https://i.imgur.com/4BiEyVh.png)

### 塞滿 CPU 資源

* 現在新增一個 `_delay_us` 函數，該函數模擬 CPU 休眠，**但其實並 ++不是真的休眠++，是在回圈內運作 !!**（這是個耗廢 CPU 資源的事情，CPU 並沒有辦法去執行其他事項）
    

    ```c=
    #define HARD_DELAY		1

    #define LED_1_PRIO 		10
    #define LED_1_STACK 	256
    #define LED_1_TICKS		1000

    #define LED_2_PRIO 		10
    #define LED_2_STACK 	256
    #define LED_2_TICKS		1000

    #if HARD_DELAY == 1
    static void _delay_us(uint32_t us) {
        volatile uint32_t len;

        for(; us > 0; us--) {
            for(len = 0; len < 20; len++);
        }
        rt_kprintf("_delay_us done\n");
    }
    #endif

    void led_thread_1(void *params) {
        uint32_t speed = 200;
        uint16_t action = 0;

        while(1) {
            rt_kprintf("led thread 1 doing\n");

            action = rt_pin_read(LED2_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED2_PIN, action);

            rt_thread_mdelay(speed);
        }
        rt_kprintf("led thread 1 finish.");
    }

    void led_thread_2(void *params) {
        uint32_t speed = 200;
        uint16_t action = 0;

        while(1) {
            rt_kprintf("led thread 2 doing\n");

            action = rt_pin_read(LED3_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED3_PIN, action);

            rt_thread_mdelay(speed);
        }
        rt_kprintf("led thread 2 finish.");
    }

    int main(void)
    {
        rt_uint32_t speed = 200;
        /* set led2 pin mode to output */
        rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
        /* set led3 pin mode to output */
        rt_pin_mode(LED3_PIN, PIN_MODE_OUTPUT);
        /* set led4 pin mode to output */
        rt_pin_mode(LED4_PIN, PIN_MODE_OUTPUT);

        rt_thread_t tid;

        tid = rt_thread_create("led_thread_1", led_thread_1, RT_NULL, 
                LED_1_STACK, 
                LED_1_PRIO, 
                LED_1_TICKS);

        RT_ASSERT(tid != RT_NULL);
        rt_thread_startup(tid);

        tid = rt_thread_create("led_thread_2", led_thread_2, RT_NULL, 
                LED_2_STACK, 
                LED_2_PRIO, 
                LED_2_TICKS);
        RT_ASSERT(tid != RT_NULL);
        rt_thread_startup(tid);

        uint16_t action = 0;

        while(1) {
            rt_kprintf("led main thread doing\n");

            action = rt_pin_read(LED4_PIN) ? PIN_LOW : PIN_HIGH;

            rt_pin_write(LED4_PIN, action);

    #if HARD_DELAY == 1
            // 大概耗時 200ms
            _delay_us(speed * 1000);
    #else
            rt_thread_mdelay(speed);
    #endif

        }
        rt_kprintf("led thread 2 finish.\n");

    }
    ```
    :::warning
    * 運行結果
    
        運行以下程式可以發現在 `MainThread` 運行完之前，`led_thread_1`、`led_thread_2` 的 LED 不會閃爍，**因為 `MainThread` 其實並沒有真正讓出 CPU 資源，仍在迴圈中運行**
    :::
    :::info
    * 在 main 之後的 **`led_thread_1`、`led_thread_2` 不會按照順序執行**，由於優先度關係，兩者優先度相同，誰搶到資源誰運作
    :::
    > ![](https://i.imgur.com/ux2T75H.png)

### 觀察 Thread 調度 - rt_scheduler_sethook 

* RTT 有提供一個 hook 函數，在 Thread 進行切換之前，可以透過該函數得到呼叫的 Thread、準備被喚醒的 Thread
    
    > ![](https://i.imgur.com/2zVFElk.png)
    ```c=
    void show_thread_schdule(struct rt_thread *from, struct rt_thread *to) {
        rt_kprintf("from: %s ---> to: %s \n", from->name, to->name);
    }	

    int main(void)
    {
        rt_uint32_t speed = 200;
        /* set led2 pin mode to output */
        rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
        /* set led3 pin mode to output */
        rt_pin_mode(LED3_PIN, PIN_MODE_OUTPUT);
        /* set led4 pin mode to output */
        rt_pin_mode(LED4_PIN, PIN_MODE_OUTPUT);

        
        // 註冊 hook 函數，在每次 Thread 切換時都會被調用
        rt_scheduler_sethook(show_thread_schdule);
        
        ... 省略部分
    
    }
    ```
    > ![](https://i.imgur.com/YBVDFXa.png)


## Appendix & FAQ

:::info
:::

###### tags: `RTOS`
