前2課講完了RT-Thread開發環境,啟動流程,啟動以后當然是開始跑線程了,那么自然我們得學會如何創建線程以及線程的有關操作。
目錄
前言
一、RT-Thread線程操作函數
1.1 動態創建線程
1.2 靜態創建線程
1.3 啟動線程
線程創建的一個細節—創建和初始化?
句柄是什么?
1.4 刪除線程和脫離線程
1.5 掛起和恢復線程
1.6 其他線程輔助函數
1.6.1 獲得當前線程
1.6.2 讓出處理器資源
1.6.3 線程睡眠(延時函數)
1.6.4 線程控制函數
1.6.5 設置和刪除空閑鉤子
1.6.6 設置調度器鉤子
二、RT-Thread線程創建示例
2.1 靜態創建線程示例
2.1 動態創建線程示例
三、RT-Thread線程管理簡析
3.1 線程調度的基本特點
3.2 線程控制塊
3.3 線程狀態
3.4 系統線程
結語
前言
前段時間寫完 RT-Thread 版本,開發環境,啟動流程后,停了好一段時間,因為完成了前面2課的講解,感覺直接用起來都問題不大了,為啥,因為RTOS的調度,線程通訊等機制,學習過FreeRTOS,看看RT-Thread官方的文檔說明,很多東西就很清楚了= =!以至于在寫本文的時候,都感覺,是不是太簡單了?
但是后來又想了想:
1、本系列博文的目的在于總結記錄,為的是以后在項目中使用起來,我可以直接參考自己總結的博文,而不是去翻官方的文檔資料。
2、盡量使得沒有學習過 RT-Thread 的同學根據系列博文能夠對 RT-Thread 有個認識,然后在一些細節的點上面有一定的理解,同時在遇到 RT-Thread 與 FreeRTOS不同的地方,會加以說明。
3、當初的FreeRTOS系列,真就是很隨意的按照自己學習測試的流程來走,對小白來說并不友好,回頭看起來,雖然我是真的畫了精力和事件去說明遇到的問題以及解決辦法,但是少了循序漸進的過程,整體也沒有一個好的框架體系,所以好像沒有幫到太多人(看的人不多哈= =?。?。所以在 RT-Thread 系列上面,該系統的還是得系統起來,即便有些東西簡單基礎,官方和網上文檔詳細,當做一個筆記,該記錄的還是得記錄!
好的,題外話說到這里,我們回到 RT-Thread 本身,上回我們已經把啟動流程講清楚了,
上文的最后講到:整個系統就正常跑起來了,然后用戶運行自己想要做的事情,可以在 main 中設計自己的應用代碼,或者創建線程。
所以我們接下來當然得說說如何創建線程以及線程的一些操作。
本 RT-Thread 專欄記錄的開發環境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發環境 及 配合CubeMX開發快速上手)
RT-Thread記錄(二、RT-Thread內核啟動流程 — 啟動文件和源碼分析
一、RT-Thread線程操作函數
RT-Thread線程操作包含:創建 / 初始化線程、啟動線程、運行線程、刪除 / 脫離線程。
1.1 動態創建線程
函數比較簡單,具體的看注釋就好(本文余下的函數介紹類似,看注釋):
/*
demo,用來接收動態線程返回的句柄
比如 led2_thread = rt_thread_create(......);
*/
static rt_thread_t led2_thread = RT_NULL;
#ifdef RT_USING_HEAP //定義使用了HEAP才能動態創建線程
/*
參數的含義,放在上面看起來更加方便,要不然太長了
1、線程的名稱;線程名稱的最大長度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分會被自動截掉
2、線程入口函數
3、線程入口函數參數,沒有就用 RT_NULL
4、線程棧大小,單位是字節
5、線程的優先級。優先級范圍根據系統配置情況(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定義),
如果支持的是 256 級優先級,那么范圍是從 0~255,數值越小優先級越高,0 代表最高優先級
6、線程的時間片大小。時間片(tick)的單位是操作系統的時鐘節拍。
當系統中存在相同優先級線程時,這個參數指定線程一次調度能夠運行的最大時間長度。
這個時間片運行結束時,調度器自動選擇下一個就緒態的同優先級線程進行運行
返回值:
線程創建成功,返回線程句柄
線程創建失敗,返回RT_BULL
*/
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)
1.2 靜態創建線程
static struct rt_thread led1_thread; //demo,用戶定義的線程句柄
static char led1_thread_stack[256]; //demo,用戶定義的靜態線程大小
/*
參數的含義
1、線程句柄。線程句柄由用戶提供出來,并指向對應的線程控制塊內存地址,上面的led1_thread。
2、線程的名稱;線程名稱的最大長度由 rtconfig.h 中定義的 RT_NAME_MAX 宏指定,多余部分會被自動截掉
3、線程入口函數
4、線程入口函數參數,沒有就用 RT_NULL
5、線程棧起始地址,根據上面定義就是 &led1_thread_stack[0],
6、線程棧大小,單位是字節。根據上面定義就是 sizeof(led1_thread_stack),
在大多數系統中需要做棧空間地址對齊(例如 ARM 體系結構中需要向 4 字節地址對齊)
7、線程的優先級。優先級范圍根據系統配置情況(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定義),
如果支持的是 256 級優先級,那么范圍是從 0~255,數值越小優先級越高,0 代表最高優先級
8、線程的時間片大小。時間片(tick)的單位是操作系統的時鐘節拍。
當系統中存在相同優先級線程時,這個參數指定線程一次調度能夠運行的最大時間長度。
這個時間片運行結束時,調度器自動選擇下一個就緒態的同優先級線程進行運行
返回值:
線程創建成功,返回RT_EOK
線程創建失敗,返回RT_ERROR
*/
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);
這里需要說明一下,為什么用戶定義一個 char 類型的數組可以作為線程棧空間呢?
因為申請一個全局變量的數組,本質就是開辟了一段連續的內存空間!這是用戶申請的,所以在編譯的時候就被確定分配好了,這段內存空間申請出來,通過rt_thread_init
函數,就分配給了這個線程使用。
如果知道了上面的話,但是還不能理解內存空間和線程有什么關系的時候,這個就得慢慢來……簡單來說就是,線程運行需要占用一段內存空間,這段內存空間每個線程的都不一樣,他們是用來線程運行的時候,函數調用線程切換保存現場用的。
反正先記住必須給每個線程單獨的一片內存空間,RTOS才能正常運行,所有的RTOS都是。
動態創建同樣的意思,只不過你看不到,由內核函數自動處理了就沒那么直觀。
在上面示例代碼中,256個char類型的數組,就是占用256個字節(char類型占用1個字節),所以最后分配給線程的空間就是256個字節。
1.3 啟動線程
創建完線程并不代表線程就運行了,在RT-Thread稱為初始狀態,要跑起來需要人為的給他“開”一下,這里與FreeRTOS創建任務后是不同的,FreeRTOS是直接創建完成就開始運行參與調度了。
創建的線程狀態處于初始狀態,并未進入就緒線程的調度隊列,我們可以在線程創建成功后調用rt_thread_startup
函數接口讓該線程進入就緒態:
/*
static rt_thread_t led2_thread = RT_NULL;
static struct rt_thread led1_thread;
上面的兩個demo就是:
rt_thread_startup(&led1_thread);
rt_thread_startup(led2_thread);
*/
rt_err_t rt_thread_startup(rt_thread_t thread);
這里又有一個小細節需要說明一下,動態和靜態創建線程的rt_thread_startup
使用的小區別!
上面代碼的注釋中,兩個Demo:
一個是rt_thread_startup(&led1_thread);
(靜態)
一個是rt_thread_startup(led2_thread);
(動態)
靜態線程為什么需要取地址,動態可以直接用,不仔細看的話還不一定發現這個問題, 其實從他們的定義就已經不同了,只不過rt_thread_t 和rt_thread 一眼看去還真可能傻傻分不清楚 = =!以前我剛用的時候也在這里迷糊了一會
:
static struct rt_thread led1_thread 靜態類型為struct rt_thread 類型就是線程控制塊結構體
static rt_thread_t led2_thread 動態類型為rt_thread_t 類型是一個指針,如下解釋:
rt_thread_t
這個類型他是經過 typedef 重名命的:
所以回到開始的問題,搞清楚了rt_thread_startup
函數的參數是線程控制塊結構體指針, 再結合動態靜態創建線程的線程句柄定義,這么問題就清楚了!明白了這個,那么這里又可以說明一個細節問題!如下
線程創建的一個細節—創建和初始化?
在文中,我介紹API使用的標題是“動態創建線程” 和“靜態創建線程”,個人認為看上去好理解,也沒問題,但是這里注意官方的用語:
動態是 – 創建和刪除線程
靜態是 – 初始化和脫離線程
說白了都是新建線程,但是用詞卻不一樣,為什么動態用創建,而靜態用初始化呢?帶著疑問我們回頭再去看看兩種方式的不同。
在使用rt_thread_init
之前,我們需要定義兩個東西,一個結構體,一個數組:
static struct rt_thread led1_thread; //demo,用戶定義的線程句柄
static char led1_thread_stack[256]; //demo,用戶定義的靜態線程大小
在編譯的時候,這個結構體和數組,就被分配了一定的內存空間,這段空間默認一般是初始化為0,就是空間給你留著了,但是等著你去放數據。不管在程序后面使不使用rt_thread_init,這段空間都已經存在了的! 這樣來說,調用rt_thread_init只是對已經存在的一段內存空間的賦值,對一個存在的東西的設置,不就是叫做 初始化嗎。所以使用靜態的創建嚴格的來說,更應該稱之為初始化線程!
而在使用rt_thread_create
之前,我們只需要定義一個rt_thread_t
類型的指針,初始化是NULL
就沒有了,只有在調用rt_thread_create
成功之后,才會開辟出一塊存放線程控制塊的內存空間,從無到有的一個過程,所以叫做 創建。
不得不佩服,官方還是用詞嚴謹,其實想想也能更好的理解函數功能!
句柄是什么?
講到這里,為了讓有些小伙伴更容易看懂,我們再插一個細節,我們經常聽到返回句柄,函數句柄,任務句柄,那么句柄是什么?
記住一句話:句柄其實就是指針,它是指向指針的指針。
在我們的rt_thread_create
函數中,如果成功返回值是 線程句柄,類型為rt_thread_t
,我們前面又講過rt_thread_t
是一個結構體指針,這個結構體是線程控制塊結構體,所以 在上面示例代碼中返回句柄的意思 ,就是返回了一個指針,這個指針指向線程控制塊。
(如果指針,指向指針的指針不明白,這是C語言基礎知識,可以查看相關資料,我有一篇博文也提到過一二:C語言學習點滴筆記 中 4、指針: 一種特殊的變量 和 多元指針,指向指針的指針)
1.4 刪除線程和脫離線程
針對上面動態靜態方法創建的線程,RT-Thread 有不同的刪除函數:
對于使用rt_thread_create
動態創建的線程,我們使用rt_thread_delete
函數,如下:
/*
參數:thread 要刪除的線程句柄
返回值:
RT_EOK 刪除線程成功
-RT_ERROR 刪除線程失敗
*/
rt_err_t rt_thread_delete(rt_thread_t thread);
調用該函數后,線程對象將會被移出線程隊列并且從內核對象管理器中刪除,線程占用的堆??臻g也會被釋放。實際上,用 rt_thread_delete() 函數刪除線程接口,僅僅是把相應的線程狀態更改為 RT_THREAD_CLOSE 狀態,然后放入到 rt_thread_defunct 隊列中;而真正的刪除動作(釋放線程控制塊和釋放線程棧)需要到下一次執行空閑線程時,由空閑線程完成最后的線程刪除動作。
對于使用rt_thread_init
靜態創建的線程,我們使用rt_thread_detach
函數,如下:
/*
參數:線程句柄,它應該是由 rt_thread_init 進行初始化的線程句柄。
返回值:
RT_EOK 線程脫離成功
-RT_ERROR 線程脫離失敗
*/
rt_err_t rt_thread_detach (rt_thread_t thread);
官方在介紹rt_thread_detach
有一句話,同樣,線程本身不應調用這個接口脫離線程本身。這句話我理解就是不管動態刪除還是靜態刪除,不能在線程函數中自己把自己刪除。
這里也與FreeRTOS任務后不同,FreeRTOS可以直接在任務中調用函數刪除自己。
但是需要特別說明的是,在 RT-Thread 中執行完畢的線程系統會自動將其刪除!用戶無需多余操作,如何理解呢,看下面的例子:
我們一般線程函數都是死循環,通過延時釋放CPU控制權,比如:
static void led1_thread_entry(void *par){
while(1){
//do_something
rt_thread_mdelay(100);
}
}
我們需要刪除的線程往往只是為了做某一件事,某一次特殊的事情,比如:
static void this_is_a_need_delete_task(void *par){
//do_one_time_thing
}
其實這個線程是為了某一件特殊事情而創建的,它是需要刪除的,我們并不需要做任何特殊處理,因為執行是沒有循環的,執行完成以后,RT-Thread 內核會自動把線程刪除??!
1.5 掛起和恢復線程
線程掛起和恢復,在官方有單獨的說明:
既然官方強烈不建議在程序中使用該接口,我們這里就不說明了,因為以應用為主,我們就不去用了。
需要說明的一點是,這里和FreeRTOS也是不同的,FreeRTOS用戶可以隨意用,最典型的就是使一段代碼進入臨界區掛起其他任務。
1.6 其他線程輔助函數
其他的線程輔助函數,除了線程睡眠函數,其他的在一般的應用中都可以不需要。所以我們簡單的過一遍,引用一下官方的介紹。如果后期應用的時候有用到,再來加以詳細說明:
1.6.1 獲得當前線程
在程序的運行過程中,相同的一段代碼可能會被多個線程執行,在執行的時候可以通過下面的函數接口獲得當前執行的線程句柄,把下面的函數加在這段代碼中的,哪個線程調用就返回哪個線程句柄:
/*
返回值
thread 當前運行的線程句柄
RT_NULL 失敗,調度器還未啟動
*/
rt_thread_t rt_thread_self(void);
1.6.2 讓出處理器資源
rt_err_t rt_thread_yield(void);
調用該函數后,當前線程首先把自己從它所在的就緒優先級線程隊列中刪除,然后把自己掛到這個優先級隊列鏈表的尾部,然后激活調度器進行線程上下文切換(如果當前優先級只有這一個線程,則這個線程繼續執行,不進行上下文切換動作)。
1.6.3 線程睡眠(延時函數)
線程睡眠,直白點說,就是延時函數,只不過RTOS中的延時函數,是會釋放CPU使用權的,釋放CPU使用權,就等于線程睡眠了。
/*
參數:tick/ms
線程睡眠的時間:sleep/delay 的傳入參數 tick 以 1 個 OS Tick 為單位 ;
mdelay 的傳入參數 ms 以 1ms 為單位;
返回
RT_EOK 操作成功,一般不需要
*/
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
1.6.4 線程控制函數
/*
參數說明:
1、thread 線程句柄
2、cmd 指示控制命令
cmd 當前支持的命令包括:
?RT_THREAD_CTRL_CHANGE_PRIORITY:動態更改線程的優先級;
?RT_THREAD_CTRL_STARTUP:開始運行一個線程,等同于 rt_thread_startup() 函數調用;
?RT_THREAD_CTRL_CLOSE:關閉一個線程,
等同于 rt_thread_delete() 或 rt_thread_detach() 函數調
用。
3、arg 控制參數
返回值:
RT_EOK 控制執行正確
-RT_ERROR 失敗
*/
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);
1.6.5 設置和刪除空閑鉤子
空閑鉤子函數是空閑線程的鉤子函數(不要和調度器鉤子函數搞混了),如果設置了空閑鉤子函數,就可以在系統執行空閑線程時,自動執行空閑鉤子函數來做一些其他事情,比如系統指示燈。設置 / 刪除空閑鉤子的接口如下:
/*
參數:
hook 設置的鉤子函數,在函數中實現一些操作,但是不要有掛起操作
返回值:
RT_EOK 設置成功
-RT_EFULL 設置失敗
*/
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
官方有一段注意說明如下:
1.6.6 設置調度器鉤子
在整個系統的運行時,系統都處于線程運行、中斷觸發 - 響應中斷、切換到其他線程,甚至是線程間的切換過程中,或者說系統的上下文切換是系統中最普遍的事件。有時用戶可能會想知道在一個時刻發生了什么樣的線程切換,可以通過調用下面的函數接口設置一個相應的鉤子函數。在系統線程切換時,這個鉤子函數將被調用:
/*
參數:
hook 表示用戶定義的鉤子函數指針
*/
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));
/*
鉤子函數 hook() 的聲明
參數說明:
1、from 表示系統所要切換出的線程控制塊指針
2、to 表示系統所要切換到的線程控制塊指針
*/
void hook(struct rt_thread* from, struct rt_thread* to);
————————————————
版權聲明:本文為CSDN博主「矜辰所致」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42328389/article/details/123440027
注:請仔細編寫你的鉤子函數,稍有不慎將很可能導致整個系統運行不正常(在這個鉤子函數中,基本上不允許調用系統 API,更不應該導致當前運行的上下文掛起)。
二、RT-Thread線程創建示例
雖然上面介紹了有一部分的線程操作函數,但是正常需要也就前面幾個,記住線程創建,啟動,一般的應用就足夠了,其他的一些輔助函數在實際中有很多情況是出了問題以后找 bug 的時候才會想起來。
所以我們演示起來也很簡單,還記得在 RT-Thread記錄 第一篇文章中:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發環境 及 配合CubeMX開發快速上手)
在上面博文的最后一節:3.3 創建一個跑馬燈任務 我上傳了一段源碼,這里我就不再重復上一邊了,我們直接通過截圖說明的方式講解下示例:
2.1 靜態創建線程示例
2.1 動態創建線程示例
三、RT-Thread線程管理簡析
經過上面的說明,我們其實能夠使用 RT-Thread 對于的函數創建線程進行一般的設計了,但是為了加深對RT-Thread的理解,我們還得聊聊 RT-Thread線程管理。
這一塊在官網其實有詳細的說明,官方的鏈接如下:RT-Thread官方文檔 RT-Thread內核線程管理
3.1 線程調度的基本特點
我這邊按照自己的理解認知記錄幾個重要的點:
1、RT-Thread 的線程調度器是搶占式的,主要的工作就是從就緒線程列表中查找最高優先級線程,保證最高優先級的線程能夠被運行,最高優先級的任務一旦就緒,總能得到 CPU 的使用權。
調度器開啟以后,就不停的在查詢列表,所有的線程根據優先級,狀態,在列表中排序,調度器總是找到排序“第一位”的線程執行。RTOS的核心就是鏈表,這個有時間會單獨的介紹。
2、當一個運行著的線程使一個比它優先級高的線程滿足運行條件,當前線程的 CPU 使用權就被剝奪了,或者說被讓出了,高優先級的線程立刻得到了 CPU 的使用權。
如果是中斷服務程序使一個高優先級的線程滿足運行條件,中斷完成時,被中斷的線程掛起,優先級高的線程開始運行。
還是上面說到的調度器的作用,使得高優先級的能夠及時執行。
3、當調度器調度線程切換時,先將當前線程上下文保存起來,當再切回到這個線程時,線程調度器將該線程的上下文信息恢復。
RT-Thread 線程具有獨立的棧,當進行線程切換時,會將當前線程的上下文存在棧中,當線程要恢復運行時,再從棧中讀取上下文信息,進行恢復。
要理解上面的話,推薦一篇博文:
FreeRTOS記錄(三、FreeRTOS任務調度原理解析_Systick、PendSV、SVC)
雖然說的是FreeRTOS的,但是都是基于Cortex-M內核的,原理機制類似。
4、每個線程都有時間片這個參數,但時間片僅對優先級相同的就緒態線程有效。
時間片只有在優先級相同的線程間會根據用戶的設置進行對應的分配。
5、線程中不能陷入死循環操作,必須要有讓出 CPU 使用權的動作,如循環中調用延時函數或者主動掛起。
使用rtos延時函數,是實際使用最常見的一種方式,切記,delay是需要在while(1){}大括號里面的:
3.2 線程控制塊
在我們上面介紹線程操作函數的時候,經常提到一個詞語,線程控制塊,線控控制塊結構體,RT-Thread 內核對于線程的管理,都是基于這個結構體進行的。這里我們先有個基本的認識,如果真的深入探討,還是要說到RTOS的鏈表,需要單獨的開篇博文說明。
我們現在要了解的是,內核對于線程的管理是通過這個線程控制塊結構體,里面包括 RT-Thread線程所有的“屬性”,對這些屬性的查看,修改就可以對實現對這個線程的管理控制。
我們來看看控制塊結構體(不是直接復制官網的哦?。?/p>
/** * Thread structure */struct rt_thread{ /* rt object */ char name[RT_NAME_MAX]; /**< the name of thread 線程名稱*/ rt_uint8_t type; /**< type of object 對象類型*/ rt_uint8_t flags; /**< thread's flags 標志位*/#ifdef RT_USING_MODULE void *module_id; /**< id of application module */#endif rt_list_t list; /**< the object list 對象列表*/ rt_list_t tlist; /**< the thread list 線程列表*/ /* stack point and entry 棧指針與入口指針*/ void *sp; /**< stack point 棧指針*/ void *entry; /**< entry 入口函數指針*/ void *parameter; /**< parameter 參數*/ void *stack_addr; /**< stack address 棧地址指針 */ rt_uint32_t stack_size; /**< stack size 棧大小*/ /* error code */ rt_err_t error; /**< error code 線程錯誤代碼*/ rt_uint8_t stat; /**< thread status 線程狀態 */#ifdef RT_USING_SMP /*多核相關支持,我們這里就一個M3內核*/ rt_uint8_t bind_cpu; /**< thread is bind to cpu */ rt_uint8_t oncpu; /**< process on cpu` */ rt_uint16_t scheduler_lock_nest; /**< scheduler lock count */ rt_uint16_t cpus_lock_nest; /**< cpus lock count */ rt_uint16_t critical_lock_nest; /**< critical lock count */#endif /*RT_USING_SMP*/ /* priority 優先級*/ rt_uint8_t current_priority; /**< current priority 當前優先級 */ rt_uint8_t init_priority; /**< initialized priority 初始優先級 */#if RT_THREAD_PRIORITY_MAX > 32 rt_uint8_t number; rt_uint8_t high_mask;#endif rt_uint32_t number_mask;#if defined(RT_USING_EVENT) /*使用事件集*/ /* thread event */ rt_uint32_t event_set; rt_uint8_t event_info;#endif#if defined(RT_USING_SIGNALS) rt_sigset_t sig_pending; /**< the pending signals */ rt_sigset_t sig_mask; /**< the mask bits of signal */#ifndef RT_USING_SMP /*多核相關支持,我們這里就一個M3內核*/ void *sig_ret; /**< the return stack pointer from signal */#endif rt_sighandler_t *sig_vectors; /**< vectors of signal handler */ void *si_list; /**< the signal infor list */#endif rt_ubase_t init_tick; /**< thread's initialized tick 線程初始化計數值*/ rt_ubase_t remaining_tick; /**< remaining tick 線程剩余計數值*/ struct rt_timer thread_timer; /**< built-in thread timer 內置線程定時器*/ /**< cleanup function when thread exit 線程退出清除函數 cleanup 函數指針指向的函數,會在線程退出的時候,被idle 線程回調一次, 執行用戶的清理現場工作。 */ void (*cleanup)(struct rt_thread *tid); /* light weight process if present */#ifdef RT_USING_LWP void *lwp;#endif rt_ubase_t user_data; /**< private user data beyond this thread 用戶數據*/};typedef struct rt_thread *rt_thread_t;
3.3 線程狀態
線程的狀態我們借用官方的幾張圖,加以說明:
來看看 RT-Thread 的任務狀態:
在上圖中除了今天我們介紹的線程操作函數,還有一些函數還沒有介紹過,比如rt_sem_take(),rt_mutex_take(),rt_mb_recv()
,這是我們后期會介紹到的關于線程間通信的一些信號量,互斥量相關的函數。
作為對比,再來看看FreeRTOS 的任務狀態:
3.4 系統線程
在 RT-Thread 內核中的系統線程有空閑線程和主線程。
空閑線程 IDLE線程:
空閑線程是系統創建的最低優先級的線程,線程狀態永遠為就緒態。當系統中無其他就緒線程存在時,調度器將調度到空閑線程,它通常是一個死循環,且永遠不能被掛起。這點其實所有RTOS都是一樣的。
但是,空閑線程在 RT-Thread 也有著它的特殊用途:
若某線程運行完畢,系統將自動刪除線程:自動執行 rt_thread_exit() 函數,先將該線程從系統就緒隊列中刪除,再將該線程的狀態更改為關閉狀態,不再參與系統調度,然后掛入 rt_thread_defunct 僵尸隊列(資源未回收、處于關閉狀態的線程隊列)中,最后空閑線程會回收被刪除線程的資源。
空閑線程也提供了接口來運行用戶設置的鉤子函數,在空閑線程運行時會調用該鉤子函數,適合鉤入功耗管理、看門狗喂狗等工作。
主線程:
在我們上一篇博文中介紹 RT-Thread 啟動流程的時候,說到了系統啟動會創建main線程:
FreeRTOS只有空閑線程,并不會創建主線程,所以在FreeRTOS中,一般在main() 之前開啟調度,永遠不會執行到main()。
結語
本文的主要目的是認識 RT-Thread 線程操作函數,同時簡單的說明了一下 RT-Thread 線程管理的一些要點,說明了一下 RT-Thread 與 FreeRTOS 在線程操作某些地方的不同,此外還加了一些博主認為的細節的問題, 希望懂的小伙伴可以多多指教,不懂的小伙伴看完還是不明白的可以留言。講得不好的地方還希望能夠指出,我一定加以修正。
總的來說,本文內容還是比較簡單的,小伙伴們可以開動起來,線程創建跑起來玩玩。優先級,任務調度,線程死循環什么的情況都可以試試。更能加加深線程調度的理解。
下一篇 RT-Thread 記錄,我會講一講 RT-Thread 時鐘管理的內容,系統時鐘,軟件定時器相關。
謝謝!
-
函數
+關注
關注
3文章
4331瀏覽量
62622 -
線程
+關注
關注
0文章
504瀏覽量
19684 -
RT-Thread
+關注
關注
31文章
1289瀏覽量
40135
發布評論請先 登錄
相關推薦
評論