本篇內(nèi)容比較簡(jiǎn)單,但卻很繁瑣,篇幅也很長(zhǎng),畢竟是囊括了整個(gè)操作系統(tǒng)的生命周期。這篇文章的目的是作為后續(xù)設(shè)計(jì)多任務(wù)開(kāi)發(fā)的鋪墊,后續(xù)會(huì)單獨(dú)再抽出一篇分析任務(wù)的相關(guān)知識(shí)。另外本篇文章以單核MCU為背景,并且以最新的3.1.xLTS版本源碼進(jìn)行分析。主要內(nèi)容目錄如下:
基于bsp/stm32/stm32f103-mini-system為背景
Cortex-M3的堆棧基礎(chǔ)概念
C語(yǔ)言main函數(shù)和rt-thread的main
rt-thread操作系統(tǒng)的傳統(tǒng)初始化與自動(dòng)初始化組件
任務(wù)是怎樣運(yùn)行起來(lái)的
Idle任務(wù)與新的構(gòu)想
基于bsp/stm32/stm32f103-mini-system的開(kāi)機(jī)介紹
關(guān)于體系結(jié)構(gòu)的知識(shí)這里不做過(guò)多的介紹,因?yàn)檫@些知識(shí)要講清楚的話足以寫(xiě)出一本大部頭的書(shū)出來(lái)。不過(guò)會(huì)簡(jiǎn)單介紹一些必要的東西。
Stm32f103單片機(jī)是cortex-m3內(nèi)核,在cortex-m3內(nèi)核中使用雙堆棧psp和msp,模式分為線程模式和handler模式,權(quán)限級(jí)別分為非特權(quán)級(jí)別和特權(quán)級(jí)別(現(xiàn)在只需要知道這么多就行了),handler模式就是當(dāng)處理發(fā)生中斷的時(shí)候自動(dòng)進(jìn)入的模式,其handler模式永遠(yuǎn)為特權(quán)級(jí)。
上電開(kāi)機(jī)最開(kāi)始運(yùn)行的是MCU內(nèi)部的ROM部分,這部分代表我們通??床坏剑渫ǔJ菍?duì)芯片進(jìn)行必要的初始化,比如FLASH和RAM的時(shí)鐘初始化等,然后跳轉(zhuǎn)到用戶flash區(qū)域運(yùn)行用戶代碼。在STM32中用戶flash地址從0x08000000開(kāi)始。我們寫(xiě)的代碼都是從這里開(kāi)始運(yùn)行的。其次由于cortexM規(guī)定其用戶FLASH區(qū)域的最前面必須是一張中斷向量表。所以也就是說(shuō)STM32的0x08000000開(kāi)始是一張中斷向量表,這是必須的也是默認(rèn)的,當(dāng)然在之后還可以重映射其它地方的向量表。這張向量表中的第一項(xiàng)是一個(gè)棧地址,第二項(xiàng)復(fù)位向量地址。下面貼一段向量表部分代碼(摘錄自startup_stm32f103xb.s):
向??滑動(dòng)可查看全部>>
__Vectors DCD __initial_sp ; Topof Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMIHandler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
另外需要注意的是開(kāi)機(jī)后會(huì)自動(dòng)進(jìn)入復(fù)位異常,通常我們叫上電復(fù)位過(guò)程,不過(guò)意外的是上電復(fù)位處理的模式是特權(quán)級(jí)線程模式。在特權(quán)模式下堆棧指針將使用MSP,非特權(quán)模式下可以被切換到PSP。RT-Thread操作系統(tǒng)就是這么做的。所以回過(guò)頭來(lái)看,中斷向量表第一項(xiàng)指定了MSP的棧起始地址,并被自動(dòng)加載到MSP,第二項(xiàng)指定了復(fù)位向量地址,也被自動(dòng)加載到PC并運(yùn)行。這樣一來(lái)開(kāi)機(jī)后我們能通過(guò)debug看到PC指針最先指向復(fù)位向量的第一條指令上。我們看一下stm32f103在armcc編譯器上的復(fù)位向量代碼:
向??滑動(dòng)可查看全部>>
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
這是一段匯編代碼,其完成兩件事,第一件事調(diào)用systemInit函數(shù)完成一些初始化,第二件事跳轉(zhuǎn)到__main函數(shù)。其中systemInit函數(shù)我們是可以找到并可以修改的一個(gè)C語(yǔ)言實(shí)現(xiàn)的函數(shù)(暫時(shí)不討論,有興趣的可以看system_stm32f1xx.c)。而這個(gè)__main就牛逼了,這既不是我們自己寫(xiě)的C語(yǔ)言的main也看不到它在哪里實(shí)現(xiàn)的。但是現(xiàn)在進(jìn)入__main后它就是會(huì)跑到你最終用C語(yǔ)言寫(xiě)的main。這個(gè)__main的來(lái)龍去脈稍后會(huì)在第三部分分析。
Cortex-M3的堆棧基礎(chǔ)概念
在Cortex-M3的處理器內(nèi)核上堆棧指針?lè)譃镻SP和MSP。handler模式下總是使用MSP,線程模式可以通過(guò)CONTROL寄存器來(lái)配置(修改的時(shí)候必須處于特權(quán)模式才可以)。
之所以需要這樣設(shè)計(jì)就是為了將普通軟件和系統(tǒng)軟件通過(guò)權(quán)限隔離開(kāi),避免普通用戶權(quán)限操作系統(tǒng)關(guān)鍵資源帶來(lái)安全風(fēng)險(xiǎn)。當(dāng)我們使用帶有操作系統(tǒng)的環(huán)境進(jìn)行開(kāi)發(fā)時(shí),操作系統(tǒng)就會(huì)將關(guān)鍵操作例如任務(wù)切換、中斷處理等在特權(quán)模式操作。而其它的操作都會(huì)運(yùn)行在非特權(quán)模式下完成。
操作系統(tǒng)一般都會(huì)將必要的操作封裝出API接口,以提供給普通軟件調(diào)用。而這背后的設(shè)計(jì)思想就是通過(guò)觸發(fā)異常,然后進(jìn)入特權(quán)模式運(yùn)行異常向量處理程序。而這段異常處理程序早就讓操作系統(tǒng)實(shí)現(xiàn)了,進(jìn)而這部分特權(quán)操作是操作系統(tǒng)接管處理的。這也就避免用戶普通軟件去進(jìn)行不必要的特權(quán)操作。例如用戶任務(wù)想主動(dòng)放棄CPU從而調(diào)用yield,yield將進(jìn)行任務(wù)切換,其中過(guò)程大概是“選出另一個(gè)任務(wù)”->”觸發(fā)SVC或者Pendsv異?!?>進(jìn)入SVC/Pendsv的handler異常處理程序,此時(shí)是特權(quán)模式,完成操作后返回到新任務(wù)運(yùn)行。在RT-Thread中進(jìn)入任務(wù)切換是通過(guò)觸發(fā)Pendsv異常。
C語(yǔ)言main函數(shù)和RT-Thread的main
前面提到過(guò)開(kāi)機(jī)啟動(dòng)最后進(jìn)入復(fù)位向量處運(yùn)行,最終調(diào)用__main就跑到我們外面寫(xiě)的C語(yǔ)言的main函數(shù)了。但這并非這么簡(jiǎn)單,在從__main到我們的main中間還有一系列操作比如初始化堆棧、初始化全局變量區(qū)域、初始化C運(yùn)行時(shí)庫(kù)等,然后再在最后調(diào)用用戶的main函數(shù)。
不過(guò)在不同的編譯器上這個(gè)__main并非時(shí)固定的,這里也就armcc是如此,如果是GCC和IAR的話其就不太一樣,不過(guò)不影響我們分析核心主題。這里僅以借用armcc為例來(lái)分析主題中心思想。另外在說(shuō)明RT-Thread中開(kāi)啟RT_USING_USER_MAIN的時(shí)候在ARMCC編譯器上還有一個(gè)支持掛鉤的操作,這種操作一般見(jiàn)于補(bǔ)丁修復(fù)的時(shí)候。其實(shí)現(xiàn)方式是在原有函數(shù)的名字前加上$Sub$$前綴就可以將原有函數(shù)劫持下來(lái),并通過(guò)加上$Sub$$前綴再調(diào)用原始函數(shù)。具體如下:The followingexample shows how to use $Super$$ and $Sub$$ to insert a callto the function ExtraFunc() before the call to the legacy function foo().
向??滑動(dòng)可查看全部>>
extern void ExtraFunc(void);extern void $Super$$foo(void); /* this functionis called instead of the original foo() */void $Sub$$foo(void){ ExtraFunc(); /* does some extra setup work */ $Super$$foo(); /* calls the original foo() function *//* To avoid calling the original foo() function * omit the $Super$$foo(); function call. */}
上例中原本有一個(gè)原始函數(shù)叫做foo,但是現(xiàn)在通過(guò)$Sub$$foo來(lái)劫持所有調(diào)用foo的地方,自動(dòng)會(huì)調(diào)用$Sub$$foo,然后新的¥Sub$$foo里面先調(diào)用自己的擴(kuò)展實(shí)現(xiàn)ExtraFunc后,再接著調(diào)用原始版本的foo函數(shù),不過(guò)調(diào)用原始的foo是加了前綴$Super$$的$Super$$foo.
當(dāng)使用RT-Thread操作系統(tǒng)開(kāi)啟RT_USING_USER_MAIN后就是利用這種騷操作來(lái)完成RT-Thread操作系統(tǒng)的初始化過(guò)程的。(代碼摘錄自components.c)
向??滑動(dòng)可查看全部>>
extern int $Super$$main(void);/* re-definemain function */int $Sub$$main(void){ rtthread_startup();return 0;}
關(guān)于rtthread_startup函數(shù)稍后再講解,不過(guò)先接著看下面這個(gè)函數(shù):
向??滑動(dòng)可查看全部>>
/* the systemmain thread */void main_thread_entry(void*parameter){extern int main(void);extern int $Super$$main(void); /* RT-Thread components initialization*/ rt_components_init(); /* invoke system main function */#if defined(__CC_ARM) || defined(__CLANG_ARM) $Super$$main(); /* for ARMCC. */#elif defined(__ICCARM__) || defined(__GNUC__) main();#endif}
上面這個(gè)函數(shù)其實(shí)是個(gè)小任務(wù),就是完成組件初始化后再跳轉(zhuǎn)到用戶main函數(shù)的。這個(gè)小任務(wù)再rtthread_startup中調(diào)用rt_application_init時(shí)創(chuàng)建的,所以此時(shí)rt-thread系統(tǒng)早就以經(jīng)跑起來(lái)了。也就是說(shuō)當(dāng)調(diào)用rtthread_startup后正常情況就不再會(huì)返回到原來(lái)的調(diào)用地方,接下來(lái)會(huì)交給系統(tǒng)的調(diào)度器去接管,切換運(yùn)行任務(wù)去了。看下面的代碼了解rt_application_init:
向??滑動(dòng)可查看全部>>
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create('main', main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, 'main', main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using toeliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}
至此,關(guān)于各種main的子子孫孫以經(jīng)差不多了解清楚了,其流程大概如下:
ResetHandle->__main->$Sub$$main->(rtthread_startup->rt_application_init->main_thread_entry)->$Super$$main。其中$Super$$main就是我們的用戶main函數(shù)。如果沒(méi)有啟用RT_USING_USER_MAIN那就簡(jiǎn)單了,其流程如下:
ResetHandle->__main->main
接下來(lái)再接著分析$Sub$$main中調(diào)用的rtthread_startup函數(shù)。
RT-Thread操作系統(tǒng)的傳統(tǒng)初始化與自動(dòng)初始化組件
這里著重討論rtthread_startup函數(shù),因?yàn)檫@就是RT-Thread操作系統(tǒng)的入口和初始化流程。不過(guò)既然說(shuō)到rtthread_startup函數(shù)了,就不得不一起介紹一下RT-Thread操作系統(tǒng)的自動(dòng)初始化組件了。
rtthread_startup函數(shù)時(shí)一個(gè)函數(shù)調(diào)用鏈,依次調(diào)用各個(gè)階段的初始化函數(shù),并在最后啟動(dòng)調(diào)度器不在返回。代碼摘錄自components.c
向??滑動(dòng)可查看全部>>
int rtthread_startup(void){ rt_hw_interrupt_disable(); /* board level initialization * NOTE: please initialize heap insideboard initialization. */ rt_hw_board_init(); /* show RT-Thread version */ rt_show_version(); /* timer system initialization */ rt_system_timer_init(); /* scheduler system initialization */ rt_system_scheduler_init(); #ifdef RT_USING_SIGNALS/* signal system initialization */ rt_system_signal_init();#endif /* create init_thread */ rt_application_init(); /* timer thread initialization */ rt_system_timer_thread_init(); /* idle thread initialization */ rt_thread_idle_init(); /* start scheduler */ rt_system_scheduler_start(); /* never reach here */return 0;}
以上代碼我們主要脈絡(luò)是這樣的:先關(guān)閉全局中斷->初始化硬件板上的資源->打印RT-Thread的LOGO->系統(tǒng)定時(shí)器功能初始化->調(diào)度器初始化->signal功能初始化->應(yīng)用程序初始化(這個(gè)通常是用來(lái)創(chuàng)建用戶任務(wù)的)->系統(tǒng)軟timer任務(wù)初始化->系統(tǒng)idle任務(wù)初始化->啟動(dòng)調(diào)度器,永遠(yuǎn)不再返回。
這里我們先來(lái)說(shuō)一下為什么要先關(guān)閉全局中斷,因?yàn)樵诔跏蓟^(guò)程中,有可能MCU就有其它的中斷和異常觸發(fā)了,這個(gè)時(shí)候系統(tǒng)還沒(méi)有初始化完成,這就勢(shì)必導(dǎo)致系統(tǒng)出現(xiàn)故障,所以先關(guān)閉全局中斷,并在啟動(dòng)調(diào)度器后再打開(kāi)。
rt_hw_board_init非常關(guān)鍵,在這個(gè)函數(shù)里面必須完成一些必須的初始化過(guò)程:堆內(nèi)存系統(tǒng)的初始化和硬件資源模塊以及如果開(kāi)啟了自動(dòng)初始化組件時(shí)還需要調(diào)用rt_components_board_init完成必要的初始化,這個(gè)函數(shù)是自動(dòng)初始化組件的一個(gè)接口。(代碼摘錄自bspstm32librariesHAL_Driversdrv_common.c)
向??滑動(dòng)可查看全部>>
RT_WEAK void rt_hw_board_init()
{
#ifdef SCB_EnableICache
/* EnableI-Cache---------------------------------------------------------*/
SCB_EnableICache();
#endif
#ifdef SCB_EnableDCache
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
#endif
/* HAL_Init() function is called at thebeginning of the program */
HAL_Init();
/* System clock initialization */
SystemClock_Config();
rt_hw_systick_init();
/* Heap initialization */
#if defined(RT_USING_HEAP)
rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END);
#endif
/* Pin driver initialization is open bydefault */
#ifdef RT_USING_PIN
rt_hw_pin_init();
#endif
/* USART driver initialization is openby default */
#ifdef RT_USING_SERIAL
rt_hw_usart_init();
#endif
/* Set the shell console output device*/
#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/* Board underlying hardwareinitialization */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
然后回到rtthread_startup函數(shù)中再看rt_application_init函數(shù),由于我們是用的stm32的BSP,這個(gè)bsp系列是使用自動(dòng)初始化組件和RT_USING_USER_MAIN功能的,所以過(guò)程稍微隱蔽一些,先是再rt_application_init中創(chuàng)建了一個(gè)小任務(wù),然后再在小任務(wù)中調(diào)用了rt_components_init,這也是自動(dòng)初始化組件的接口。如果沒(méi)有開(kāi)啟自動(dòng)初始化組件的話,通常我們的用戶任務(wù)可以在rt_application_init中創(chuàng)建了。也可以像這里的實(shí)現(xiàn)一樣,先創(chuàng)建一個(gè)小任務(wù),然后再在小任務(wù)里完成一些初始化和創(chuàng)建用戶任務(wù)。
然后再回到rthtread_startup中看到有初始化軟timer和idle任務(wù)的,其中軟件timer功能是可以通過(guò)裁剪配置選擇的,如果打開(kāi)后就可以在后續(xù)創(chuàng)建softtimer。否則所有的timer都會(huì)在OS TICK的中斷上下文中計(jì)時(shí)。另外這個(gè)idle任務(wù)也是系統(tǒng)中必不可上和優(yōu)先級(jí)最低的任務(wù)。即使我們啟動(dòng)調(diào)度器后沒(méi)有創(chuàng)建任何用戶任務(wù),系統(tǒng)中也有一個(gè)idle任務(wù)在運(yùn)行。Idle任務(wù)的優(yōu)先級(jí)最低,在此我建議開(kāi)發(fā)人員最好不要將自己的用戶任務(wù)優(yōu)先級(jí)配置成最低以免和idle競(jìng)爭(zhēng)時(shí)間片,這會(huì)給你今后的開(kāi)發(fā)帶來(lái)不必要的麻煩。關(guān)于這個(gè)問(wèn)題,我最后會(huì)提出一些新的設(shè)計(jì)構(gòu)想。不過(guò)這里先要介紹一下idle任務(wù)的功能。Idle任務(wù)會(huì)在系統(tǒng)空閑時(shí)被調(diào)度運(yùn)行,所以我們通常在idle任務(wù)里做低功耗設(shè)計(jì)。其次idle任務(wù)里還會(huì)完成系統(tǒng)資源的回收。例如被刪除的任務(wù),被刪除的module等。
最后rthtread_startup啟動(dòng)調(diào)度器rt_system_scheduler_start開(kāi)始調(diào)度系統(tǒng)的任務(wù),從此就開(kāi)始運(yùn)行任務(wù),不再返回。這里又要記住一個(gè)概念,在上文提到的PSP和MSP,到目前為止MCU還是使用一開(kāi)始中斷向量表中指定的MSP棧。但是當(dāng)調(diào)度任務(wù)后,任務(wù)會(huì)有自己的棧,且rt-thread系統(tǒng)會(huì)將任務(wù)的棧切換到PSP棧指針。值得注意的是,這個(gè)MSP是全局共享的,所有的中斷程序都會(huì)使用這個(gè)??臻g,所以我們需要根據(jù)自己的情況來(lái)配置這個(gè)MSP棧的空間大小。
接下來(lái)我們?cè)賮?lái)介紹自動(dòng)初始化組件。RT-Thread中的自動(dòng)初始化組件思路來(lái)自于Linux內(nèi)核。其實(shí)現(xiàn)手段是將需要初始化的函數(shù)接口通過(guò)鏈接器指令放在特殊的section中。這個(gè)section的概念是當(dāng)我們程序最終鏈接成一個(gè)image后會(huì)形成一個(gè)標(biāo)準(zhǔn)格式的文件,其中armcc中叫做ARM ELF。詳細(xì)的介紹可以查閱官方資料。其中ELF文件就有將代碼分成成為section的區(qū)域,可以稱作段。并且可以指定自己的代碼放在指定名稱的段中,且可以指定這個(gè)section段的ROM地址。這樣當(dāng)我們?cè)O(shè)計(jì)玩初始化接口后,通過(guò)鏈接器的指令以及鏈接腳本文件將我們的初始化代碼放在特定的地方,并且利用命名規(guī)則來(lái)做到順序排序。等需要調(diào)用初始化的時(shí)候可以利用這些section的地址轉(zhuǎn)換成函數(shù)指針直接批量循環(huán)調(diào)用。通常你會(huì)再M(fèi)DK的工程文件中鏈接器參數(shù)中看到這樣的指令:--keep *.o(.rti_fn.*),這是為了在鏈接階段保證這些自定義段不被刪除。同時(shí)也可以看出rti_fn就是自動(dòng)初始化組件的section名字。類似的將函數(shù)放置在這些段中的鏈接器指令如下:(摘錄自rtdef.h)
向??滑動(dòng)可查看全部>>
/*initialization export */#ifdef RT_USING_COMPONENTS_INITtypedef int (*init_fn_t)(void);#ifdef _MSC_VER/* we do notsupport MS VC++ compiler */#define INIT_EXPORT(fn,level)#else #if RT_DEBUG_INITstruct rt_init_desc {const char* fn_name;const init_fn_t fn; };#define INIT_EXPORT(fn, level) const char __rti_##fn##_name[] =#fn; RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION('.rti_fn.'level)= { __rti_##fn##_name, fn};#else#define INIT_EXPORT(fn, level) RT_USED const init_fn_t __rt_init_##fn SECTION('.rti_fn.'level)= fn#endif#endif#else#define INIT_EXPORT(fn, level)#endif /* board initroutines will be called in board_init() function */#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn,'1') /*pre/device/component/env/app init routines will be called in init_thread *//* componentspre-initialization (pure software initilization) */#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn,'2')/* deviceinitialization */#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn,'3')/* componentsinitialization (dfs, lwip, ...) */#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn,'4')/* environmentinitialization (mount disk, ...) */#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn,'5')/* appliationinitialization (rtgui application etc ...) */#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn,'6')
其中不同的數(shù)字代表不同的初始化順序,可以根據(jù)需要來(lái)選擇。接著如上文提到的兩個(gè)函數(shù)rt_components_board_init和rt_components_init是如何實(shí)現(xiàn)的:摘錄自components.c
向??滑動(dòng)可查看全部>>
#ifdef RT_USING_COMPONENTS_INIT
/*
* Components Initialization will initializesome driver and components as following
* order:
* rti_start --> 0
* BOARD_EXPORT --> 1
* rti_board_end --> 1.end
*
* DEVICE_EXPORT --> 2
* COMPONENT_EXPORT --> 3
* FS_EXPORT --> 4
* ENV_EXPORT --> 5
* APP_EXPORT --> 6
*
* rti_end --> 6.end
*
* These automatically initialization, thedriver or component initial function must
* be defined with:
* INIT_BOARD_EXPORT(fn);
* INIT_DEVICE_EXPORT(fn);
* ...
* INIT_APP_EXPORT(fn);
* etc.
*/
static int rti_start(void)
{
return 0;
}
INIT_EXPORT(rti_start,'0');
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start,'0.end');
static int rti_board_end(void)
{
return 0;
}
INIT_EXPORT(rti_board_end,'1.end');
static int rti_end(void)
{
return 0;
}
INIT_EXPORT(rti_end,'6.end');
/**
* RT-Thread Components Initialization forboard
*/
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf('initialize %s', desc->fn_name);
result = desc->fn();
rt_kprintf(':%d done', result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
/**
* RT-Thread Components Initialization
*/
void rt_components_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
rt_kprintf('do components initialization.');
for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc++)
{
rt_kprintf('initialize %s', desc->fn_name);
result = desc->fn();
rt_kprintf(':%d done', result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
之所以要分開(kāi)這兩個(gè)函數(shù)就是因?yàn)閎oard階段的初始化比其它普通的組件初始化早,board階段的初始化通常沒(méi)什么系統(tǒng)資源依賴。而其它情況下則通常在操作系統(tǒng)以經(jīng)完成必要的初始化后才能做的初始化才會(huì)放在rt_components_init里。
任務(wù)是怎樣運(yùn)行起來(lái)的
要說(shuō)明任務(wù)是怎么運(yùn)行起來(lái)的,就得知道任務(wù)是怎么創(chuàng)建得,其次結(jié)合之前寫(xiě)的文章<源碼解讀·RT-Thread多任務(wù)調(diào)度算法>就差不多了。那么這里就介紹一下任務(wù)的創(chuàng)建。照樣用上面的rt_application_init里創(chuàng)建任務(wù)的代碼來(lái)舉例:
向??滑動(dòng)可查看全部>>
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create('main', main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result =rt_thread_init(tid, 'main', main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack),RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using toeliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}
首先要說(shuō)明的是RT-Thread任務(wù)創(chuàng)建有兩種,一種是動(dòng)態(tài)的,一種是靜態(tài)的。所謂的動(dòng)態(tài)就是其任務(wù)棧自動(dòng)在堆內(nèi)存中分配;靜態(tài)是用戶自己指定??臻g,當(dāng)然通常這個(gè)棧來(lái)自于用戶定義的數(shù)組。如上例中當(dāng)RT_USING_HEAP宏被打開(kāi),也就是有堆內(nèi)存的時(shí)候會(huì)采用rt_thread_create接口來(lái)創(chuàng)建動(dòng)態(tài)資源的任務(wù)。當(dāng)然可以利用rt_thread_init來(lái)創(chuàng)建一個(gè)靜態(tài)資源的任務(wù)。先來(lái)了解一下這兩個(gè)函數(shù)在創(chuàng)建任務(wù)時(shí)的一些參數(shù):”main”這是任務(wù)的名稱,任務(wù)名稱用一個(gè)字符串來(lái)指定,不是很重要,不過(guò)最好能起到一定的說(shuō)明性,有利于今后調(diào)試用。main_thread_entry這是任務(wù)的入口函數(shù),所謂的任務(wù)就是一個(gè)C語(yǔ)言中的函數(shù)而已。RT_NULL,這是傳給任務(wù)入口函數(shù)的參數(shù),如果沒(méi)有就為NULL.因?yàn)镽T_Thread中的任務(wù)原型為:void (*entry)(void*parameter);RT_MAIN_THREAD_STACK_SIZE為任務(wù)的棧大小,以字節(jié)為單位。RT_MAIN_THREAD_PRIORITY為任務(wù)的優(yōu)先級(jí)號(hào)。20為任務(wù)的時(shí)間片大小。其中靜態(tài)任務(wù)中還有tid代表任務(wù)的TCB數(shù)據(jù)結(jié)構(gòu)句柄。main_stack為??臻g起始地址。當(dāng)用動(dòng)態(tài)創(chuàng)建的方法創(chuàng)建成功后會(huì)返回一個(gè)任務(wù)的TCB任務(wù)句柄出來(lái)。之后我們利用rt_thread_startup(任務(wù)句柄)的形式啟動(dòng)任務(wù)即可。例如上例中rt_thread_startup(tid);不過(guò)rt_thread_startup函數(shù)真正的功能是將任務(wù)放置與調(diào)度隊(duì)列中,并值任務(wù)狀態(tài)為ready,由此交給調(diào)度器去調(diào)度,能不能立馬運(yùn)行取決與調(diào)度器的調(diào)度。一般情況下,要想任務(wù)獲得運(yùn)行必須滿足的條件:調(diào)度器已經(jīng)運(yùn)行,任務(wù)以經(jīng)ready,沒(méi)有更高優(yōu)先級(jí)任務(wù),沒(méi)有中斷發(fā)生。只要條件滿足調(diào)度器就會(huì)調(diào)度此任務(wù),做好必要的棧初始化和狀態(tài)置位,就會(huì)切換到任務(wù)開(kāi)始運(yùn)行。只要任務(wù)獲得運(yùn)行就會(huì)使用創(chuàng)建任務(wù)時(shí)指定的棧空間。
不過(guò)一般的任務(wù)通常是一直運(yùn)行,持續(xù)的服務(wù)。形式如下:
向??滑動(dòng)可查看全部>>
void task(void *parameter)
{
while (1)
{
// do_work();
}
}
idle任務(wù)與新的構(gòu)想
上面解釋過(guò)idle任務(wù)在rt-thread操作系統(tǒng)中的功能:釋放資源、低功耗設(shè)計(jì)。
關(guān)于資源釋放通常是任務(wù)的析構(gòu)過(guò)程,這就是任務(wù)的結(jié)束。例如上例中的main_thread_entry任務(wù)之所以稱為小任務(wù)的原因就是它做完事情就結(jié)束了。那么可能就會(huì)想,既然任務(wù)都結(jié)束了那么它的資源如何釋放呢?比如??臻g,TCB等。這就是idle該干的事情。即使所有的用戶任務(wù)都結(jié)束,最后也會(huì)剩下idle任務(wù)在運(yùn)行。如果有必要的話,可以在idle任務(wù)中可以通過(guò)調(diào)用低功耗組件進(jìn)入低功耗或者干脆調(diào)用電源開(kāi)關(guān)控制來(lái)關(guān)機(jī)。
其次idle任務(wù)占用了最低優(yōu)先級(jí)。雖然用戶任務(wù)也可以使用和idle任務(wù)相同的優(yōu)先級(jí),但是并不建議這樣做,比如在低功耗設(shè)計(jì)時(shí)就會(huì)出問(wèn)題。另外我個(gè)人在思考一個(gè)問(wèn)題,idel任務(wù)既然以經(jīng)在設(shè)計(jì)之初就明確了其獲得運(yùn)行的條件,那么何不做成無(wú)需優(yōu)先級(jí)的任務(wù),唯一的調(diào)度決策就是:當(dāng)調(diào)度器沒(méi)有任務(wù)處于ready狀態(tài)時(shí)就切換到idel任務(wù)運(yùn)行。這就無(wú)需關(guān)注最低優(yōu)先級(jí)被idle霸占的問(wèn)題了。
線上活動(dòng)
1、【RT-Thread能力認(rèn)證考試12月——RCEA】經(jīng)過(guò)第一次考試的驗(yàn)證,RT-Thread能力認(rèn)證得到了更多社區(qū)開(kāi)發(fā)者和產(chǎn)業(yè)界的大力支持!(點(diǎn)此查看)如果您有晉升、求職、尋找更好機(jī)會(huì)的需要,有深入學(xué)習(xí)和掌握RT-Thread的需求,歡迎垂詢/報(bào)考!
能力認(rèn)證官網(wǎng)鏈接:https://www.rt-thread.org/page/rac.html(在外部瀏覽器打開(kāi))
掃碼報(bào)名
2、【智能戰(zhàn)車DIY活動(dòng)】如果你想要打造一輛戰(zhàn)車給孩子當(dāng)玩具,想和小伙伴“robomaster”,用作課程demo調(diào)動(dòng)學(xué)生興趣····加入我們!1、在制作智能戰(zhàn)車的過(guò)程中,您獲得來(lái)自RT-Thread官方團(tuán)隊(duì)的全程技術(shù)支持與指導(dǎo)2、前3名優(yōu)勝者,有1000元現(xiàn)金獎(jiǎng)勵(lì)與徽章!3、參與活動(dòng),即得參與獎(jiǎng)(T恤、書(shū)籍等)
馬上報(bào)名
#題外話# 喜歡RT-Thread不要忘了在GitHub上留下你的
聯(lián)系客服