網站首頁 編程語言 正文
FreeRTOS基本程序架構
int main(void) { 必要的初始化工作; 創建任務1; 創建任務2; ... vTaskStartScheduler(); /*啟動調度器*/ while(1); }
任務創建完成后,靜態變量指針pxCurrentTCB(見《FreeRTOS進階之任務創建完全解析》第7節內容)指向優先級最高的就緒任務。但此時任務并不能運行,因為接下來還有關鍵的一步:啟動FreeRTOS調度器。
啟動FreeRTOS調度器
調度器是FreeRTOS操作系統的核心,主要負責任務切換,即找出最高優先級的就緒任務,并使之獲得CPU運行權。調度器并非自動運行的,需要人為啟動它。
API函數vTaskStartScheduler()用于啟動調度器,它會創建一個空閑任務、初始化一些靜態變量,最主要的,它會初始化系統節拍定時器并設置好相應的中斷,然后啟動第一個任務。這篇文章用于分析啟動調度器的過程,和上一篇文章一樣,啟動調度器也涉及到硬件特性(比如系統節拍定時器初始化等),因此本文仍然以Cortex-M3架構為例。
啟動調度器的API函數vTaskStartScheduler()的源碼精簡后如下所示:
void vTaskStartScheduler( void ) { BaseType_t xReturn; StaticTask_t *pxIdleTaskTCBBuffer= NULL; StackType_t *pxIdleTaskStackBuffer= NULL; uint16_t usIdleTaskStackSize =tskIDLE_STACK_SIZE; /*如果使用靜態內存分配任務堆棧和任務TCB,則需要為空閑任務預先定義好任務內存和任務TCB空間*/ #if(configSUPPORT_STATIC_ALLOCATION == 1 ) { vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &usIdleTaskStackSize); } #endif /*configSUPPORT_STATIC_ALLOCATION */ /* 創建空閑任務,使用最低優先級*/ xReturn =xTaskGenericCreate( prvIdleTask, "IDLE",usIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT), &xIdleTaskHandle,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer, NULL ); if( xReturn == pdPASS ) { /* 先關閉中斷,確保節拍定時器中斷不會在調用xPortStartScheduler()時或之前發生.當第一個任務啟動時,會重新啟動中斷*/ portDISABLE_INTERRUPTS(); /* 初始化靜態變量 */ xNextTaskUnblockTime = portMAX_DELAY; xSchedulerRunning = pdTRUE; xTickCount = ( TickType_t ) 0U; /* 如果宏configGENERATE_RUN_TIME_STATS被定義,表示使用運行時間統計功能,則下面這個宏必須被定義,用于初始化一個基礎定時器/計數器.*/ portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); /* 設置系統節拍定時器,這與硬件特性相關,因此被放在了移植層.*/ if(xPortStartScheduler() != pdFALSE ) { /* 如果調度器正確運行,則不會執行到這里,函數也不會返回*/ } else { /* 僅當任務調用API函數xTaskEndScheduler()后,會執行到這里.*/ } } else { /* 執行到這里表示內核沒有啟動,可能因為堆??臻g不夠 */ configASSERT( xReturn ); } /* 預防編譯器警告*/ ( void ) xIdleTaskHandle; }
這個API函數首先創建一個空閑任務,空閑任務使用最低優先級(0級),空閑任務的任務句柄存放在靜態變量xIdleTaskHandle中,可以調用API函數xTaskGetIdleTaskHandle()獲得空閑任務句柄。
Cortex-M3中斷優先級
如果任務創建成功,則關閉中斷(調度器啟動結束時會再次使能中斷的),初始化一些靜態變量,然后調用函數xPortStartScheduler()來啟動系統節拍定時器并啟動第一個任務。因為設置系統節拍定時器涉及到硬件特性,因此函數xPortStartScheduler()由移植層提供,不同的硬件架構,這個函數的代碼也不相同。
對于Cortex-M3架構,函數xPortStartScheduler()的實現如下所示:
BaseType_t xPortStartScheduler( void ) { #if(configASSERT_DEFINED == 1 ) { volatile uint32_tulOriginalPriority; /* 中斷優先級寄存器0:IPR0 */ volatile uint8_t * constpucFirstUserPriorityRegister = ( uint8_t * ) (portNVIC_IP_REGISTERS_OFFSET_16 +portFIRST_USER_INTERRUPT_NUMBER ); volatile uint8_tucMaxPriorityValue; /* 這一大段代碼用來確定一個最高ISR優先級,在這個ISR或者更低優先級的ISR中可以安全的調用以FromISR結尾的API函數.*/ /* 保存中斷優先級值,因為下面要覆寫這個寄存器(IPR0) */ ulOriginalPriority = *pucFirstUserPriorityRegister; /* 確定有效的優先級位個數. 首先向所有位寫1,然后再讀出來,由于無效的優先級位讀出為0,然后數一數有多少個1,就能知道有多少位優先級.*/ *pucFirstUserPriorityRegister= portMAX_8_BIT_VALUE; ucMaxPriorityValue = *pucFirstUserPriorityRegister; /* 冗余代碼,用來防止用戶不正確的設置RTOS可屏蔽中斷優先級值 */ ucMaxSysCallPriority =configMAX_SYSCALL_INTERRUPT_PRIORITY &ucMaxPriorityValue; /* 計算最大優先級組值 */ 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寄存器的值復原*/ *pucFirstUserPriorityRegister= ulOriginalPriority; } #endif /*conifgASSERT_DEFINED */ /* 將PendSV和SysTick中斷設置為最低優先級*/ portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI; /* 啟動系統節拍定時器,即SysTick定時器,初始化中斷周期并使能定時器*/ vPortSetupTimerInterrupt(); /* 初始化臨界區嵌套計數器 */ uxCriticalNesting = 0; /* 啟動第一個任務 */ prvStartFirstTask(); /* 永遠不會到這里! */ return 0; }
從源碼中可以看到,開始的一大段都是冗余代碼。因為Cortex-M3的中斷優先級有些違反直覺:Cortex-M3中斷優先級數值越大,表示優先級越低。而FreeRTOS的任務優先級則與之相反:優先級數值越大的任務,優先級越高。
根據官方統計,在Cortex-M3硬件上使用FreeRTOS,絕大多數的問題都出在優先級設置不正確上。
因此,為了使FreeRTOS更健壯,FreeRTOS的作者在編寫Cortex-M3架構移植層代碼時,特意增加了冗余代碼。關于詳細的Cortex-M3架構中斷優先級設置,參考《Cortex-M內核使用注意事項》一文。
在Cortex-M3架構中,FreeRTOS為了任務啟動和任務切換使用了三個異常:SVC、PendSV和SysTick。
SVC(系統服務調用)用于任務啟動,有些操作系統不允許應用程序直接訪問硬件,而是通過提供一些系統服務函數,通過SVC來調用;
PendSV(可掛起系統調用)用于完成任務切換,它的最大特性是如果當前有優先級比它高的中斷在運行,PendSV會推遲執行,直到高優先級中斷執行完畢;
SysTick用于產生系統節拍時鐘,提供一個時間片,如果多個任務共享同一個優先級,則每次SysTick中斷,下一個任務將獲得一個時間片。關于詳細的SVC、PendSV異常描述,推薦《Cortex-M3權威指南》一書的“異?!辈糠帧?/p>
這里將PendSV和SysTick異常優先級設置為最低,這樣任務切換不會打斷某個中斷服務程序,中斷服務程序也不會被延遲,這樣簡化了設計,有利于系統穩定。
接下來調用函數vPortSetupTimerInterrupt()設置SysTick定時器中斷周期并使能定時器運行這個函數比較簡單,就是設置SysTick硬件的相應寄存器。
再接下來有一個關鍵的函數是prvStartFirstTask(),這個函數用來啟動第一個任務。我們先看一下源碼:
__asm void prvStartFirstTask( void ) { PRESERVE8 /* Cortext-M3硬件中,0xE000ED08地址處為VTOR(向量表偏移量)寄存器,存儲向量表起始地址*/ ldr r0, =0xE000ED08 ldr r0, [r0] /* 取出向量表中的第一項,向量表第一項存儲主堆棧指針MSP的初始值*/ ldr r0, [r0] /* 將堆棧地址存入主堆棧指針 */ msr msp, r0 /* 使能全局中斷*/ cpsie i cpsie f dsb isb /* 調用SVC啟動第一個任務 */ svc 0 nop nop }
程序開始的幾行代碼用來復位主堆棧指針MSP的值,表示從此以后MSP指針被FreeRTOS接管,需要注意的是,Cortex-M3硬件的中斷也使用MSP指針。之后使能中斷,使用匯編指令svc 0觸發SVC中斷,完成啟動第一個任務的工作。
SVC中斷服務函數
__asm void vPortSVCHandler( void ) { PRESERVE8 ldr r3, =pxCurrentTCB /* pxCurrentTCB指向處于最高優先級的就緒任務TCB */ ldr r1, [r3] /* 獲取任務TCB地址 */ ldr r0, [r1] /* 獲取任務TCB的第一個成員,即當前堆棧棧頂pxTopOfStack */ ldmia r0!, {r4-r11} /* 出棧,將寄存器r4~r11出棧 */ msr psp, r0 /* 最新的棧頂指針賦給線程堆棧指針PSP */ isb mov r0, #0 msr basepri, r0 orrr14, #0xd /* 這里0x0d表示:返回后進入線程模式,從進程堆棧中做出棧操作,返回Thumb狀態*/ bx r14 }
通過上一篇介紹任務創建的文章,我們已經認識了指針pxCurrentTCB。這是定義在tasks.c中的唯一一個全局變量,指向處于最高優先級的就緒任務TCB。我們知道FreeRTOS的核心功能是確保處于最高優先級的就緒任務獲得CPU權限,因此可以說這個指針指向的任務要么正在運行中,要么即將運行(調度器關閉),所以這個變量才被命名為pxCurrentTCB。
根據《FreeRTOS進階之任務創建》第三節我們可以知道,一個任務創建時,會將它的任務堆棧初始化的像是經過一次任務切換一樣,如圖1-1所示。對于Cortex-M3架構,需要依次入棧xPSR、PC、LR、R12、R3~R0、R11~R4,其中r11~R4需要人為入棧,其它寄存器由硬件自動入棧。寄存器PC被初始化為任務函數指針vTask_A,這樣當某次任務切換后,任務A獲得CPU控制權,任務函數vTask_A被出棧到PC寄存器,之后會執行任務A的代碼;LR寄存器初始化為函數指針prvTaskExitError,這是由移植層提供的一個出錯處理函數。
任務TCB結構體成員pxTopOfStack表示當前堆棧的棧頂,它指向最后一個入棧的項目,所以在圖中它指向R4,TCB結構體另外一個成員pxStack表示堆棧的起始位置,所以在圖中它指向堆棧的最開始處。
圖1-1:任務創建后任務堆棧分布情況
所以,SVC中斷服務函數一開始就使用全局指針pxCurrentTCB獲得第一個要啟動的任務TCB,從而獲得任務的當前堆棧棧頂指針。先將人為入棧的寄存器R4~R11出棧,將最新的堆棧棧頂指針賦值給線程堆棧指針PSP,再取消中斷掩蔽。到這里,只要發生中斷,就都能夠被響應了。
中斷服務函數通過下面兩句匯編返回。Cortex-M3架構中,r14的值決定了從異常返回的模式,這里r14最后四位按位或上0x0d,表示返回時從進程堆棧中做出棧操作、返回后進入線程模式、返回Thumb狀態。
orr r14, #0xd bx r14
執行bx r14指令后,硬件自動將寄存器xPSR、PC、LR、R12、R3~R0出棧,這時任務A的任務函數指針vTask_A會出棧到PC指針中,從而開始執行任務A。
至此,任務vTask_A獲得CPU執行權,調度器正式開始工作。
原文鏈接:https://freertos.blog.csdn.net/article/details/51331638
相關推薦
- 2022-01-16 jQuery 核心函數css和平滑滾動頂部
- 2022-06-18 C#使用LOCK實現線程同步_C#教程
- 2022-09-05 Involution: Inverting the Inherence of Convolution
- 2023-01-29 Python??序列化反序列化和異常處理的問題小結_python
- 2022-04-01 報錯處理-bash: fork: Cannot allocate memory
- 2022-07-13 Springboot 項目中使用 @RestControllerAdvice 注解不生效
- 2023-04-21 如何在C#項目中鏈接一個文件夾下的所有文件詳解_C#教程
- 2022-07-07 C#多線程之線程鎖_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支