---
title: 'RTOS - Thread 概念'
disqus: kyleAlien
---
RTOS - Thread 概念
===
## OverView of Content
一個優秀的項目在規劃時就會明確規範了基本的 Thread 功能,還有其調度方法
[TOC]
## RT Thread 概述
RTOS 屬於優先權導向與可搶佔的即時排程:每個任務都依照優先權排序執行,若有高優先任務進入 ready 狀態,會立即中斷低優先任務,確保即時性。
某些 RTOS 也可以設定為非搶佔模式,如下圖中所示,一個任務執行完才切換到下一個
> 
:::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 的記憶體空間**
> 
* `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
**內核會維護一個內核的全局列表**,把這些對象放置在各自的雙向鏈表裡面
> 
* **`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();
}
```
> 
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");
}
```
> 
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 並沒有印出最後的文字
> 
## 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 (資源回收)
:::
> 
### 塞滿 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` 不會按照順序執行**,由於優先度關係,兩者優先度相同,誰搶到資源誰運作
:::
> 
### 觀察 Thread 調度 - rt_scheduler_sethook
* RTT 有提供一個 hook 函數,在 Thread 進行切換之前,可以透過該函數得到呼叫的 Thread、準備被喚醒的 Thread
> 
```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);
... 省略部分
}
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `RTOS`