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