首先糾正一下上一篇中的一個(gè)錯(cuò)誤。對(duì)象類型(是否為系統(tǒng)對(duì)象)記錄在rt_thread結(jié)構(gòu)的type成員里,而不是flag成員里。
何謂線程
所謂“線程”,其實(shí)就是一段子程序。只是它和普通子程序的區(qū)別在于,這段子程序可能隨時(shí)被操作系統(tǒng)所中斷,并在將來的某個(gè)時(shí)間點(diǎn)恢復(fù)運(yùn)行。為了達(dá)到這個(gè)目的,我們需要一些基礎(chǔ)設(shè)施。例如,我們需要在中斷一個(gè)線程的運(yùn)行時(shí)保存當(dāng)前的PC寄存器,這樣我們才能在恢復(fù)其運(yùn)行時(shí)知道從何處重新開始;我們還需要保存當(dāng)前CPU的所有其他寄存器,因?yàn)檫@些寄存器可能被其他線程所破壞。
線程的創(chuàng)建
有兩個(gè)函數(shù)可用于創(chuàng)建線程:rt_thread_init和rt_thread_create。這兩者的區(qū)別在于,前者需要調(diào)用者自己分配線程對(duì)象和線程棧,而后者由操作系統(tǒng)分配線程對(duì)象和??臻g。換句話說,rt_thread_create比rt_thread_init多了資源分配的操作。這里,我們先看rt_thread_init。
rt_thread_init首先對(duì)調(diào)用者提供的線程對(duì)象進(jìn)行初始化:
rt_err_t rt_thread_init(...)
{
rt_object_init((rt_object_t)thread,
RT_Object_Class_Thread, name);
...
}
rt_object_init的代碼做以下幾個(gè)工作:
1. 將對(duì)象類型設(shè)置為RT_Object_Class_Static
這個(gè)標(biāo)記表明該對(duì)象是由用戶分配而不是操作系統(tǒng)分配的。
一般來說,硬件資源的分配遵循“誰申請(qǐng),誰釋放”的原則。因此,當(dāng)線程退出運(yùn)行時(shí),操作系統(tǒng)需要判斷線程對(duì)象和線程棧占用的存儲(chǔ)空間是用戶分配的還是系統(tǒng)分配的。如果這些存儲(chǔ)空間是用戶分配的,那么操作系統(tǒng)不會(huì)對(duì)這些內(nèi)存做什么事情;但如果這些存儲(chǔ)空間是系統(tǒng)分配的(當(dāng)你用rt_thread_create創(chuàng)建線程時(shí)),操作系統(tǒng)就要負(fù)責(zé)釋放線程對(duì)象和線程棧所占用的存儲(chǔ)空間。判斷的依據(jù)就是線程對(duì)象類型。
2. 將name參數(shù)(對(duì)象名稱)復(fù)制到線程對(duì)象中
3. 將該線程對(duì)象加入線程對(duì)象鏈表,方便后面的管理。
線程對(duì)象初始化完畢后,就調(diào)用_rt_thread_init進(jìn)行實(shí)際的初始化工作:
1. 首先初始化該線程的tlist成員。
我們前面說過,tlist成員是用來將該線程掛入某個(gè)狀態(tài)隊(duì)列的。但是該線程目前還沒有初始化完成,所以目前不將其掛入任何一個(gè)隊(duì)列,僅將其prev和next字段指向自身。
2. 將entry、parameter、stack_start、stack_size四個(gè)參數(shù)保存到線程對(duì)象中。
entry和parameter是線程入口地址和線程參數(shù);stack_start是線程棧起始地址。在后面將線程投入運(yùn)行時(shí),這些參數(shù)都是必須的。
stack_size并不是一個(gè)線程運(yùn)行所必須的參數(shù);但該變量可以幫助我們檢查線程在運(yùn)行過程中是否發(fā)生了堆棧溢出。RTT將線程??臻g全部初始化為'#'。這樣,在線程運(yùn)行過程中,通過檢查堆棧內(nèi)容,就可以判斷該線程實(shí)際使用的最大堆棧空間,以及線程堆??臻g是否溢出。
3. 初始化線程棧
rt_hw_stack_init(...)
這個(gè)函數(shù)的作用是將線程棧模擬成這個(gè)線程剛剛被切換出去的樣子。當(dāng)調(diào)度器將該線程投入運(yùn)行時(shí),它不會(huì)知道這個(gè)線程到底是真的曾經(jīng)被切換出去,還是被模擬成這個(gè)樣子。這是一個(gè)體系結(jié)構(gòu)相關(guān)的函數(shù),比較關(guān)鍵的是這一句(以arm7為例):
*(--stk) = (unsigned long)texit; /* lr */
texit是RTT提供的系統(tǒng)函數(shù)。我們結(jié)合一個(gè)最簡單的線程作為例子來說明這一句的作用:
void SampleThread(void *param)
{
}
這個(gè)線程什么也不做,投入運(yùn)行后直接退出。函數(shù)返回時(shí)將執(zhí)行bx lr指令,即將lr寄存器的內(nèi)容從堆棧中彈入pc。而lr的值就是上面那條語句中的texit入口地址。因此,該線程退出后將執(zhí)行texit()函數(shù)。texit()會(huì)做一些必要的清理工作,然后進(jìn)行一次調(diào)度(因?yàn)楫?dāng)前線程已經(jīng)退出運(yùn)行了)。我們后面還要回到texit()函數(shù)中去。
從這里我們看到,RTT不像uc/OS那樣要求線程一定要有一個(gè)while(1)循環(huán)。對(duì)于RTT來說,當(dāng)一個(gè)線程不再需要時(shí),可以像函數(shù)返回一樣使用return語句令其退出運(yùn)行。(插一句話,RTT關(guān)于texit的設(shè)計(jì)是激發(fā)我當(dāng)初學(xué)習(xí)RTT的主要原因之一。)
4. 將優(yōu)先級(jí)、初始tick都保存到線程對(duì)象。這里我們看到,RTT將init_tick和remaining_tick都設(shè)置為相同的值。
5. 初始化thread_timer成員。thread_timer的作用我們前面已經(jīng)說過了,rt_timer_init函數(shù)我們留到定時(shí)器相關(guān)內(nèi)容中分析。
聯(lián)系客服