国产日韩欧美一区二区三区三州_亚洲少妇熟女av_久久久久亚洲av国产精品_波多野结衣网站一区二区_亚洲欧美色片在线91_国产亚洲精品精品国产优播av_日本一区二区三区波多野结衣 _久久国产av不卡

?

實(shí)時操作系統(tǒng)RT?Thread啟動流程剖析

2022-06-14 06:33陳瑞雪王宜懷王庭琛
現(xiàn)代電子技術(shù) 2022年12期
關(guān)鍵詞:鏈表空閑主線

陳瑞雪,王宜懷,王庭琛

(蘇州大學(xué) 計算機(jī)與科學(xué)技術(shù)學(xué)院,江蘇 蘇州 215006)

RT-Thread(Real-Time Thread)是一款嵌入式實(shí)時操作系統(tǒng)(Real-Time Operating System,RTOS),具有組件完整豐富、簡易開發(fā)、超低功耗等特點(diǎn),2006年由中國開源社區(qū)主導(dǎo)開發(fā),因其具有淺顯易懂且方便移植的特點(diǎn)而被廣泛應(yīng)用在農(nóng)業(yè)、車載、醫(yī)療等領(lǐng)域。但基于RTOS進(jìn)行RT-Thread嵌入式開發(fā)需要對其啟動過程有深刻的認(rèn)識,涉及到時間嘀嗒處理、堆空間初始化以及線程創(chuàng)建、調(diào)度機(jī)制等方面的設(shè)置。截至目前,有關(guān)操作系統(tǒng)的啟動研究集中在mbedOS操作系統(tǒng)、Windows操作系統(tǒng)以及MINIX3操作系統(tǒng)的啟動過程等方面,缺乏對RT-Thread的啟動剖析研究。因此,本文將利用Cortex-M4F內(nèi)核的STM32微控制器,基于STM32CubeIDE 1.0.2開發(fā)環(huán)境和SD-RT-Thread工程框架分析RT-Thread的啟動流程,對從芯片上電啟動運(yùn)行到entry函數(shù),最終轉(zhuǎn)入RT-Thread啟動的全過程進(jìn)行剖析,并與一些關(guān)鍵的代碼及注釋、流程圖、PendSV中斷服務(wù)程序相結(jié)合分析其實(shí)現(xiàn)原理。充分理解RTThread的啟動流程,不僅可以幫助讀者從微觀層面來理解RTOS的啟動過程,也有助于開發(fā)人員設(shè)計出相應(yīng)速度快、穩(wěn)定性強(qiáng)的嵌入式系統(tǒng)。

1 RT?Thread工程框架啟動流程

SD-RT-Thread工程框架的啟動過程由芯片上電啟動和RT-Thread操作系統(tǒng)啟動兩部分組成,如圖1所示。進(jìn)入應(yīng)用程序的入口點(diǎn)函數(shù)entry時,調(diào)用啟動函數(shù)rtthread_startup(),系統(tǒng)控制權(quán)將轉(zhuǎn)移給RT-Thread,由它完成一系列前期準(zhǔn)備工作后實(shí)現(xiàn)線程調(diào)度。

圖1 RT?Thread工程框架啟動流程

芯片上電啟動始于調(diào)用啟動文件startup_stm32l431rctx.s,這個文件包含了中斷向量表及系統(tǒng)啟動代碼。在中斷向量表中,每個中斷向量號對應(yīng)一個中斷源,中斷向量號與中斷源是一一對應(yīng)的關(guān)系,按照中斷向量號的固定地址存放中斷服務(wù)程序入口地址(每個占用4 B)。本文采用的MCU其中段向量表的位置在存儲區(qū)0x0800 0000~0x0800 0190的這段地址范圍。

最后,跳轉(zhuǎn)entry函數(shù),調(diào)用rtthread_startup()函數(shù)啟動操作系統(tǒng),并啟動初始線程來調(diào)用main函數(shù)。在main函數(shù)的內(nèi)部調(diào)用自啟動任務(wù)函數(shù)app_init來初始化外設(shè)模塊、初始化有關(guān)變量、使能中斷模塊等,創(chuàng)建并啟動其他用戶線程。

2 RT?Thread啟動流程解析

芯片上電后開始啟動,執(zhí)行到entry函數(shù)時,實(shí)際調(diào)用總啟動函數(shù)rtthread_startup開始RT-Thread操作系統(tǒng)的啟動,其具體調(diào)用關(guān)系和執(zhí)行流程如圖2所示,主要完成內(nèi)核資源初始化、主線程和空閑線程的初始化以及調(diào)度器的啟動等工作。

圖2 RT?Thread啟動過程總流程

2.1 內(nèi)核資源初始化

2.1.1 板級硬件資源初始化

板級硬件初始化主要完成系統(tǒng)時鐘SysTick初始化和堆初始化。

1)SysTick初始化

SysTick是RT-Thread整個系統(tǒng)的時鐘基準(zhǔn),系統(tǒng)通過每次“嘀嗒”進(jìn)入中斷服務(wù)程序?qū)θ蝿?wù)狀態(tài)進(jìn)行管理。在進(jìn)行板級硬件初始化時,首先調(diào)用_SysTick_Config()函數(shù)初始化系統(tǒng)時鐘基準(zhǔn)SysTick,該函數(shù)將SystemCoreClock/RT_TICK_PER_SECOND作為參數(shù)傳入。SystemCoreClock系統(tǒng)時鐘頻率為48 MHz,RT_TICK_PER_SECOND是宏定義設(shè)置的嘀嗒頻率,默認(rèn)為1 000 Hz,因此系統(tǒng)SysTick調(diào)度的頻率被設(shè)置為1 1 000 Hz=1 ms一次。

//系統(tǒng)時間嘀嗒初始化

_SysTick_Config SystemCoreClock/RT_TICK_PER_SECOND;

2)堆初始化

堆是用于存放臨時變量的區(qū)域,一般由程序員動態(tài)分配,最終也由程序員釋放。在內(nèi)存中,堆區(qū)的位置介于靜態(tài)區(qū)和棧區(qū)之間,使用時按照RAM區(qū)地址由低到高的順序申請。與其他操作系統(tǒng)不同之處在于,RTThread中系統(tǒng)使用的堆空間是自定義的一個靜態(tài)數(shù)組rt_heap,在內(nèi)存中屬于bss區(qū),大小由RT_HEAP_SIZE決定,目前將其宏定義為1 024,也可以根據(jù)工程需求修改(不能超過內(nèi)部靜態(tài)RAM區(qū)大小)。RT-Thread啟動時會 通 過rt_heap_begin_get()、rt_heap_end_get()獲 取rt_heap的起始地址及結(jié)束地址,并將這兩個地址作為參數(shù)傳入rt_system_heap_init()函數(shù)對堆內(nèi)存進(jìn)行初始化。

2.1.2 定時器初始化

其他操作系統(tǒng)如mbed OS中,當(dāng)線程需要延時時,系統(tǒng)會獲取當(dāng)前正在運(yùn)行的線程,阻塞該線程并將阻塞的線程根據(jù)延時時長插入到等待隊列或延時隊列中,最后獲取當(dāng)前優(yōu)先級最高的就緒態(tài)線程并切換就緒態(tài)線程為運(yùn)行態(tài)。RT-Thread內(nèi)部沒有設(shè)置等待隊列或延時隊列,而是通過定義一個雙向鏈表類型的全局系統(tǒng)定時器來類比延時隊列。在每個線程中內(nèi)置一個定時器,在線程需要延時時,系統(tǒng)先掛起該線程并啟動內(nèi)置的定時器,將定時器按照線程延時時間長短插入到升序排列的rt_timer_list鏈表中。

在初始化系統(tǒng)定時器列表時,rt_timer_list里面的成員是一個雙向鏈表的根節(jié)點(diǎn),每個節(jié)點(diǎn)均有指向下一個節(jié)點(diǎn)的指針next和指向前一個節(jié)點(diǎn)的指針prev,初始化時將這兩個指針都指向該節(jié)點(diǎn)。初始化好的系統(tǒng)定時器列表如圖3所示。

圖3 初始化好的系統(tǒng)定時器列表

2.1.3 調(diào)度器初始化

RT-Thread中的調(diào)度器用于切換線程,即找到就緒列表中最高優(yōu)先級的線程并執(zhí)行。調(diào)度器初始化主要完成初始化整個線程就緒列表(rt_thread_priority_table)為空、初始化當(dāng)前線程的優(yōu)先級(rt_current_priority)為空閑線程的優(yōu)先級(31)、初始化當(dāng)前線程控制塊指針(rt_current_thread)為空和初始化線程就緒優(yōu)先級組(rt_thread_ready_priority_group)為0等內(nèi)容。

rt_thread_priority_table是一個一維數(shù)組,大小默認(rèn)為32。

rt_list_t rt_thread_priority_table[32];

在該數(shù)組中,每個下標(biāo)表示與之相同的優(yōu)先級,比如位0表示優(yōu)先級0。當(dāng)線程就緒時,根據(jù)優(yōu)先級插入到相同下標(biāo)的鏈表中。有三個線程就緒時的鏈表掛載情況如圖4所示。

圖4 就緒列表的掛載情況

初始時,并未有任何就緒線程,故將就緒列表初始化為空??盏木€程就緒列表如圖5所示,每個索引初始化成對應(yīng)優(yōu)先級的雙鏈表根節(jié)點(diǎn)。

圖5 空的線程就緒列表

可以將線程就緒優(yōu)先級組rt_thread_priority_group看作是一個32位的整型數(shù),與rt_thread_priority_table相似,每一個位對應(yīng)一個優(yōu)先級,如圖6所示。若某線程準(zhǔn)備好,其優(yōu)先級為P(0≤P≤31),則 將rt_thread_priority_group的位置為1,再根據(jù)這一位在線程優(yōu)先級表的對應(yīng)位置插入線程,如此便可以快速地找到線程在線程優(yōu)先級表中插入和移除的位置。

圖6 線程就緒優(yōu)先級組的位號與線程優(yōu)先級對應(yīng)的關(guān)系

2.2 創(chuàng)建主線程和空閑線程

定時器和調(diào)度器初始化完成之后,系統(tǒng)創(chuàng)建主線程和空閑線程,然后將它們加入就緒列表,便于調(diào)度器啟動后可以立即運(yùn)行主線程來執(zhí)行用戶程序,并確保CPU在無任務(wù)時通過空閑線程保持運(yùn)轉(zhuǎn)。

2.2.1 創(chuàng)建主線程

RT-Thread啟動時,需要先創(chuàng)建一個主線程為其分配運(yùn)行所需的資源,設(shè)置入口函數(shù)、線程棧、優(yōu)先級和時間片等。在調(diào)度器啟動后,在入口函數(shù)中調(diào)用main()函數(shù)創(chuàng)建用戶線程,流程如圖7所示。當(dāng)所有用戶線程都成功創(chuàng)建后,終止主線程。

圖7 主線程和用戶線程創(chuàng)建關(guān)系

主線程的創(chuàng)建主要由函數(shù)rt_application_init()完成,整個過程由創(chuàng)建線程和啟動線程兩部分組成。函數(shù)調(diào)用關(guān)系如圖8所示。

圖8 創(chuàng)建主線程的函數(shù)調(diào)用關(guān)系

1)創(chuàng)建主線程

受嵌入式環(huán)境下的低資源條件限制,主線程在執(zhí)行完必要的任務(wù)后可以回收其資源,因此系統(tǒng)調(diào)用rt_thread_create()函數(shù)來創(chuàng)建主線程。該函數(shù)創(chuàng)建線程采用動態(tài)分配內(nèi)存的方式,即該線程控制塊(TCB)和線程棧的內(nèi)存都從堆內(nèi)存中申請分配,若堆內(nèi)存不足,內(nèi)存分配失敗,則整個線程創(chuàng)建失敗。創(chuàng)建成功后若線程不再運(yùn)行也可主動刪除線程來釋放對應(yīng)的內(nèi)存資源。

創(chuàng)建主線程的過程由內(nèi)核對象分配rt_object_allocate()、內(nèi)核內(nèi)存分配RT_KERNEL_MALLOC()、線程實(shí)際初始化_rt_thread_init等函數(shù)構(gòu)成。rt_object_allocate()從堆空間中動態(tài)申請分配TCB并進(jìn)行初始化;RT_KERNEL_MALLOC()從堆空間中動態(tài)申請空間用于線程棧;_rt_thread_init()中初始化TCB各項屬性,包括線程的入口函數(shù)和參數(shù)、線程棧的地址、大小和sp指針以及線程內(nèi)置定時器等。

2)啟動主線程

線程創(chuàng)建并初始化結(jié)束后,此時啟動主線程并不意味著真正啟動運(yùn)行主線程,而是為了調(diào)度器的調(diào)度運(yùn)行進(jìn)行相應(yīng)的初始化,完成的主要工作有:設(shè)置線程當(dāng)前優(yōu)先級及掩碼值;將線程插入就緒列表;判斷是否進(jìn)行一次調(diào)度。由于此時還處于RT-Thread啟動過程中,調(diào)度器還未啟動,所以當(dāng)前線程不進(jìn)行調(diào)度。

2.2.2 創(chuàng)建空閑線程

空閑線程的創(chuàng)建由函數(shù)rt_thread_idle_init()完成,與創(chuàng)建主線程相似的是都先創(chuàng)建并初始化線程,然后調(diào)用相同的rt_thread_startup()函數(shù)啟動線程,不同之處在于創(chuàng)建線程的方式。創(chuàng)建空閑線程采用的是創(chuàng)建靜態(tài)線程對象的方式,實(shí)際調(diào)用的函數(shù)是rt_thread_init()。該方式適用于創(chuàng)建長期的線程對象,其TCB和線程棧的空間在編譯之前就已經(jīng)確定,申請后不可釋放。這是由于空閑線程的主要任務(wù)是在內(nèi)核無用戶線程時被內(nèi)核執(zhí)行,使CPU保持運(yùn)行狀態(tài),同時對終止的無效線程進(jìn)行資源回收的工作,它始終存在于系統(tǒng)內(nèi)。

2.3 啟動調(diào)度器

主線程和空閑線程創(chuàng)建完成后,啟動調(diào)度器切換線程,主要實(shí)現(xiàn)的功能有:首先找到系統(tǒng)當(dāng)前就緒列表中最高優(yōu)先級的線程,然后通過rt_hw_context_switch_to()函數(shù)實(shí)現(xiàn)第一次的線程切換。此時,線程就緒隊列中有主線程(優(yōu)先級為10)和空閑線程(優(yōu)先級為31),因此從線程就緒隊列中選擇主線程開始運(yùn)行。

2.3.1 第一次線程切換函數(shù)

第一次線程切換函數(shù)rt_hw_context_switch_to()采用匯編編程,其功能是為觸發(fā)PendSV進(jìn)行第一次線程切換做前期準(zhǔn)備工作,內(nèi)部用到的一些變量定義如下所示,其功能流程如圖9所示。

圖9 rt_hw_context_switch_to()函數(shù)執(zhí)行流程

該函數(shù)執(zhí)行結(jié)束后,PendSV中斷立即被觸發(fā),執(zhí)行中斷處理程序,PendSV_Handler進(jìn)行線程的真正切換。

2.3.2 PendSV中斷服務(wù)程序

PendSV中斷服務(wù)程序主要分為上文保存和下文切換兩部分,該函數(shù)執(zhí)行流程如圖10所示。首先獲取中斷標(biāo)志位并清零,然后判斷rt_interrupt_from_thread的值是否為0。如果是則表示系統(tǒng)進(jìn)行第一次線程切換,不用做上文保存的工作,直接執(zhí)行下文切換;如果不為0則需要先保存上文,然后再切換到下文。下文切換要做的工作主要有:加載下一個即將運(yùn)行的線程棧的內(nèi)容到CPU寄存器,更改PC指針和PSP指針,從而實(shí)現(xiàn)程序的跳轉(zhuǎn)。

圖10 PendSV_Handler中斷處理程序執(zhí)行流程

3 存儲器使用情況分析

3.1 FLASH使用情況分析

FLASH區(qū)一般用于存放中斷向量表、程序代碼和常數(shù)等。本文選用STM32L431芯片,片內(nèi)FLASH地址范圍為0x0800_0000~0x0803_FFFF,共256 KB。其中前256 B為中斷向量表,共有98個中斷向量。RT-Thread啟動后FLASH中各區(qū)的分配情況如表1所示。

表1 FLASH中的各區(qū)分配情況

3.2 RAM使用情況分析

3.2.1 RT-Thread啟動后RAM使用情況分析

靜態(tài)隨機(jī)存儲器SRAM是RAM的一種,一般用來存儲靜態(tài)變量、臨時變量、全局變量等。在STM32L431芯片中,SRAM的大小為64 KB,地址范圍為0x2000_0000~0x2001_0000。該芯片??臻g是按照地址從高向低的方向生長的,而堆空間的生長方向與之相反,以此減少棧空間和堆空間的重疊錯誤。RT-Thread啟動后RAM中各個段分配情況如表2所示。特別要注意的是,heap段是根據(jù)定義的靜態(tài)數(shù)組rt_heap來決定的,因此在編譯后rt_heap也屬于bss段;同時stack段未有相關(guān)的初始操作,默認(rèn)從RAM地址的最大值+1當(dāng)作棧頂向下使用。

表2 RAM中的各段分配情況

3.2.2 各線程RAM分配情況分析

RT-Thread啟動時,系統(tǒng)先后建立了主線程main、空閑線程idle,這兩個線程的RAM分配情況如表3所示。表中的成員名在線程控制塊結(jié)構(gòu)體中定義,數(shù)據(jù)采用十六進(jìn)制表示,可以通過對程序進(jìn)行單步調(diào)試獲得,這些數(shù)據(jù)會因每次程序的運(yùn)行而有所變化;sp的值等于stack_addr+stack_size-68(棧幀大小為68,其中前64 B為固定區(qū)域,用于保存線程上下文,即在線程切換上下文時保存R0~R12、R14、R15、xPSR等16個寄存器的值,還有4 B為未使用到的FPU標(biāo)志位flag)。

表3 系統(tǒng)線程的RAM分配情況

在主線程函數(shù)app_init中建立3個用戶線程:綠燈線程thd_greenlight、紅燈線程thd_redlight和藍(lán)燈線程thd_bluelight。主線程在這三個用戶線程啟動完后進(jìn)入終止?fàn)顟B(tài),所以此時系統(tǒng)中實(shí)際存在的是空閑線程、綠燈線程、紅燈線程和藍(lán)燈線程這四個線程,它們對應(yīng)的RAM分配如表4所示。

表4 主線程終止后線程的RAM分配情況

RT-Thread啟動后,空閑線程、綠燈線程、紅燈線程和藍(lán)燈線程這四個線程之間的指向關(guān)系如圖11所示。在RT-Thread中就緒列表的每個優(yōu)先級對應(yīng)一條雙向鏈表,即31優(yōu)先級的空閑線程處于一個鏈表,10優(yōu)先級的紅燈線程、藍(lán)燈線程和綠燈線程處于一個鏈表。此處以10優(yōu)先級對應(yīng)的鏈表為例,可在最先啟動的紅燈線程中輸出就緒列表中10優(yōu)先級的鏈表狀況,輸出結(jié)果為:

圖11 就緒列表中用戶線程之間的關(guān)系

其中0x2000_1368地址為就緒列表中10優(yōu)先級對應(yīng)的雙鏈表根節(jié)點(diǎn),即0x2000_18C0、0x2000_1B40、0x2000_1640分別對應(yīng)著綠燈線程、紅燈線程和藍(lán)燈線程。由于線程是通過自身控制塊的tlist節(jié)點(diǎn)成員接入就緒列表中,故與TCB地址有著0x14的偏移,以紅燈線程為例,即0x2000_1B40=0x2000_1B2C+0x14。

4 結(jié) 語

本文通過對SD-RT-Thread工程框架的啟動流程進(jìn)行分析,簡要地給出芯片上電啟動過程,結(jié)合相關(guān)源碼及注釋、流程圖、表格等重點(diǎn)分析RT-Thread的啟動過程;詳細(xì)分析從板級資源到調(diào)度器的初始化過程、主線程和空閑線程的創(chuàng)建過程以及調(diào)度器的啟動過程;最后分析STM32L431的存儲器使用情況,并深入研究在RTThread啟動后RAM的使用情況以及各線程的分配情況。通過剖析,讀者可以快速理解RT-Thread的啟動過程,了解內(nèi)部工作機(jī)制,同時也可為RT-Thread在更多的領(lǐng)域應(yīng)用起到推動作用。

猜你喜歡
鏈表空閑主線
恩賜
人物報道的多維思考、主線聚焦與故事呈現(xiàn)
“鳥”字謎
更加突出主線 落實(shí)四個到位 推動主題教育取得實(shí)實(shí)在在成效
基于二進(jìn)制鏈表的粗糙集屬性約簡
跟麥咭學(xué)編程
基于鏈表多分支路徑樹的云存儲數(shù)據(jù)完整性驗證機(jī)制
彪悍的“寵”生,不需要解釋
數(shù)字主線
WLAN和LTE交通規(guī)則