1. 軟硬件平臺
- GD32F307E-START Board開發板
- MDK-ARM Keil
2.RT-Thread Nano
3.RT-Thread 內核學習-線程管理
? 在多線程操作系統中,可以把一個復雜的應用分解成多個小的、可調度的、序列化的程序單元,當合理地劃分任務并正確地執行時,這種設計能夠讓系統滿足實時系統的性能及時間的要求。在多線程實時系統中,可以將這個任務分解成子任務。
? 在 RT-Thread 中,與上述子任務對應的程序實體就是線程,線程是實現任務的載體,它是 RT-Thread 中最基本的調度單位,它描述了一個任務執行的運行環境,也描述了這個任務所處的優先等級,重要的任務可設置相對較高的優先級,非重要的任務可以設置較低的優先級,不同的任務還可以設置相同的優先級,輪流運行。(RT-Thread 中Thread 對應于FreeRTOS中的task)。
當線程運行時,它會認為自己是以獨占 CPU 的方式在運行,線程執行時的運行環境稱為上下文,具體來說就是各個變量和數據,包括所有的寄存器變量、堆棧、內存信息等。
? RT-Thread 線程管理的主要功能是對線程進行管理和調度,系統中總共存在兩類線程,分別是系統線程和用戶線程,**系統線程是由 RT-Thread 內核創建的線程,用戶線程是由應用程序創建的線程,**這兩類線程都會從內核對象容器中分配線程對象,當線程被刪除時,也會被從對象容器中刪除,如下圖所示,每個線程都有重要的屬性,如線程控制塊、線程棧、入口函數等。
RT-Thread 的線程調度器是搶占式的,主要的工作就是從就緒線程列表中查找最高優先級線程,保證最高優先級的線程能夠被運行,最高優先級的任務一旦就緒,總能得到 CPU 的使用權。
當一個運行著的線程使一個比它優先級高的線程滿足運行條件,當前線程的 CPU 使用權就被剝奪了,或者說被讓出了,高優先級的線程立刻得到了 CPU 的使用權。
如果是中斷服務程序使一個高優先級的線程滿足運行條件,中斷完成時,被中斷的線程掛起,優先級高的線程開始運行。
當調度器調度線程切換時,先將當前線程上下文保存起來,當再切回到這個線程時,線程調度器將該線程的上下文信息恢復。
重要概念1 線程控制塊
? 在 RT-Thread 中,線程控制塊由結構體 struct rt_thread 表示,線程控制塊是操作系統用于管理線程的一個數據結構,它會存放線程的一些信息,例如優先級、線程名稱、線程狀態等,也包含線程與線程之間連接用的鏈表結構,線程等待事件集合等,詳細定義如下:
/* 線程控制塊 */
struct rt_thread
{/* rt 對象 */char name[RT_NAME_MAX]; /* 線程名稱 */rt_uint8_t type; /* 對象類型 */rt_uint8_t flags; /* 標志位 */rt_list_t list; /* 對象列表 */rt_list_t tlist; /* 線程列表 *//* 棧指針與入口指針 */void *sp; /* 棧指針 */void *entry; /* 入口函數指針 */void *parameter; /* 參數 */void *stack_addr; /* 棧地址指針 */rt_uint32_t stack_size; /* 棧大小 *//* 錯誤代碼 */rt_err_t error; /* 線程錯誤代碼 */rt_uint8_t stat; /* 線程狀態 *//* 優先級 */rt_uint8_t current_priority; /* 當前優先級 */rt_uint8_t init_priority; /* 初始優先級 */rt_uint32_t number_mask;......rt_ubase_t init_tick; /* 線程初始化計數值 */rt_ubase_t remaining_tick; /* 線程剩余計數值 */struct rt_timer thread_timer; /* 內置線程定時器 */void (*cleanup)(struct rt_thread *tid); /* 線程退出清除函數 */rt_uint32_t user_data; /* 用戶數據 */
};
其中 init_priority 是線程創建時指定的線程優先級,在線程運行過程當中是不會被改變的(除非用戶執行線程控制函數進行手動調整線程優先級)。**cleanup 會在線程退出時,被空閑線程回調一次以執行用戶設置的清理現場等工作。**最后的一個成員 user_data 可由用戶掛接一些數據信息到線程控制塊中,以提供一種類似線程私有數據的實現方式。
重要概念2 線程狀態
線程運行的過程中,同一時間內只允許一個線程在處理器中運行,從運行的過程上劃分,線程有多種不同的運行狀態,如初始狀態、掛起狀態、就緒狀態等。在 RT-Thread 中,線程包含五種狀態,操作系統會自動根據它運行的情況來動態調整它的狀態。 RT-Thread 中線程的五種狀態,如下表所示:
狀態 | 描述 |
---|---|
初始狀態 | 當線程剛開始創建還沒開始運行時就處于初始狀態;在初始狀態下,線程不參與調度。此狀態在 RT-Thread 中的宏定義為 RT_THREAD_INIT |
就緒狀態 | 在就緒狀態下,線程按照優先級排隊,等待被執行;一旦當前線程運行完畢讓出處理器,操作系統會馬上尋找最高優先級的就緒態線程運行。此狀態在 RT-Thread 中的宏定義為 RT_THREAD_READY |
運行狀態 | 線程當前正在運行。在單核系統中,只有 rt_thread_self() 函數返回的線程處于運行狀態;在多核系統中,可能就不止這一個線程處于運行狀態。此狀態在 RT-Thread 中的宏定義為 RT_THREAD_RUNNING |
掛起狀態 | 也稱阻塞態。它可能因為資源不可用而掛起等待,或線程主動延時一段時間而掛起。在掛起狀態下,線程不參與調度。此狀態在 RT-Thread 中的宏定義為 RT_THREAD_SUSPEND |
關閉狀態 | 當線程運行結束時將處于關閉狀態。關閉狀態的線程不參與線程的調度。此狀態在 RT-Thread 中的宏定義為 RT_THREAD_CLOSE |
RT-Thread 提供一系列的操作系統調用接口,使得線程的狀態在這五個狀態之間來回切換。幾種狀態間的轉換關系如下圖所示:
- 線程通過調用函數 rt_thread_create/init() 進入到初始狀態(RT_THREAD_INIT)
- 初始狀態的線程通過調用函數 rt_thread_startup() 進入到就緒狀態(RT_THREAD_READY);就緒狀態的線程被調度器調度后進入運行狀態(RT_THREAD_RUNNING)
- 當處于運行狀態的線程調用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函數或者獲取不到資源時,將進入到掛起狀態(RT_THREAD_SUSPEND)
- 處于掛起狀態的線程,如果等待超時依然未能獲得資源或由于其他線程釋放了資源,那么它將返回到就緒狀態。
- 掛起狀態的線程,如果調用 rt_thread_delete/detach() 函數,將更改為關閉狀態(RT_THREAD_CLOSE)
- 而運行狀態的線程,如果運行結束,就會在線程的最后部分執行 rt_thread_exit() 函數,將狀態更改為關閉狀態
重要概念3 線程優先級
RT-Thread 線程的優先級是表示線程被調度的優先程度。每個線程都具有優先級,線程越重要,賦予的優先級就應越高,線程被調度的可能才會越大。
**RT-Thread 最大支持 256 個線程優先級 (0~255),數值越小的優先級越高,0 為最高優先級。**在一些資源比較緊張的系統中,可以根據實際情況選擇只支持 8 個或 32 個優先級的系統配置;對于 ARM Cortex-M 系列,普遍采用 32 個優先級。最低優先級默認分配給空閑線程使用,用戶一般不使用。在系統中,當有比當前線程優先級更高的線程就緒時,當前線程將立刻被換出,高優先級線程搶占處理器運行。
絕大多數的RTOS的優先級都是數值越小,優先級越高,除了FreeRTOS。
重要概念4 系統線程:空閑線程 主線程
空閑線程
**空閑線程(idle)是系統創建的最低優先級的線程,線程狀態永遠為就緒態。當系統中無其他就緒線程存在時,調度器將調度到空閑線程,它通常是一個死循環,且永遠不能被掛起。**另外,空閑線程在 RT-Thread 也有著它的特殊用途:
若某線程運行完畢,系統將自動刪除線程:自動執行 rt_thread_exit() 函數,先將該線程從系統就緒隊列中刪除,再將該線程的狀態更改為關閉狀態,不再參與系統調度,然后掛入 rt_thread_defunct 僵尸隊列(資源未回收、處于關閉狀態的線程隊列)中,最后空閑線程會回收被刪除線程的資源。
空閑線程也提供了接口來運行用戶設置的鉤子函數,在空閑線程運行時會調用該鉤子函數,適合處理功耗管理、看門狗喂狗等工作。空閑線程必須有得到執行的機會,即其他線程不允許一直while(1)死卡,必須調用具有阻塞性質的函數;否則例如線程刪除、回收等操作將無法得到正確執行。
主線程
在系統啟動時,系統會創建 main 線程,它的入口函數為 main_thread_entry(),用戶的應用入口函數 main() 就是從這里真正開始的,系統調度器啟動后,main 線程就開始運行,過程如下圖,用戶可以在 main() 函數里添加自己的應用程序初始化代碼。
線程管理函數API
/** thread interface*/
rt_err_t rt_thread_init(struct rt_thread *thread,const char *name,void (*entry)(void *parameter),void *parameter,void *stack_start,rt_uint32_t stack_size,rt_uint8_t priority,rt_uint32_t tick);
rt_err_t rt_thread_detach(rt_thread_t thread);
rt_thread_t rt_thread_create(const char *name,void (*entry)(void *parameter),void *parameter,rt_uint32_t stack_size,rt_uint8_t priority,rt_uint32_t tick);
rt_thread_t rt_thread_self(void);
rt_thread_t rt_thread_find(char *name);
rt_err_t rt_thread_startup(rt_thread_t thread);
rt_err_t rt_thread_delete(rt_thread_t thread);rt_err_t rt_thread_yield(void);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg);
rt_err_t rt_thread_suspend(rt_thread_t thread);
rt_err_t rt_thread_resume(rt_thread_t thread);
void rt_thread_timeout(void *parameter);#ifdef RT_USING_SIGNALS //使用信號量
void rt_thread_alloc_sig(rt_thread_t tid);
void rt_thread_free_sig(rt_thread_t tid);
int rt_thread_kill(rt_thread_t tid, int sig);
#endif#ifdef RT_USING_HOOK //使用鉤子函數
void rt_thread_suspend_sethook(void (*hook)(rt_thread_t thread));
void rt_thread_resume_sethook (void (*hook)(rt_thread_t thread));
void rt_thread_inited_sethook (void (*hook)(rt_thread_t thread));
#endif/** idle thread interface*/
void rt_thread_idle_init(void);
#if defined(RT_USING_HOOK) || defined(RT_USING_IDLE_HOOK)
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
#endif
void rt_thread_idle_excute(void);
rt_thread_t rt_thread_idle_gethandler(void);
線程的創建(動態創建、靜態創建)
動態創建 rt_thread_create
rt_thread_t rt_thread_create(const char* name,void (*entry)(void* parameter),void* parameter,rt_uint32_t stack_size,rt_uint8_t priority,rt_uint32_t tick);
參數 | 描述 |
---|---|
name | 線程的名稱;線程名稱的最大長度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分會被自動截掉 |
entry | 線程入口函數 |
parameter | 線程入口函數參數 |
stack_size | 線程棧大小,單位是字節 |
priority | 線程的優先級。優先級范圍根據系統配置情況(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定義),如果支持的是 256 級優先級,那么范圍是從 0~255,數值越小優先級越高,0 代表最高優先級 |
tick | 線程的時間片大小。時間片(tick)的單位是操作系統的時鐘節拍。當系統中存在相同優先級線程時,這個參數指定線程一次調度能夠運行的最大時間長度。這個時間片運行結束時,調度器自動選擇下一個就緒態的同優先級線程進行運行 |
返回值 | |
thread | 線程創建成功,返回線程句柄 |
RT_NULL | 線程創建失敗 |
一般情況下,入口參數沒有的情況比較多。
靜態創建 rt_thread_init
rt_err_t rt_thread_init(struct rt_thread *thread,const char *name,void (*entry)(void *parameter),void *parameter,void *stack_start,rt_uint32_t stack_size,rt_uint8_t priority,rt_uint32_t tick);
參數 | 描述 |
---|---|
thread | 線程句柄。線程句柄由用戶提供出來,并指向對應的線程控制塊內存地址 |
name | 線程的名稱;線程名稱的最大長度由 rtconfig.h 中定義的 RT_NAME_MAX 宏指定,多余部分會被自動截掉 |
entry | 線程入口函數 |
parameter | 線程入口函數參數 |
stack_start | 線程棧起始地址 |
stack_size | **線程棧大小,單位是字節。**在大多數系統中需要做棧空間地址對齊(例如 ARM 體系結構中需要向 4 字節地址對齊) |
priority | 線程的優先級。優先級范圍根據系統配置情況(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定義),如果支持的是 256 級優先級,那么范圍是從 0 ~ 255,數值越小優先級越高,0 代表最高優先級 |
tick | **線程的時間片大小。**時間片(tick)的單位是操作系統的時鐘節拍。當系統中存在相同優先級線程時,這個參數指定線程一次調度能夠運行的最大時間長度。這個時間片運行結束時,調度器自動選擇下一個就緒態的同優先級線程進行運行 |
返回 | |
RT_EOK | 線程創建成功 |
RT_ERROR | 線程創建失敗 |
在靜態創建過程中,需要預先定義一個數組,提前申請空間,動態創建過程則不需要,系統會從動態堆內存中分配一個線程句柄以及按照參數中指定的棧大小從動態堆內存中分配相應的空間。
線程啟動
創建(初始化)的線程狀態處于初始狀態,并未進入就緒線程的調度隊列,我們可以在線程初始化 / 創建成功后調用下面的函數接口讓該線程進入就緒態:
rt_err_t rt_thread_startup(rt_thread_t thread);
當調用這個函數時,將把線程的狀態更改為就緒狀態,并放到相應優先級隊列中等待調度。如果新啟動的線程優先級比當前線程優先級高,將立刻切換到這個線程。線程啟動接口 rt_thread_startup() 的參數和返回值見下表:
參數 | 描述 |
---|---|
thread | 線程句柄 |
返回 | |
RT_EOK | 線程啟動成功 |
RT_ERROR | 線程啟動失敗 |
/** 程序清單:創建、初始化/脫離線程** 這個例子會創建兩個線程,一個動態線程,一個靜態線程。* 靜態線程在運行完畢后自動被系統脫離,動態線程打印計數。*/
#include <rtthread.h>#define THREAD_PRIORITY 15
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5static rt_thread_t tid1 = RT_NULL;/* 線程1的入口函數 */
static void thread1_entry(void *parameter)
{rt_uint32_t count = 0;for (count =0 ;count< 20;count++){/* 線程1采用低優先級運行,打印計數值20結束 */rt_kprintf("thread1 count: %d\n", count ++);}rt_kprintf("thread1 exit\n");
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;/* 線程2入口 */
static void thread2_entry(void *param)
{rt_uint32_t count = 0;/* 線程2擁有較高的優先級,以搶占線程1而獲得執行 */for (count = 0; count < 10 ; count++){/* 線程2打印計數值 */rt_kprintf("thread2 count: %d\n", count);}rt_kprintf("thread2 exit\n");/* 線程2運行結束后也將自動被系統脫離 */
}/* 線程示例 */
int thread_sample(void)
{/* 創建線程1,名稱是thread1,入口是thread1_entry*/tid1 = rt_thread_create("thread1",thread1_entry, RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY, THREAD_TIMESLICE);/* 如果獲得線程控制塊,啟動這個線程 */if (tid1 != RT_NULL)rt_thread_startup(tid1);/* 初始化線程2,名稱是thread2,入口是thread2_entry */rt_thread_init(&thread2,"thread2",thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY - 1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 導出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);
#include "main.h"void Hardware_Init(void)
{SystemInit (); systick_config();bsp_uart_init();HW_LED_Init(); }int main(void)
{Hardware_Init();printf("SystemInit [ok] \r\n");printf("systick_config[ok] \r\n");printf("bsp_uart_init [ok] \r\n");printf("Hardware_Init [ok] \r\n");printf("LED_Init [ok] \r\n");printf("GD32307E-START Board Testing \r\n");printf("GD32307E-START Board thread_sample test start...\r\n");thread_sample();printf("GD32307E-START Board thread_sample test end...\r\n");while(1){gpio_bit_set(GPIOC,GPIO_PIN_6);rt_thread_delay(500); /* 延時500個tick */rt_kprintf("led_thread running,LED1_ON\r\n");gpio_bit_reset(GPIOC,GPIO_PIN_6); rt_thread_delay(500); /* 延時500個tick */ rt_kprintf("led_thread running,LED1_OFF\r\n");}
}
整個程序先執行thread2,因為THREAD_PRIORITY優先級比thread1高,thread2打印完成10次計數值之后,就執行thread1,打印完成20次計數值結束。