freeRtos学习笔(3)临界区管理
freeRtos學(xué)習(xí)筆記
freeRtos臨界區(qū)管理
freeRtos臨界區(qū)
代碼的臨界段也稱為臨界區(qū),一旦這部分代碼開始執(zhí)行,則不允許任何中斷打斷。為確保臨界段代碼的執(zhí)行不被中斷,在進(jìn)入臨界段之前須關(guān)中斷,而臨界段代碼執(zhí)行完畢后,要立即開中斷。
FreeRTOS 的源碼中有多處臨界段的地方, 臨界段雖然保護(hù)了關(guān)鍵代碼的執(zhí)行不被打斷, 但也會影響系統(tǒng)的實時性。比如此時某個任務(wù)正在調(diào)用系統(tǒng) API 函數(shù),而且此時中斷正好關(guān)閉了,也就是進(jìn)入到了臨界區(qū)中,這個時候如果有一個緊急的中斷事件被觸發(fā),這個中斷就不能得到及時執(zhí)行,必須等到中斷開啟才可以得到執(zhí)行, 如果關(guān)中斷時間超過了緊急中斷能夠容忍的限度, 危害是可想而知的。
所以,操作系統(tǒng)的中斷在某些時候會有適當(dāng)?shù)闹袛嘌舆t,因此調(diào)用中斷屏蔽函數(shù)進(jìn)入臨界段的時候,也需快進(jìn)快出。 當(dāng)然 FreeRTOS 也能允許一些高優(yōu)先級的中斷不被屏蔽掉,能夠及時做出響應(yīng),不過這些中斷就不受系統(tǒng)管理,也不允許調(diào)用 FreeRTOS 中與中斷相關(guān)的任何 API 函數(shù)接口。
FreeRTOS 源碼中就有多處臨界段的處理, 跟 FreeRTOS 一樣, uCOS-II 和 uCOS-III 源碼中都是有臨界段的, 而 RTX操作系統(tǒng) 的源碼中不存在臨界段。 另外, 除了 FreeRTOS 操作系統(tǒng)源碼所帶的臨界段以外,用戶寫應(yīng)用的時候也有臨界段的問題,比如以下兩種:
- 讀取或者修改變量(特別是用于任務(wù)間通信的全局變量)的代碼,一般來說這是最常見的臨界代碼。
- 硬件資源或者調(diào)用公共函數(shù)的代碼,特別是不可重入的函數(shù)(函數(shù)使用了全局變量或者局部靜態(tài)變量),如果多個任務(wù)都訪問這個函數(shù),結(jié)果是可想而知的??傊?#xff0c; 對于臨界段要做到執(zhí)行時間越短越好, 否則會影響系統(tǒng)的實時性。
臨界區(qū)處理方法
進(jìn)入臨界段前操作寄存器 basepri 關(guān)閉了所有小于等于宏定義 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
所定義的中斷優(yōu)先級, 這樣臨界段代碼就不會被中斷干擾到, 而且實現(xiàn)任務(wù)切換功能的 PendSV 中斷和滴答定時器中斷是最低優(yōu)先級中斷, 所以此任務(wù)在執(zhí)行臨界段代碼期間是不會被其它高優(yōu)先級任務(wù)打斷的。
退出臨界段時重新操作 basepri 寄存器,即打開被關(guān)閉的中斷.大于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
所定義的中斷優(yōu)先級的中斷不受freertos管理,在臨界區(qū)仍然可以響應(yīng),但是中斷服務(wù)函數(shù)中不可以調(diào)用freertos的API。
如果使用單純的開關(guān)中斷,則在以下臨界區(qū)嵌套情況下,FunctionC()函數(shù)本意是要臨界保護(hù)的,但是在FunctionB()中已退出臨界區(qū),和設(shè)計本意不同。
void FunctionB() {taskENTER_CRITICAL()/* 進(jìn)入臨界區(qū) *//* 臨界段代碼 */taskEXIT_CRITICAL();/* 退出臨界區(qū) */ }void FunctionA() {taskENTER_CRITICAL(); /* 進(jìn)入臨界區(qū) */FunctionB(); /* 臨界段代碼--調(diào)用函數(shù) B */FunctionC(); /* 臨界段代碼--調(diào)用函數(shù) C */taskEXIT_CRITICAL(); /* 退出臨界區(qū) */C }因此為了避免這種情況,在taskENTER_CRITICAL()和taskEXIT_CRITICAL()函數(shù)中存在一個全局變量,記錄嵌套次數(shù),因此需要注意進(jìn)入和退出臨界區(qū)函數(shù)必須要成對出現(xiàn)。
#define taskENTER_CRITICAL() portENTER_CRITICAL() #define taskEXIT_CRITICAL() portEXIT_CRITICAL() #define portENTER_CRITICAL() vPortEnterCritical() #define portEXIT_CRITICAL() vPortExitCritical() void vPortEnterCritical( void ) {portDISABLE_INTERRUPTS();uxCriticalNesting++;if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );} } /*-----------------------------------------------------------*/ void vPortExitCritical( void ) {configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();} }一般在使用freertos時,NVIC建議配置為只有搶占優(yōu)先級,中斷是可以嵌套的,但是進(jìn)入臨界區(qū)后就關(guān)閉了freeRtos可以管理的中斷,中斷服務(wù)函數(shù)中還需要記錄臨界區(qū)嵌套次數(shù)嗎?
是需要的,如果在中斷服務(wù)函數(shù)中調(diào)用其他函數(shù),其他函數(shù)中也有可能會有臨界區(qū)保護(hù),因此一樣需要記錄臨界區(qū)嵌套次數(shù)。freertos中中斷服務(wù)函數(shù)中的API一般都是FROM_ISR結(jié)尾的,臨界區(qū)管理也是這樣的。
注意和taskENTER_CRITICAL()和taskEXIT_CRITICAL()函數(shù)中存在一個全局變量,記錄嵌套次數(shù)不同,taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus )為了提高執(zhí)行速度,這里采用了另一種方式,使用一個局部變量記錄進(jìn)入臨界區(qū)時basepri寄存器的值,在退出臨界區(qū)時,恢復(fù)寄存器basepri,從而也達(dá)到了嵌套管理的目的,因此進(jìn)入臨界區(qū)和退出臨界區(qū)函數(shù)也必須要成對出現(xiàn)。
例子1 調(diào)用不可重入函數(shù)需進(jìn)入臨界區(qū)
/*!* @brief LED閃爍任務(wù)** @param pvParame ** @return 無** @note ** @see */ void vTaskLED(void* pvParame) {while(1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);/* 進(jìn)入臨界區(qū) */taskENTER_CRITICAL();printf("LED TASK IS RUNING\r\n");/* 退出臨界區(qū) */taskEXIT_CRITICAL();vTaskDelay(50);} }/*!* @brief printf打印任務(wù)** @param pvParame ** @return 無** @note ** @see */ void vTaskPrintf(void* pvParame) {while(1){/* 進(jìn)入臨界區(qū) */taskENTER_CRITICAL();printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");/* 退出臨界區(qū) */taskEXIT_CRITICAL();vTaskDelay(100);} }/* 任務(wù)句柄 */ static TaskHandle_t ledTaskHandle; static TaskHandle_t printfTaskHandle;/*** @brief The application entry point.* @retval int*/ int main(void) {HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_CRC_Init();MX_RTC_Init();USART1->SR;/* 初始化 Event Recorder 組件 方便MDK調(diào)試 */EvrFreeRTOSSetup(1);BaseType_t err;/* 創(chuàng)建LED閃爍任務(wù) */err = xTaskCreate(vTaskLED, "LED TASK", 128, 0, 10, &ledTaskHandle);if(pdTRUE != err){}/* 創(chuàng)建printf打印任務(wù) */err = xTaskCreate(vTaskPrintf, "PRINTF TASK", 128, 0, 9, &printfTaskHandle);if(err != pdTRUE){}/* 啟動任務(wù)調(diào)度器 */vTaskStartScheduler();while (1){}}上述例子中創(chuàng)建了兩個任務(wù),LED閃爍和printf打印任務(wù),兩個任務(wù)都調(diào)用了printf函數(shù),printf函數(shù)為不可重入函數(shù),如果不對printf進(jìn)行臨界段保護(hù),則打印結(jié)果就會如下所示錯亂。
Printf task iLED TASK IS RUNING s runing LED TASK IS RUNING LED TASK IS RUNING Printf task is runing Printf task is runing Printf task is runing Printf task is runing Printf task is runing正常打印結(jié)果
LED TASK IS RUNING Printf task is runing Printf task is runing Printf task is runing Printf task is runing Printf task is runing LED TASK IS RUNING不可重入函數(shù)
不可重入函數(shù)即 使用了全局變量或局部靜態(tài)變量并對該變量進(jìn)行了寫操作的函數(shù)都為不可重入函數(shù),假設(shè)一個函數(shù)使用了局部靜態(tài)變量,任務(wù)A調(diào)用了這個函數(shù)修改了該局部靜態(tài)變量,接著任務(wù)B運行,任務(wù)B也調(diào)用了這個函數(shù)修改了該局部靜態(tài)變量,當(dāng)任務(wù)A重新運行時,該局部靜態(tài)變量的值已經(jīng)發(fā)送了變化,如果該函數(shù)需要根據(jù)該局部靜態(tài)變量的值進(jìn)行一些處理,則可能會造成任務(wù)A異常。
保護(hù)全局變量進(jìn)入臨界區(qū)
原子操作
在上一個世紀(jì),人們認(rèn)為原子是組成物質(zhì)的最小顆粒 ,在計算機領(lǐng)域引用了這個術(shù)語,原子操作即不可分割的,在執(zhí)行完畢之前不會被任何其它任務(wù)或事件中斷
在cortex-M中,32位的變量讀和寫操作都是原子操作,也就是只要一條指令就可以了。如果對全局變量都是原子操作,還進(jìn)什么臨界區(qū)啊!
讀和寫都是原子操作,但是在程序中經(jīng)常會出現(xiàn)先讀后寫的情況,兩個原子操作加一起就不是原子操作了。
上述為stm32f103 在mdk -o3 中一個變量自減操作,會編譯成4行匯編,如果第二行匯編執(zhí)行完后,進(jìn)入了中斷,在中斷中修改了這個變量,然后退出中斷后,接著執(zhí)行后兩行匯編,就會導(dǎo)致中斷中的修改無效。在裸機情況下,為了避免該情況,也應(yīng)該關(guān)中斷進(jìn)臨界區(qū)(有些小伙伴就說,自己從來都沒進(jìn)臨界區(qū),代碼不照樣跑的好好的。雖然上述情況出現(xiàn)的概率很小,但是不是不會發(fā)生的,要盡量避免)。裸機的情況下都需要進(jìn)入臨界區(qū),使用RTOS時就更需要注意了,RTOS任務(wù)間也會進(jìn)行搶占,上述情況的發(fā)生概率一般會比裸機高許多。
為什么對全局變量進(jìn)行臨界區(qū)保護(hù)?除了上面的原因,最為常見的是邏輯上,我們認(rèn)為是原子的,但是實際用代碼表達(dá)時,卻必須用多條語句變成了非原子操作。例如在任務(wù)A中逐字符接收數(shù)據(jù)并放入緩沖區(qū),緩沖區(qū)滿后對之前數(shù)據(jù)進(jìn)行覆蓋;在任務(wù)B中檢查接收緩沖區(qū)滿后將數(shù)據(jù)全部發(fā)送出去。任務(wù)B將全部數(shù)據(jù)發(fā)送出去在邏輯上就是一個原子操作,但是在代碼中卻需要很多條語句,如果不進(jìn)入臨界區(qū),發(fā)送一半時,緩沖區(qū)又通過任務(wù)A接收到了很多數(shù)據(jù),覆蓋了老數(shù)據(jù),就會造成任務(wù)B發(fā)送了一半老數(shù)據(jù)又發(fā)送了一半新數(shù)據(jù)。
《安富萊 STM32-V6 開發(fā)板 FreeRTOS 教程》
本文參考 freertos官方文檔 https://freertos.org/a00110.html
總結(jié)
以上是生活随笔為你收集整理的freeRtos学习笔(3)临界区管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言对齐方式研究笔记
- 下一篇: freeRtos学习笔(4)消息队列