目錄
1、基本概念
2、線程建立與删除
1、線程的組成
2、線程棧
3、入口函數
3、建立和啟動線程
4、線程優先級和Tick
1、線程優先級
2、時間片
5、線程狀态
6、空閑線程
7、個人總結:
1、基本概念
對于整個單片機程式,我們稱之為application,應用程式。
使用RT-Thread時,我們可以在application中建立多個線程(thread),有些文檔也把線程稱之為任務(task).
以日常生活為例,比如這個母親要同時做兩件事,
- 喂飯:這是一個線程
- 回消息:這是另一個線程
這可以引入很多概念:
線程狀态(state):
目前正在喂飯,它是一個runnning狀态,另一個回消息的線程就是not running狀态
not running狀态可以細分為:
init:初始狀态
ready:就緒狀态
suspended:挂起狀态,等待某些資源暫時不能運作
close:關閉狀态,線程退出
優先級(priority):
我生活工作兼顧:喂飯和回消息優先級一樣,輪流做
我忙裡偷閑,還有空閑線程,休息一下
廚房着火,什麼都别說,先滅火,優先級更高
棧(Stack)
喂小孩時,我要記得上一口喂了米飯,這口要喂青菜了
回消息時,我要記得剛剛聊的是啥
做不同的線程,這些細節不一樣
對于人來收,當然是記在腦子裡
對于程式來說,是記在棧裡
每個線程有自己的棧
事件驅動
孩子吃飯太慢,先休息一會,等他咽下去了,等他提醒我,再喂下一口
2、線程建立與删除
1、線程的組成
在RT-Thread中,線程是RT-Thread中最基本的排程機關,使用rt_thread結構體表示線程。
rt_thread描述了一個線程的運作環境,也描述了這個線程所處的優先級
系統中總共有兩種線程,分别是系統線程和使用者線程
- 系統線程由RT-Thread核心建立
- 使用者線程由使用者應用程式建立
這兩類線程都會從核心對象容器中配置設定線程對象,如下圖所示:
每個線程由三部分組成:線程控制塊,線程棧和入口函數
線程控制塊
線程控制塊由結構體rt_thread表示,線程控制塊是作業系統用于管理線程的一個資料結構
它存放一些現成的資訊,例如線程優先級,線程名稱,線程狀态等,也包括線程與線程之間連接配接用的連結清單結構,線程等待事件集合等
它在rtdef.h中定義:
2、線程棧
在裸機系統中,涉及局部變量、子函數調用或中斷發生,就需要用到棧,
在RTOS系統中,每個線程運作時,也是普通函數調用,也涉及局部變量、子函數調用、中斷,也要用到棧。
但不同于裸機系統,RTOS存在多個線程,每個線程互不幹擾的,是以需要為每個線程都配置設定獨立的棧空間,這就是線程棧。
可以使用兩種方法提供線程棧:靜态配置設定和動态配置設定。棧的大小通常由使用者定義,如下使用全局數組定義一個靜态棧,大小為512位元組。
rt_uint32_t test_stack[512]
對于資源較大的MCU,可以适當的設定較大的線程棧。
也可以在初始化時設定為較大的棧,比如1K或者2K,在進入系統後,通過終端的list_thread指令可以檢視目前線程的棧的最大使用率,如果使用率超過了70%,将線程的棧在設定大一點,如果小于70%,将線程棧設定小一點。
3、入口函數
入口函數是線程要運作的函數,由使用者自行設計。
可分為無限循環模式和順序執行模式
void thread_entry(void *parameter)
{
while(1)
{
/*等待事件發生*/
/*對事件進行服務,進行處理*/
}
}
使用這種模式時,需要注意,一個實時作業系統,不應該讓一個線程一直處于最高優先級占用CPU,讓其他線程得不到執行。
是以在這種模式下,需要調用延時函數或者主動挂起。這種無限循環模式設計的目的是讓這個線程一直循環排程運作,而不結束。
順序執行模式
static void thread_entry(void *parameter)
{
/*處理事務1*/
...
/*處理事務2*/
...
/*處理事務3*/
}
使用這種模式時線程不會一直循環,最後一定會執行完畢。
執行完畢之後,線程将被系統自動删除。
以上就是線程的三要素:線程控制塊,線程棧,入口函數;
線程的五種狀态:初始态,就緒态,運作态,挂起态,關閉态。
線程的三種基本形式:單次執行,周期執行,資源驅動(時間也是資源的一種)
3、建立和啟動線程
RT-Thread提供兩種線程的建立方式:
- 靜态建立:使用rt_thread_init(void *parameter)
- 動态建立:使用rt_thread_creare(void *paremeter)
這兩種方式的差別就是配置設定線程控制塊和線程棧的配置設定方式是靜态的還是動态的,動态線程是系統自動從動态記憶體堆上配置設定棧空間與線程句柄,靜态線程是由使用者配置設定棧空間和線程句柄。
靜态線程初始化函數如下:
參數說明
參數 | 描述 |
thread | 線程句柄,指向線程控制塊記憶體位址,由使用者傳入 |
name | 線程名字,由rtconfig.h中定義的RT_NAME_MAX宏指定最大長度 |
entry | 線程入口函數 |
parameter | 線程入口函數參數 |
stack_start | 線程棧起始位址 |
stack_size | 線程棧大小,機關是位元組 |
priority | 線程優先級,由rtconfig.h中定義的RT_THREAD_PRIORITY_MAX宏指定優先級範圍,假設支援的是256級優先級,範圍是0-255,數值越小,優先級越高,0代表最高優先級 |
tick | 線程的時間片大小,時間片(Tick)是作業系統的時鐘節拍,當系統中存在優先級相同的線程時,時間片大小指定線程一次排程能夠運作的最大時間長度。當在時間片運作結束之後,運作另外的同優先級線程。 |
傳回值 | 成功:RT_EOK 失敗:RT_ERROR |
動态建立函數如下:
參數說明:
參數 | 描述 |
name | 線程名字,在rtconfig.h中定義的RT_NAME_MAX宏定義指定最大長度 |
entry | 線程入口函數 |
parameter | 線程入口函數參數 |
stack_size | 線程棧大小,機關是位元組 |
priority | 線程優先級,由rtconofig.h中定義的RT_THREAD_PRIORITY_MAX宏指定優先級範圍,假設支 持的是256級優先級,那麼範圍是0-255,數值越小,優先級越高 |
tick | 線程的的時間片大小,時間片(Tick)是作業系統的時鐘節拍,當系統中存在 相同優先級的線程時,時間片大小指定線程一次排程能夠運作的最大時間長度 目前線程的時間片運作結束後,運作另外的同優先級線程 |
傳回值 | 成功:thread,線程句柄 失敗:RT_NULL |
建立線程後,還需要啟動線程,才能讓線程運作起來。
啟動線程函數如下:
rt_err_t rt_thread_startup(rt_thread_t thread)
參數說明:
參數 | 描述 |
thread | 線程句柄 |
傳回值 | 成功:RT_EOK 失敗:-RT_ERROR |
示例1:建立線程
使用靜态建立和動态建立兩種方法分别建立兩個線程
線程1的代碼:
/*線程1的入口函數*/
static void thread1_entry(void *parameter)
{
const char *thread_name = "Thread1_run\r\n";
volatile rt_uint32_t cnt = 0;
/*線程1*/
while(1)
{
/*列印線程1的資訊*/
rt_kprintf(thread_name);
/*延遲一會(比較簡單粗暴)*/
for(cnt=0;cnt<100000;cnt++)
{
}
}
}
線程2的代碼:
/*線程2入口函數*/
static void thread_entry(void *parametr)
{
const char *thread_name = "Thread2 run";
volatile rt_uint32_t cnt = 0;
/*線程2*/
while(1)
{
/*列印線程2的資訊*/
rt_kprintf(thread_name);
}
/*延遲一會,簡單粗暴*/
for(cnt = 0;cnt<100000;cnt++)
{
}
}
靜态建立線程:
rt_thread_init(&thread1, //線程句柄
"thread1" //線程名字
thread1_entry, //入口函數
RT_NULL, //入口函數參數
&thread1_stack[0], //線程棧起始位址
sizeof(thread1_stack), //棧大小
THREAD_PRIORITY, //線程優先級
THREAD_TIMESLICE //線程時間片大小
);
rt_thread_startup(&thread1);
動态建立線程:
thread2 = rt_thread_create("thread2", //線程名字
thread2_entry, //入口函數
RT_NULL, //入口函數參數
THREAD__STACK_SIZE, //棧大小
THREAD_PRIORITY, //線程優先級
THREAD_TIMESLICE //線程時間片
)
4、線程優先級和Tick
1、線程優先級
RT-Thread的線程優先級是指線程被排程的優先程度
每個線程都有優先級,線程的重要性越高,優先級應該設定的越高,被排程的可能才會更大。
由rtconfig.h中定義的RT_THREAD_PRIORITY宏指定優先級範圍,RT-Thread最大支援256級優先級,數值越小的優先級越高,0為最高優先級。
在學習排程方法之前,隻要粗略的知道,RT-Thread會確定優先級最高,可運作的線程,馬上就能執行。
RT-Thread會確定優先級最高的,可運作的線程馬上就能執行。
對于相同優先級的,可運作的線程,輪流執行。
舉個例子,
廚房着火了,當然優先滅火
喂飯,回複資訊同樣重要,輪流做
2、時間片
RT-Thread中也有心跳,它使定時器産生固定間隔的中斷,這叫Tick,滴答,比如每1ms發生一次時鐘中斷。
5、線程狀态
簡單分為運作态和非運作态,具體的非運作态又可以細分,
初始态(RT_THREAD_INIT):建立線程的時候會将線程的狀态設定為初始态
就緒态(RT_THREAD_READY):該線程就在就緒清單中,就緒的線程已經具備執行的能力,隻等待CPU
運作态(RT_THREAD_RUNNING):該線程正在運作,此時它正占用CPU
挂起态(RT_THREAD_SUSPEND):如果線程目前正在等待某個時序或外部中斷,我們就說這個線程處于挂起狀态,該線程不在就緒清單中,包含線程被挂起、線程被延時,線程正在等待信号量、讀寫隊列或者等待讀寫事件
關閉态(RT_THREAD_CL):該線程運作結束,等待系統回收資源。
線程狀态遷移
RT_Thread系統中的每一個線程都有多種運作狀态,他們之間的轉換關系如下圖所示,從運作态變成阻塞态、或者從阻塞态變成就緒态,這些線程狀态是怎麼遷移的呢?
6、空閑線程
空閑線程是系統中一個比較特殊的線程,它具有最低的優先級,當系統中無其他線程可運作時,排程器将排程到空閑線程。空閑線程通常是一個死循環,永遠不挂起。
RT_Thread實時作業系統為空閑線程提供了一個鈎子函數(鈎子函數:使用者提供的一段代碼,在系統運作的某一路徑上設定一個鈎子,當系統經過這個位置時,轉而執行這個鈎子函數,然後再傳回到它的正常路徑上) ,可以讓系統在空閑的時候執行一些特定的任務,例如系統運作訓示燈閃爍,電源管理等,除了調用鈎子函數,RT_Thread也把線程清理(rt_thread_cleanup)函數,真正的線程删除動作放到了空閑線程中(在删除線程時,僅改變線程的狀态為關閉狀态不在參與系統排程)。
排程器相關接口
排程器初始化
在系統啟動時需要執行排程器的初始化,以初始化系統排程器用到的一些全局變量。排程器初始化可以調用下面的接口:
void rt_system_schduler_init(void)
啟動排程器
void rt_system_scheduler(void)
執行排程
void rt_schedule(void)
設定排程器鈎子函數
在整個系統的運作時,系統都處于線程運作、中斷觸發-響應中斷、切換到其他線程,甚至是線程間的切換過程中,或者說系統的上下文切換是系統中最普遍的事。有時使用者可能會想知道在一個時刻發生了什麼樣的線程切換,可以通過調用下面的函數接口設定一個響應的鈎子函數。在系統線程切換時,這個鈎子函數将被調用
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from,struct rt_thread* to))
7、個人總結:
建立線程主要可以看成四個步驟:
1、動态配置設定或者靜态配置設定線程控制塊
2、動态配置設定或者靜态配置設定線程棧
3、建立入口函數
4、構造棧内容(線程控制塊結構體的成員)
上述函數的作用:調整sp和虛構棧内容
線程排程
由搶占式優先級和時間片輪轉排程算法進行排程
RT-Thread啟動流程RT-Thread啟動流程_~Old的部落格-CSDN部落格