在上一篇文章中,我們了解了RT-Thread的版本以及開發(fā)環(huán)境,使用RT-Thread Studio成功創(chuàng)建了一個工程。
但是要了解一個操作系統(tǒng),內(nèi)核的了解是必不可少的,
我們今天就在前面我們RT-Thread Studio工程基礎之上講一講RT-Thread內(nèi)核啟動流程
RT-Thread啟動流程
1、基礎介紹
2、源碼分析
2.1 匯編部分 — startup_xxxx.s說明
2.2 C部分 — rtthread_startup 說明
2.2.1 板級硬件初始化 — rt_hw_board_init
板級硬件初始化更新說明
2.2.2 RT-Thread 堆和??臻g說明(與FreeRTOS不同)
2.2.3 main線程創(chuàng)建 — rt_application_init
2.2.4 調(diào)度器說明
1、基礎介紹
在裸機程序中,一般在 .s 文件中就跳轉到 _main
從而跳轉到 main()
函數(shù)啟動,而 RT-Thread 啟動會先跳轉到其啟動函數(shù) rtthread_startup()
進行一系列的必要的初始化,最后才跳轉至 main()
函數(shù)。
簡單來說就是: 程序啟動,通過 startup_xxxx.s 文件(匯編語言)跳轉到 RT-Thread啟動函數(shù)rtthread_startup() (C語言),再通過 rtthread_startup() 跳轉到 main()(C語言)函數(shù)。
官方的圖片很詳細的表明了這個流程:
在 RT-Thread 中,會把 main()函數(shù) 當成是一個線程。這個在 rtthread_startup() 就會將 main() 創(chuàng)建成一個線程,除此之外,rtthread_startup() 還會創(chuàng)建timer 線程 和 空閑線程 這兩個線程。
結合上圖,下面我們通過上篇文章創(chuàng)建的示例代碼來說明一下這個流程。
2、源碼分析
2.1 匯編部分 — startup_xxxx.s說明
打開RT-Thread Studio工程,在哪里找到 startup_xxxx.s
文件呢,看下面一張圖:
我們找到了啟動文件,可以打開查看,啟動文件的說明我在我在另一篇博文有詳細的介紹:
STM32的啟動過程 — startup_xxxx.s文件解析(更新GCC環(huán)境下的啟動文件分析)
已經(jīng)講解的比較詳細了,這里我只把主要的簡單說明一下。在上面推薦的博文中講到過,GCC環(huán)境下面的啟動,需要兩個文件,一個是 startup_xxxx.s
文件,還一個是 .ld
鏈接文件,我們先看一下鏈接文件:
在以前講過,GCC下的鏈接文件主要制定了入口函數(shù),堆棧大小和數(shù)據(jù)段的整體布局。在上圖中我們看到值定義了系統(tǒng)棧的大小,并沒有定義堆大小。
這里為什么只定義系統(tǒng)棧?
雖然我們在其他博文說過,如果不用 malloc
函數(shù),不需要用到堆,這里沒有定義是因為在后面初始化的時候會根據(jù)是否使用堆,來定義堆的大小。
在本文下面板級硬件初始化部分有介紹說明。
然后就簡單來看一下 startup_xxxx.s文件,首先我們找到上電執(zhí)行的第一個指令 Reset_Handler(芯片剛上電,就是上電復位,直接就會觸發(fā)Reset_Handler):
上圖中所進行的操作不理解的可以查看博文:
STM32的內(nèi)存管理相關(內(nèi)存架構,內(nèi)存管理,map文件分析)
完成數(shù)據(jù)搬運以后,就是系統(tǒng)基本的初始化,如下圖:
完成基本初始化,MCU得以運行起來,就跳轉到我們上面基礎介紹里面說到的入口函數(shù),如下圖:
通過上面的步驟,最終就從 .s 中的匯編跳轉到了 C語言部分,通過入口函數(shù)跳轉到 rtthread_startup
函數(shù),我們通過下面的介紹說明一下,進入rtthread_startup
函數(shù) 后,RT-Thread 確實做了哪些工作。
2.2 C部分 — rtthread_startup 說明
在本文第一節(jié)基礎介紹中通過官方的一張圖表示了進入rtthread_startup
后,所會進行的操作,我們上面也說明了工程是怎么進入 rtthread_startup
函數(shù)的,那么進入 rtthread_startup
函數(shù) 后執(zhí)行了哪些操作,如下圖:
補充說明: 上圖中的SMP相關,是與多核處理器有關的設置。
上面的過程很好理解,主要有做了以下工作:
1、基本的硬件初始化;
2、一定會創(chuàng)建main現(xiàn) 線程;
3、根據(jù)是否使用軟件定時器創(chuàng)建 time r線程;
4、一定會創(chuàng)建 idle 線程;
5、初始化開啟調(diào)度器;
其中有一些初始化我們可以更加深入的看看具體的操作:
2.2.1 板級硬件初始化 — rt_hw_board_init
在上圖找那個,板級硬件初始化最后調(diào)用了rt_components_board_init()
函數(shù),這個函數(shù)如下:
rt_components_board_init()函數(shù)會把所有 INIT_BOARD_EXPORT 的設備都初始化,這里暫時不介紹是如何實現(xiàn)的,但是有必要說明一下。
比如我們什么外設都沒使能,但是使用到了串口1作為打印LOG的設備,所以串口1 必定會被使能,那么這個初始化就是在這里完成的,我們可以在工程 drivers 文件夾里的drv_usart.c 文件中查看到串口相關的初始化代碼,我們可以看到如下圖所示部分(此部分串口1 的說明有待確認,因為后期加了其他串口以后回頭來看這個地方,并沒有發(fā)現(xiàn)下圖代碼……):
板級硬件初始化更新說明
對于上圖提到的串口會使用 INIT_BOARD_EXPORT(rt_hw_usart_init)
,后續(xù)我反而并沒有找到圖示代碼,也不知道是因為版本問題還是什么原因,這里需要補充說明一下:
硬件設備的初始化是在hw_board_init
函數(shù)中的:
2.2.2 RT-Thread 堆和??臻g說明(與FreeRTOS不同)
在上圖中,有一點比較特殊,就是對 堆 空間的初始化,我們以前遇到的都是在啟動文件中定義好堆??臻g,而我們上面分析 RT-Thread 啟動文件的時候,只定義了棧空間,堆空間沒有定義,其實是放在了這個地方:
剛開始看到這里還有個疑問,HEAP 把余下 所有的 RAM 都使用了,按照以前的理解,系統(tǒng)棧應該是在最后面的位置的,這里是怎么回事?
關于 系統(tǒng)棧位置的問題,可以參考博文:RTOS的 任務棧 和 系統(tǒng)棧
上面我們通過源碼看到的結論和 這篇博文說到的不一樣(當時是用裸機和 FreeRTOS作為例子說明的),然后在 RT-Thread 下,系統(tǒng)棧的位置在什么地方,于是乎回頭看了看定義數(shù)據(jù)段整體布局的鏈接文件:
通過鏈接文件我們可以推斷 .stack 的位置,那么為了確認一下,我們可以查看程序編譯過后的 .map文件:
在 RAM 數(shù)據(jù)段我們可以查看數(shù)據(jù)存放的位置,找到關于 系統(tǒng)棧的位置部分:
確認了在 RT-Thread 中,系統(tǒng)棧的位置是確實存放于 .data 段和 .bss 之間的,所以堆空間即便使用了余下全部的 ram 空間也是沒有問題的。
2.2.3 main線程創(chuàng)建 — rt_application_init
在 RT-Thread 中,創(chuàng)建了一個名字為 "main"
的線程來調(diào)用 main()
函數(shù),就是在rtthread_startup
函數(shù)中的rt_application_init()
,如下圖:
2.2.4 調(diào)度器說明
調(diào)度器是操作系統(tǒng)的核心知識,調(diào)度器是基于鏈表進行操作的,具體的原理將來會單獨寫一篇文章說明,這里我們就簡單的過一遍,知道函數(shù)的用意。
在rtthread_startup
函數(shù)中,使用rt_system_scheduler_init();
初始化調(diào)度器,rt_system_scheduler_start();
開啟調(diào)度器,開啟調(diào)度器之后,線程之間就會根據(jù)一定的規(guī)則進行切換(時間片,優(yōu)先級):
開啟調(diào)度器后,會在就緒列表中找到最高優(yōu)先級的線程,然后通過設置 線程指針(PSP),來跳轉到對應的位置執(zhí)行:
線程指針什么意思,可以參考博文:FreeRTOS記錄(三、FreeRTOS任務調(diào)度原理解析_Systick、PendSV、SVC)
至此,整個系統(tǒng)就正常跑起來了,然后用戶運行自己想要做的事情,可以在 main 中設計自己的應用代碼,或者創(chuàng)建線程。
-
內(nèi)核
+關注
關注
3文章
1336瀏覽量
40082 -
STM
+關注
關注
1文章
555瀏覽量
42270 -
RT-Thread
+關注
關注
31文章
1239瀏覽量
39426
發(fā)布評論請先 登錄
相關推薦
評論