《《《《《正文》》》》》
《前言》
????本章是我們真正從從 0 到 1 寫 RT-Thread 的第一章,屬于基礎(chǔ)中的基礎(chǔ),必須要學(xué)會(huì)創(chuàng)建線程,并重點(diǎn)掌握線程是如何切換的。因?yàn)榫€程的切換是由匯編代碼來完成的,所以代碼看起來比較難懂,但是我會(huì)盡力把代碼講得透徹。如果本章內(nèi)容學(xué)不會(huì),后面的內(nèi)容根本無從下手。
????在這章中,我們會(huì)創(chuàng)建兩個(gè)線程,并讓這兩個(gè)線程不斷地切換,線程的主體都是讓一個(gè)變量按照一定的頻率翻轉(zhuǎn),通過 KEIL 的軟件仿真功能,在邏輯分析儀中觀察變量的波形變化,最終的波形圖具體見圖 6-1。

其實(shí),圖 6-1 的波形圖的效果,并不是真正的多線程系統(tǒng)中線程切換的效果圖,這個(gè)效果其實(shí)可以完全由裸機(jī)代碼來實(shí)現(xiàn),具體見代碼清單 6-1。


????在多線程系統(tǒng)中,兩個(gè)線程不斷切換的效果圖應(yīng)該像圖 6-2 所示那樣,即兩個(gè)變量的波形是完全一樣的,就好像 CPU 在同時(shí)干兩件事一樣,這才是多線程的意義。雖然兩者的波形圖一樣,但是,代碼的實(shí)現(xiàn)方式是完全不一樣的,由原來的順序執(zhí)行變成了線程的主動(dòng)切換,這是根本區(qū)別。這章只是開始,我們先掌握好線程是如何切換,在后面章節(jié)中,我們會(huì)陸續(xù)的完善功能代碼,加入系統(tǒng)調(diào)度,實(shí)現(xiàn)真正的多線程。千里之行,始于本章節(jié),不要急。

《什么是線程》
在裸機(jī)系統(tǒng)中,系統(tǒng)的主體就是 main 函數(shù)里面順序執(zhí)行的無限循環(huán),這個(gè)無限循環(huán)里面 CPU 按照順序完成各種事情。在多線程系統(tǒng)中,我們根據(jù)功能的不同,把整個(gè)系統(tǒng)分割成一個(gè)個(gè)獨(dú)立的且無法返回的函數(shù),這個(gè)函數(shù)我們稱為線程。線程的大概形式具體見代碼清單 6-2。

《創(chuàng)建線程》
1、定義線程棧
我們先回想下,在一個(gè)裸機(jī)系統(tǒng)中,如果有全局變量,有子函數(shù)調(diào)用,有中斷發(fā)生。那么系統(tǒng)在運(yùn)行的時(shí)候,全局變量放在哪里,子函數(shù)調(diào)用時(shí),局部變量放在哪里,中斷發(fā)生時(shí),函數(shù)返回地址發(fā)哪里。如果只是單純的裸機(jī)編程,它們放哪里我們不用管,但是如果要寫一個(gè) RTOS,這些種種環(huán)境參數(shù),我們必須弄清楚他們是如何存儲(chǔ)的。在裸機(jī)系統(tǒng)中,他們統(tǒng)統(tǒng)放在一個(gè)叫棧的地方,棧是單片機(jī) RAM 里面一段連續(xù)的內(nèi)存空間,棧的大小一般在啟動(dòng)文件或者鏈接腳本里面指定,最后由 C 庫函數(shù)_main 進(jìn)行初始化。
但是,在多線程系統(tǒng)中,每個(gè)線程都是獨(dú)立的,互不干擾的,所以要為每個(gè)線程都分配獨(dú)立的??臻g,這個(gè)??臻g通常是一個(gè)預(yù)先定義好的全局?jǐn)?shù)組,也可以是動(dòng)態(tài)分配的一段內(nèi)存空間,但它們都存在于 RAM 中。
本章我們要實(shí)現(xiàn)兩個(gè)變量按照一定的頻率輪流的翻轉(zhuǎn),每個(gè)變量對(duì)應(yīng)一個(gè)線程,那么就需要定義兩個(gè)線程棧,具體見代碼清單 6-3。在多線程系統(tǒng)中,有多少個(gè)線程就需要定義多少個(gè)線程棧。

代碼清單 6-3 (1):線程棧其實(shí)就是一個(gè)預(yù)先定義好的全局?jǐn)?shù)據(jù),數(shù)據(jù)類型為rt_uint8_t,大小我們?cè)O(shè)置為 512。在 RT-Thread 中,凡是涉及到數(shù)據(jù)類型的地方,RT-Thread 都會(huì)將標(biāo)準(zhǔn)的 C 數(shù)據(jù)類型用 typedef 重新取一個(gè)類型名,以“rt”前綴開頭。這些經(jīng)過重定義的數(shù)據(jù)類型放在 rtdef.h(rtdef.h 第一次使用需要在 include 文件夾下面新建然后添加到工程 rtt/source 這個(gè)組文件)這個(gè)頭文件,具體見代碼清單 6-4。代碼清單 6-4 中除了rt_uint8_t 外,其它數(shù)據(jù)類型重定義是本章后面內(nèi)容需要使用到,這里統(tǒng)一貼出來,后面將不再贅述。


代碼清單 6-3 (2):設(shè)置變量需要多少個(gè)字節(jié)對(duì)齊,對(duì)在它下面的變量起作用。ALIGN是一個(gè)帶參宏,在 rtdef.h 中定義,具體見代碼清單 6-4。RT_ALIGN_SIZE 是一個(gè)在rtconfig.h(rtconfig.h 第一次使用需要在 User 文件夾下面新建然后添加到工程 user 這個(gè)組文件)中定義的宏,默認(rèn)為 4,表示 4 個(gè)字節(jié)對(duì)齊,具體見代碼清單 6-5。

2、定義線程函數(shù)
線程是一個(gè)獨(dú)立的函數(shù),函數(shù)主體無限循環(huán)且不能返回。本章我們?cè)?main.c 中定義的兩個(gè)線程具體見代碼清單 6-6。


代碼清單 6-6 (1)、(2):正如我們所說的那樣,線程是一個(gè)獨(dú)立的、無限循環(huán)且不能返回的函數(shù)。
3、定義線程控制塊
在裸機(jī)系統(tǒng)中,程序的主體是 CPU 按照順序執(zhí)行的。而在多線程系統(tǒng)中,線程的執(zhí)行是由系統(tǒng)調(diào)度的。系統(tǒng)為了順利的調(diào)度線程,為每個(gè)線程都額外定義了一個(gè)線程控制塊,這個(gè)線程控制塊就相當(dāng)于線程的身份證,里面存有線程的所有信息,比如線程的棧指針,線程名稱,線程的形參等。有了這個(gè)線程控制塊之后,以后系統(tǒng)對(duì)線程的全部操作都可以通過這個(gè)線程控制塊來實(shí)現(xiàn)。定義一個(gè)線程控制塊需要一個(gè)新的數(shù)據(jù)類型,該數(shù)據(jù)類型在rtdef.h 這個(gè)頭文件中聲明,具體的聲明見代碼清單 6-7,使用它可以為每個(gè)線程都定義一個(gè)線程控制塊實(shí)體。

代碼清單 6-7 (1):目前線程控制塊結(jié)構(gòu)體里面的成員還比較少,往后我們會(huì)慢慢在里面添加成員。代碼清單 6-7 (2):在 RT-Thread 中,都會(huì)給新聲明的數(shù)據(jù)結(jié)構(gòu)重新定義一個(gè)指針。往后如果要定義線程控制塊變量就使用 struct rt_thread xxx 的形式,定義線程控制塊指針就使用 rt_thread_t xxx 的形式。在本章實(shí)驗(yàn)中,我們?cè)?main.c 文件中為兩個(gè)線程定義的線程控制塊,具體見代碼清單6-8。


4、實(shí)現(xiàn)線程創(chuàng)建函數(shù)
線程的棧,線程的函數(shù)實(shí)體,線程的控制塊最終需要聯(lián)系起來才能由系統(tǒng)進(jìn)行統(tǒng)一調(diào)度。那么這個(gè)聯(lián)系的工作就由線程初始化函數(shù) rt_thread_init()來實(shí)現(xiàn),該函數(shù)在 thread.c(thread.c 第一次使用需要自行在文件夾 rtthread/3.0.3/src 中新建并添加到工程的 rtt/source組)中定義,在 rtthread.h 中聲明,所有跟線程相關(guān)的函數(shù)都在這個(gè)文件定義。rt_thread_init()函數(shù)的實(shí)現(xiàn)見代碼清單 6-9。

代碼清單 6-9:rt_thread_init 函數(shù)遵循 RT-Thread 中的函數(shù)命名規(guī)則,以小寫的 rt 開頭,
表示這是一個(gè)外部函數(shù),可以由用戶調(diào)用,以_rt 開頭的函數(shù)表示內(nèi)部函數(shù),只能由 RT
Thread 內(nèi)部使用。緊接著是文件名,表示該函數(shù)放在哪個(gè)文件,最后是函數(shù)功能名稱。
代碼清單 6-9 (1):thread 是線程控制塊指針。
代碼清單 6-9 (2):entry 是線程函數(shù)名, 表示線程的入口。
代碼清單 6-9 (3):parameter 是線程形參,用于傳遞線程參數(shù)。
代碼清單 6-9 (4):stack_start 用于指向線程棧的起始地址。
代碼清單 6-9 (5):stack_size 表示線程棧的大小,單位為字節(jié)。
待續(xù)。。。。。。
電子發(fā)燒友App






























評(píng)論