什么是臨界段
代碼的臨界段也稱為臨界區,指處理時不可分割的代碼區域,一旦這部分代碼開始執行,則不允許任何中斷打斷。為確保臨界段代碼的執行不被中斷,在進入臨界段之前須關中斷,而臨界段代碼執行完畢后,要立即打開中斷。
臨界段的作用
其實在RTOS中,使用最多的臨界段是OS本身的調用,但是我們用戶也是需要對臨界資源進行保護的(臨界資源是一次僅允許一個線程使用的共享資源),特別是一些全局變量,當線程正在使用的時候不希望有人來打斷我的操作,就行很多時候我們寫代碼時,需要集中精力,不希望別人打斷我們的思路一樣。這樣子使得系統的運行更加穩定健壯。
什么時候會打斷代碼的執行?
顧名思義,代碼正在正常運行的時候,基本不會被打斷,能被打斷的都是系統發生了異常(中斷也是異常),在OS中,除了外部中斷能將正在運行的代碼打斷,還有線程的調度——PendSV,系統產生PendSV中斷,在PendSV Handler里面實現線程的切換。我們要將這項東西屏蔽掉,保證當前只有一個線程在使用臨界資源。
如何關閉中斷?
其實,在我們常用的MCU中,一般為Cortex-M內核的,M內核是有一些指令能快速關閉中斷,一起來看看Cortex-M權威指南吧(以Cortex-M3為例)。
簡單來說,快速屏蔽中斷就是處理這些內核寄存器,在Cortex-M中有相應的操作指令,一般我們無需關注,因為OS已經給我們寫好了這些底層的東西。不過如果你是想自己寫一個OS的話,可以了解一下,要訪問PRIMASK, FAULTMASK以及BASEPRI,同樣要使用MRS/MSR指令,如:
1MRSR0,BASEPRI;讀取BASEPRI到R0中2MRSR0,FAULTMASK;似上3MRSR0,PRIMASK;似上4MSRBASEPRI,R0;寫入R0到BASEPRI中5MSRFAULTMASK,R0;似上6MSRPRIMASK,R0;似上
只有在特權級下,才允許訪問這3個寄存器。
其實,為了快速地開關中斷,CM3還專門設置了一條CPS指令,有4種用法:
1CPSIDI;PRIMASK=1,;關中斷2CPSIEI;PRIMASK=0,;開中斷3CPSIDF;FAULTMASK=1,;關異常4CPSIEF;FAULTMASK=0;開異常
上面的代碼中的PRIMASK和FAULTMAST是Cortex-M內核 里面三個中斷屏蔽寄存器中的兩個,還有一個是BASEPRI,這些寄存器都用于屏蔽中斷。具體的作用見表格(表格出自《【野火】RT-Thread內核實現與應用開發實戰指南》)
不同OS的處理臨界段的區別
FreeRTOS對中斷的開和關是通過操作BASEPRI寄存器來實現的,即大于等于BASEPRI的值的中斷會被屏蔽,小于BASEPRI的值的中斷則不會被屏蔽。這樣子的好處就是用戶可以設置BASEPRI的值來選擇性的給一些非常緊急的中斷留一條后路。比如飛控的防撞處理。代碼在portmacro.h 中實現:
屏蔽中斷:
1staticportFORCE_INLINEvoidvPortRaiseBASEPRI(void) 2{ 3uint32_tulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY; 4 5__asm 6{ 7msrbasepri,ulNewBASEPRI 8dsb 9isb10}11}
打開中斷:
1staticportFORCE_INLINEvoidvPortSetBASEPRI(uint32_tulBASEPRI)2{3__asm4{5msrbasepri,ulBASEPRI6}7}
RT-Thread:
與FreeRTOS不同的是,RT-Thread對臨界段的保護處理的很干脆,不管三七二十一直接把中斷全部關了(直接操作PRIMASK內核寄存器), 只有NMI FAULT和硬FAULT能被相應。 這種方法簡單粗暴,是很不錯的選擇。一般我們臨界段的處理時間是比較短的,關了再開其實并沒有太大的影響。
現在要看看RT-Thread的關中斷的代碼實現:
1rt_hw_interrupt_disablePROC2EXPORTrt_hw_interrupt_disable3MRSr0,PRIMASK4CPSIDI5BXLR6ENDP
開中斷:
1rt_hw_interrupt_enablePROC2EXPORTrt_hw_interrupt_enable3MSRPRIMASK,r04BXLR5ENDP
這短短的幾句代碼其實還是很有意思的,我就引用火哥的話來解釋一下這些處理操作(我個人是不會匯編的,但是跟著書來解讀這些代碼還是很輕而易舉的)
可能有人懂匯編的話,就會看出來,關中斷,不就是直接使用CPSID I指令就行了嘛~開中斷,不就是使用CPSIE I指令就行了嘛,為啥跟我等凡人想的不一樣?
RT-Thread的處理好像是多此一舉了,實則不然,“所有東西的存在必然有其存在的意義”這句話應該沒人反駁吧~~因為RT-Thread要防止用戶錯誤地退出了中斷臨界段,因為這樣子可能會產生巨大的危害,所以RT-Thread將當前的PRIMASK的狀態保存起來,這樣子就必須要關多少次中斷就得開多少次中斷。
怎么說呢,用例子來證明吧:
1/*臨界段1開始*/ 2rt_hw_interrupt_disable();/*關中斷,PRIMASK=1*/ 3{ 4/*臨界段2*/ 5rt_hw_interrupt_disable();/*關中斷,PRIMASK=1*/ 6{ 7} 8rt_hw_interrupt_enable();/*開中斷,PRIMASK=0*/(注意) 9}10/*臨界段1結束*/11rt_hw_interrupt_enable();/*開中斷,PRIMASK=0*/
如果直接操作PRIMASK,而不保存PRIMASK的狀態,這樣子當臨界段2結束后調用一次打開中斷,那么連臨界段1的后半部分就無效了。而RT-Thread的實現就能很好避免這種問題,也用代碼來說明吧:
1/*臨界段1開始*/ 2level1=rt_hw_interrupt_disable();/*關中斷,level1=0,PRIMASK=1*/ 3{ 4/*臨界段2*/ 5level2=rt_hw_interrupt_disable();/*關中斷,level2=1,PRIMASK=1*/ 6{ 7} 8rt_hw_interrupt_enable(level2);/*開中斷,level2=1,PRIMASK=1*/ 9}10/*臨界段1結束*/11rt_hw_interrupt_enable(level1);/*開中斷,level1=0,PRIMASK=0*/
這樣子就完全避免了對吧!
有人又會問了,FreeRTOS的臨界段能允許嵌套嗎,答案是肯定的,FreeRTOS中早已給我們想好調用的函數了,并且全部使用宏定義實現了:
1#defineportDISABLE_INTERRUPTS()vPortRaiseBASEPRI()2#defineportENABLE_INTERRUPTS()vPortSetBASEPRI(0)3#defineportENTER_CRITICAL()vPortEnterCritical()4#defineportEXIT_CRITICAL()vPortExitCritical()5#defineportSET_INTERRUPT_MASK_FROM_ISR()ulPortRaiseBASEPRI()6#defineportCLEAR_INTERRUPT_MASK_FROM_ISR(x)vPortSetBASEPRI(x)
其實原理都是差不多的,通過保存和恢復寄存器basepri的數值就可以實現嵌套使用。
1UBaseType_tuxSavedInterruptStatus; 2 3uxSavedInterruptStatus=portSET_INTERRUPT_MASK_FROM_ISR(); 4{ 5uxSavedInterruptStatus=portSET_INTERRUPT_MASK_FROM_ISR(); 6{ 7//臨界區代碼 8} 9portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);10}11portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
進入中斷源碼的實現:
1staticportFORCE_INLINEuint32_tulPortRaiseBASEPRI(void) 2{ 3uint32_tulReturn,ulNewBASEPRI=configMAX_SYSCALL_INTERRUPT_PRIORITY; 4 5__asm 6{ 7mrsulReturn,basepri 8msrbasepri,ulNewBASEPRI 9dsb10isb11}12returnulReturn;13}
退出中斷源碼實現:(跟前面的函數一樣)
1staticportFORCE_INLINEvoidvPortSetBASEPRI(uint32_tulBASEPRI)2{3__asm4{5msrbasepri,ulBASEPRI6}7}
總結
對于時間關鍵的任務而言,恰如其分地使用PRIMASK和BASEPRI來暫時關閉一些中斷是非常重要的。
FreeRTOS源碼中就有多處臨界段的處理,除了FreeRTOS操作系統源碼所帶的臨界段以外,用戶寫應用的時候也有臨界段的問題,比如以下兩種:
讀取或者修改變量(特別是用于任務間通信的全局變量)的代碼,一般來說這是最常見的臨界代碼。
調用公共函數的代碼,特別是不可重入的函數,如果多個任務都訪問這個函數,結果是可想而知的。
總之,對于臨界段要做到執行時間越短越好,否則會影響系統的實時性。
那假如我有一個線程,處理的時間較長,但是我又不想被其他線程打斷,關中斷可能影響系統的正常運行,怎么辦呢?其實很簡單,在OS中一般可以直接掛起調度器,系統正常運行,但是不會切換線程,當我處理完再把調度器解除即可。
-
中斷
+關注
關注
5文章
898瀏覽量
41497 -
RTOS
+關注
關注
22文章
813瀏覽量
119636 -
代碼
+關注
關注
30文章
4788瀏覽量
68612
原文標題:RTOS臨界段知識詳解
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
【安富萊】【RTX操作系統教程】第11章 臨界段,任務鎖和中斷鎖
轉:第15章 FreeRTOS臨界段和開關中斷
第11章 臨界段,任務鎖和中斷鎖
FreeRTOS關于臨界段的疑問如何解答
怎樣去使用FreeRTOS的中斷配置和臨界段呢
FreeRTOS學習筆記--臨界段代碼處關閉中斷
![FreeRTOS學習筆記--<b class='flag-5'>臨界</b><b class='flag-5'>段</b>代碼處關閉中斷](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
FreeRTOS臨界段
![FreeRTOS<b class='flag-5'>臨界</b><b class='flag-5'>段</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
STM32之FreeRTOS:(一) 中斷配置和臨界段的使用
![STM32之FreeRTOS:(一) 中斷配置和<b class='flag-5'>臨界</b><b class='flag-5'>段</b>的使用](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論