--- 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`