FreeRTOS進(jìn)階之調(diào)度器啟動(dòng)過程分析
FreeRTOS基本程序架構(gòu)
int main(void) { 必要的初始化工作; 創(chuàng)建任務(wù)1; 創(chuàng)建任務(wù)2; ... vTaskStartScheduler(); /*啟動(dòng)調(diào)度器*/ while(1); }
任務(wù)創(chuàng)建完成后,靜態(tài)變量指針pxCurrentTCB(見《FreeRTOS進(jìn)階之任務(wù)創(chuàng)建完全解析》第7節(jié)內(nèi)容)指向優(yōu)先級(jí)最高的就緒任務(wù)。但此時(shí)任務(wù)并不能運(yùn)行,因?yàn)榻酉聛磉€有關(guān)鍵的一步:?jiǎn)?dòng)FreeRTOS調(diào)度器。
啟動(dòng)FreeRTOS調(diào)度器
調(diào)度器是FreeRTOS操作系統(tǒng)的核心,主要負(fù)責(zé)任務(wù)切換,即找出最高優(yōu)先級(jí)的就緒任務(wù),并使之獲得CPU運(yùn)行權(quán)。調(diào)度器并非自動(dòng)運(yùn)行的,需要人為啟動(dòng)它。
API函數(shù)vTaskStartScheduler()用于啟動(dòng)調(diào)度器,它會(huì)創(chuàng)建一個(gè)空閑任務(wù)、初始化一些靜態(tài)變量,最主要的,它會(huì)初始化系統(tǒng)節(jié)拍定時(shí)器并設(shè)置好相應(yīng)的中斷,然后啟動(dòng)第一個(gè)任務(wù)。這篇文章用于分析啟動(dòng)調(diào)度器的過程,和上一篇文章一樣,啟動(dòng)調(diào)度器也涉及到硬件特性(比如系統(tǒng)節(jié)拍定時(shí)器初始化等),因此本文仍然以Cortex-M3架構(gòu)為例。
啟動(dòng)調(diào)度器的API函數(shù)vTaskStartScheduler()的源碼精簡(jiǎn)后如下所示:
void vTaskStartScheduler( void ) { BaseType_t xReturn; StaticTask_t *pxIdleTaskTCBBuffer= NULL; StackType_t *pxIdleTaskStackBuffer= NULL; uint16_t usIdleTaskStackSize =tskIDLE_STACK_SIZE; /*如果使用靜態(tài)內(nèi)存分配任務(wù)堆棧和任務(wù)TCB,則需要為空閑任務(wù)預(yù)先定義好任務(wù)內(nèi)存和任務(wù)TCB空間*/ #if(configSUPPORT_STATIC_ALLOCATION == 1 ) { vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &usIdleTaskStackSize); } #endif /*configSUPPORT_STATIC_ALLOCATION */ /* 創(chuàng)建空閑任務(wù),使用最低優(yōu)先級(jí)*/ xReturn =xTaskGenericCreate( prvIdleTask, "IDLE",usIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT), &xIdleTaskHandle,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer, NULL ); if( xReturn == pdPASS ) { /* 先關(guān)閉中斷,確保節(jié)拍定時(shí)器中斷不會(huì)在調(diào)用xPortStartScheduler()時(shí)或之前發(fā)生.當(dāng)?shù)谝粋€(gè)任務(wù)啟動(dòng)時(shí),會(huì)重新啟動(dòng)中斷*/ portDISABLE_INTERRUPTS(); /* 初始化靜態(tài)變量 */ xNextTaskUnblockTime = portMAX_DELAY; xSchedulerRunning = pdTRUE; xTickCount = ( TickType_t ) 0U; /* 如果宏configGENERATE_RUN_TIME_STATS被定義,表示使用運(yùn)行時(shí)間統(tǒng)計(jì)功能,則下面這個(gè)宏必須被定義,用于初始化一個(gè)基礎(chǔ)定時(shí)器/計(jì)數(shù)器.*/ portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); /* 設(shè)置系統(tǒng)節(jié)拍定時(shí)器,這與硬件特性相關(guān),因此被放在了移植層.*/ if(xPortStartScheduler() != pdFALSE ) { /* 如果調(diào)度器正確運(yùn)行,則不會(huì)執(zhí)行到這里,函數(shù)也不會(huì)返回*/ } else { /* 僅當(dāng)任務(wù)調(diào)用API函數(shù)xTaskEndScheduler()后,會(huì)執(zhí)行到這里.*/ } } else { /* 執(zhí)行到這里表示內(nèi)核沒有啟動(dòng),可能因?yàn)槎褩?臻g不夠 */ configASSERT( xReturn ); } /* 預(yù)防編譯器警告*/ ( void ) xIdleTaskHandle; }
這個(gè)API函數(shù)首先創(chuàng)建一個(gè)空閑任務(wù),空閑任務(wù)使用最低優(yōu)先級(jí)(0級(jí)),空閑任務(wù)的任務(wù)句柄存放在靜態(tài)變量xIdleTaskHandle中,可以調(diào)用API函數(shù)xTaskGetIdleTaskHandle()獲得空閑任務(wù)句柄。
Cortex-M3中斷優(yōu)先級(jí)
如果任務(wù)創(chuàng)建成功,則關(guān)閉中斷(調(diào)度器啟動(dòng)結(jié)束時(shí)會(huì)再次使能中斷的),初始化一些靜態(tài)變量,然后調(diào)用函數(shù)xPortStartScheduler()來啟動(dòng)系統(tǒng)節(jié)拍定時(shí)器并啟動(dòng)第一個(gè)任務(wù)。因?yàn)樵O(shè)置系統(tǒng)節(jié)拍定時(shí)器涉及到硬件特性,因此函數(shù)xPortStartScheduler()由移植層提供,不同的硬件架構(gòu),這個(gè)函數(shù)的代碼也不相同。
對(duì)于Cortex-M3架構(gòu),函數(shù)xPortStartScheduler()的實(shí)現(xiàn)如下所示:
BaseType_t xPortStartScheduler( void ) { #if(configASSERT_DEFINED == 1 ) { volatile uint32_tulOriginalPriority; /* 中斷優(yōu)先級(jí)寄存器0:IPR0 */ volatile uint8_t * constpucFirstUserPriorityRegister = ( uint8_t * ) (portNVIC_IP_REGISTERS_OFFSET_16 +portFIRST_USER_INTERRUPT_NUMBER ); volatile uint8_tucMaxPriorityValue; /* 這一大段代碼用來確定一個(gè)最高ISR優(yōu)先級(jí),在這個(gè)ISR或者更低優(yōu)先級(jí)的ISR中可以安全的調(diào)用以FromISR結(jié)尾的API函數(shù).*/ /* 保存中斷優(yōu)先級(jí)值,因?yàn)橄旅嬉矊戇@個(gè)寄存器(IPR0) */ ulOriginalPriority = *pucFirstUserPriorityRegister; /* 確定有效的優(yōu)先級(jí)位個(gè)數(shù). 首先向所有位寫1,然后再讀出來,由于無效的優(yōu)先級(jí)位讀出為0,然后數(shù)一數(shù)有多少個(gè)1,就能知道有多少位優(yōu)先級(jí).*/ *pucFirstUserPriorityRegister= portMAX_8_BIT_VALUE; ucMaxPriorityValue = *pucFirstUserPriorityRegister; /* 冗余代碼,用來防止用戶不正確的設(shè)置RTOS可屏蔽中斷優(yōu)先級(jí)值 */ ucMaxSysCallPriority =configMAX_SYSCALL_INTERRUPT_PRIORITY &ucMaxPriorityValue; /* 計(jì)算最大優(yōu)先級(jí)組值 */ ulMaxPRIGROUPValue =portMAX_PRIGROUP_BITS; while( (ucMaxPriorityValue &portTOP_BIT_OF_BYTE ) ==portTOP_BIT_OF_BYTE ) { ulMaxPRIGROUPValue--; ucMaxPriorityValue <<= ( uint8_t ) 0x01; } ulMaxPRIGROUPValue <<=portPRIGROUP_SHIFT; ulMaxPRIGROUPValue &=portPRIORITY_GROUP_MASK; /* 將IPR0寄存器的值復(fù)原*/ *pucFirstUserPriorityRegister= ulOriginalPriority; } #endif /*conifgASSERT_DEFINED */ /* 將PendSV和SysTick中斷設(shè)置為最低優(yōu)先級(jí)*/ portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI; /* 啟動(dòng)系統(tǒng)節(jié)拍定時(shí)器,即SysTick定時(shí)器,初始化中斷周期并使能定時(shí)器*/ vPortSetupTimerInterrupt(); /* 初始化臨界區(qū)嵌套計(jì)數(shù)器 */ uxCriticalNesting = 0; /* 啟動(dòng)第一個(gè)任務(wù) */ prvStartFirstTask(); /* 永遠(yuǎn)不會(huì)到這里! */ return 0; }
從源碼中可以看到,開始的一大段都是冗余代碼。因?yàn)镃ortex-M3的中斷優(yōu)先級(jí)有些違反直覺:Cortex-M3中斷優(yōu)先級(jí)數(shù)值越大,表示優(yōu)先級(jí)越低。而FreeRTOS的任務(wù)優(yōu)先級(jí)則與之相反:優(yōu)先級(jí)數(shù)值越大的任務(wù),優(yōu)先級(jí)越高。
根據(jù)官方統(tǒng)計(jì),在Cortex-M3硬件上使用FreeRTOS,絕大多數(shù)的問題都出在優(yōu)先級(jí)設(shè)置不正確上。
因此,為了使FreeRTOS更健壯,F(xiàn)reeRTOS的作者在編寫Cortex-M3架構(gòu)移植層代碼時(shí),特意增加了冗余代碼。關(guān)于詳細(xì)的Cortex-M3架構(gòu)中斷優(yōu)先級(jí)設(shè)置,參考《Cortex-M內(nèi)核使用注意事項(xiàng)》一文。
在Cortex-M3架構(gòu)中,F(xiàn)reeRTOS為了任務(wù)啟動(dòng)和任務(wù)切換使用了三個(gè)異常:SVC、PendSV和SysTick。
SVC(系統(tǒng)服務(wù)調(diào)用)用于任務(wù)啟動(dòng),有些操作系統(tǒng)不允許應(yīng)用程序直接訪問硬件,而是通過提供一些系統(tǒng)服務(wù)函數(shù),通過SVC來調(diào)用;
PendSV(可掛起系統(tǒng)調(diào)用)用于完成任務(wù)切換,它的最大特性是如果當(dāng)前有優(yōu)先級(jí)比它高的中斷在運(yùn)行,PendSV會(huì)推遲執(zhí)行,直到高優(yōu)先級(jí)中斷執(zhí)行完畢;
SysTick用于產(chǎn)生系統(tǒng)節(jié)拍時(shí)鐘,提供一個(gè)時(shí)間片,如果多個(gè)任務(wù)共享同一個(gè)優(yōu)先級(jí),則每次SysTick中斷,下一個(gè)任務(wù)將獲得一個(gè)時(shí)間片。關(guān)于詳細(xì)的SVC、PendSV異常描述,推薦《Cortex-M3權(quán)威指南》一書的“異常”部分。
這里將PendSV和SysTick異常優(yōu)先級(jí)設(shè)置為最低,這樣任務(wù)切換不會(huì)打斷某個(gè)中斷服務(wù)程序,中斷服務(wù)程序也不會(huì)被延遲,這樣簡(jiǎn)化了設(shè)計(jì),有利于系統(tǒng)穩(wěn)定。
接下來調(diào)用函數(shù)vPortSetupTimerInterrupt()設(shè)置SysTick定時(shí)器中斷周期并使能定時(shí)器運(yùn)行這個(gè)函數(shù)比較簡(jiǎn)單,就是設(shè)置SysTick硬件的相應(yīng)寄存器。
再接下來有一個(gè)關(guān)鍵的函數(shù)是prvStartFirstTask(),這個(gè)函數(shù)用來啟動(dòng)第一個(gè)任務(wù)。我們先看一下源碼:
__asm void prvStartFirstTask( void ) { PRESERVE8 /* Cortext-M3硬件中,0xE000ED08地址處為VTOR(向量表偏移量)寄存器,存儲(chǔ)向量表起始地址*/ ldr r0, =0xE000ED08 ldr r0, [r0] /* 取出向量表中的第一項(xiàng),向量表第一項(xiàng)存儲(chǔ)主堆棧指針MSP的初始值*/ ldr r0, [r0] /* 將堆棧地址存入主堆棧指針 */ msr msp, r0 /* 使能全局中斷*/ cpsie i cpsie f dsb isb /* 調(diào)用SVC啟動(dòng)第一個(gè)任務(wù) */ svc 0 nop nop }
程序開始的幾行代碼用來復(fù)位主堆棧指針MSP的值,表示從此以后MSP指針被FreeRTOS接管,需要注意的是,Cortex-M3硬件的中斷也使用MSP指針。之后使能中斷,使用匯編指令svc 0觸發(fā)SVC中斷,完成啟動(dòng)第一個(gè)任務(wù)的工作。
SVC中斷服務(wù)函數(shù)
__asm void vPortSVCHandler( void ) { PRESERVE8 ldr r3, =pxCurrentTCB /* pxCurrentTCB指向處于最高優(yōu)先級(jí)的就緒任務(wù)TCB */ ldr r1, [r3] /* 獲取任務(wù)TCB地址 */ ldr r0, [r1] /* 獲取任務(wù)TCB的第一個(gè)成員,即當(dāng)前堆棧棧頂pxTopOfStack */ ldmia r0!, {r4-r11} /* 出棧,將寄存器r4~r11出棧 */ msr psp, r0 /* 最新的棧頂指針賦給線程堆棧指針PSP */ isb mov r0, #0 msr basepri, r0 orrr14, #0xd /* 這里0x0d表示:返回后進(jìn)入線程模式,從進(jìn)程堆棧中做出棧操作,返回Thumb狀態(tài)*/ bx r14 }
通過上一篇介紹任務(wù)創(chuàng)建的文章,我們已經(jīng)認(rèn)識(shí)了指針pxCurrentTCB。這是定義在tasks.c中的唯一一個(gè)全局變量,指向處于最高優(yōu)先級(jí)的就緒任務(wù)TCB。我們知道FreeRTOS的核心功能是確保處于最高優(yōu)先級(jí)的就緒任務(wù)獲得CPU權(quán)限,因此可以說這個(gè)指針指向的任務(wù)要么正在運(yùn)行中,要么即將運(yùn)行(調(diào)度器關(guān)閉),所以這個(gè)變量才被命名為pxCurrentTCB。
根據(jù)《FreeRTOS進(jìn)階之任務(wù)創(chuàng)建》第三節(jié)我們可以知道,一個(gè)任務(wù)創(chuàng)建時(shí),會(huì)將它的任務(wù)堆棧初始化的像是經(jīng)過一次任務(wù)切換一樣,如圖1-1所示。對(duì)于Cortex-M3架構(gòu),需要依次入棧xPSR、PC、LR、R12、R3~R0、R11~R4,其中r11~R4需要人為入棧,其它寄存器由硬件自動(dòng)入棧。寄存器PC被初始化為任務(wù)函數(shù)指針vTask_A,這樣當(dāng)某次任務(wù)切換后,任務(wù)A獲得CPU控制權(quán),任務(wù)函數(shù)vTask_A被出棧到PC寄存器,之后會(huì)執(zhí)行任務(wù)A的代碼;LR寄存器初始化為函數(shù)指針prvTaskExitError,這是由移植層提供的一個(gè)出錯(cuò)處理函數(shù)。
任務(wù)TCB結(jié)構(gòu)體成員pxTopOfStack表示當(dāng)前堆棧的棧頂,它指向最后一個(gè)入棧的項(xiàng)目,所以在圖中它指向R4,TCB結(jié)構(gòu)體另外一個(gè)成員pxStack表示堆棧的起始位置,所以在圖中它指向堆棧的最開始處。
圖1-1:任務(wù)創(chuàng)建后任務(wù)堆棧分布情況
所以,SVC中斷服務(wù)函數(shù)一開始就使用全局指針pxCurrentTCB獲得第一個(gè)要啟動(dòng)的任務(wù)TCB,從而獲得任務(wù)的當(dāng)前堆棧棧頂指針。先將人為入棧的寄存器R4~R11出棧,將最新的堆棧棧頂指針賦值給線程堆棧指針PSP,再取消中斷掩蔽。到這里,只要發(fā)生中斷,就都能夠被響應(yīng)了。
中斷服務(wù)函數(shù)通過下面兩句匯編返回。Cortex-M3架構(gòu)中,r14的值決定了從異常返回的模式,這里r14最后四位按位或上0x0d,表示返回時(shí)從進(jìn)程堆棧中做出棧操作、返回后進(jìn)入線程模式、返回Thumb狀態(tài)。
orr r14, #0xd bx r14
執(zhí)行bx r14指令后,硬件自動(dòng)將寄存器xPSR、PC、LR、R12、R3~R0出棧,這時(shí)任務(wù)A的任務(wù)函數(shù)指針vTask_A會(huì)出棧到PC指針中,從而開始執(zhí)行任務(wù)A。
至此,任務(wù)vTask_A獲得CPU執(zhí)行權(quán),調(diào)度器正式開始工作。
以上就是FreeRTOS進(jìn)階之調(diào)度器啟動(dòng)過程分析的詳細(xì)內(nèi)容,更多關(guān)于FreeRTOS調(diào)度器啟動(dòng)過程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
FreeRTOS實(shí)時(shí)操作系統(tǒng)Cortex-M內(nèi)核使用注意事項(xiàng)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)Cortex-M內(nèi)核使用注意事項(xiàng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS動(dòng)態(tài)內(nèi)存分配管理heap_1示例
這篇文章主要為大家介紹了FreeRTOS動(dòng)態(tài)內(nèi)存分配管理heap_1的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)創(chuàng)建與任務(wù)切換
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)創(chuàng)建與任務(wù)切換,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS動(dòng)態(tài)內(nèi)存分配管理heap_5示例
這篇文章主要為大家介紹了FreeRTOS動(dòng)態(tài)內(nèi)存分配管理heap_5示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)之可視化追蹤調(diào)試
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)之可視化追蹤調(diào)試的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS進(jìn)階之系統(tǒng)延時(shí)完全解析
這篇文章主要為大家介紹了FreeRTOS進(jìn)階之系統(tǒng)延時(shí)完全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04freertos實(shí)時(shí)操作系統(tǒng)空閑任務(wù)阻塞延時(shí)示例解析
這篇文章主要為大家介紹了freertos實(shí)時(shí)操作系統(tǒng)的空閑任務(wù)及阻塞延時(shí)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的內(nèi)存管理分析
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的內(nèi)存管理的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)信號(hào)量基礎(chǔ)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)信號(hào)量基礎(chǔ),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)在Cortex-M3上的移植過程
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)在Cortex-M3上的移植過程的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04