您好,登錄后才能下訂單哦!
今天小編給大家分享一下freertos實時操作系統臨界段保護開關中斷及進入退出的方法的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
嵌套向量中斷控制器 NVIC(Nested Vectored Interrupt Controller與內核是緊耦合的。提供如下的功能:可嵌套中斷支持、向量中斷支持、動態優先級調整支持、中斷延遲大大縮短、 中斷可屏蔽。
所有的外部中斷和絕大多數系統異常均支持可嵌套中斷。異常都可以被賦予不同的優先級,當前優先級被存儲在 xPSR的專用字段中。當一個異常發生時,硬件自動比較該異常的優先級和當前的異常優先級,如果發現該異常的優先級更高,處理器就會中斷當前的中斷服務例程(或者是普通程序),而服務新來的異常(立即搶占)。
如果優先級組設置使得中斷嵌套層次很深,要確認主堆棧空間足夠用。 異常服務程序總是使用MSP,主堆棧的容量應是嵌套最深時需要的量。
CM3 支持中斷嵌套,使得高優先級異常會搶占(preempt)低優先級異常。
有3個系統異常:復位,NMI 以及硬 fault,它們有固定的優先級,并且優先級號是負數,高于所有其它異常。所有其它異常的優先級都是可編程的(但不能編程為負數)。
CM3 支持3個固定的高優先級和多達256級的可編程優先級,并且支持128級搶占。但是大多數CM3芯片實際上支持的優先級數會更少如8級、16級、32級等。
裁掉表達優先級的幾個低端有效位,從而讓優先級數減少。如果使用更多的位來表達優先級,優先級數增加,需要的門也更多,帶來更多的成本和功耗。
使用3個位來表達優先級,優先級配置寄存器的結構如下圖所示,能夠使用的8個優先級為:0x00(最高),0x20,0x40,0x60,0x80, 0xA0,0xC0,0xE0。
為了使搶占機能變得更可控,CM3 把 256 級優先級按位分成高低兩段,分別是搶占優先級和亞優先級。搶占優先級決定了搶占行為。當搶占優先級相同的異常有不止一個懸起時,就優先響應亞優先級最高的異常(亞優先級處理內務)。
優先級分組規定:亞優先級至少是1個位。所以搶占優先級最多是7個位,最多只有 128 級搶占的現象。
下圖是只使用 3 個位來表達優先級,從bit5處分組,得到4級搶占優先級,每個搶占優先級的內部有2個亞優先級。
下圖是3 位優先級,從比特1處分組,(雖然[4:0]未使用,卻允許從它們中分組)。
應用程序中斷及復位控制寄存器(AIRCR)(地址:0xE000_ED00)如下。
中斷發生時,正在處理同級或高優先級異常,或者被掩蔽,則中斷不能立即得到響應。此時中斷被懸起。
可以通過中斷設置懸起寄存器(SETPEND)、中斷懸起清除寄存器(CLRPEND)讀取中斷的懸起狀態,還可以寫它們來手工懸起中斷。
處理器在響應某異常時,如果又發生其優先級高的異常,當前異常被阻塞,轉而執行優先級高的異常。那么異常執行返回后,系統處理懸起的異常時,如果先POP再把POP出的再PUSH回去,這就是浪費CPU時間。
所以CM3不POP這些寄存器,而是繼續使用上一個異常已經PUSH好的成果。如下圖所示。
入棧的階段,尚未執行其服務例程時,如果此時收到了高優先級異常的請求,入棧后,將執行高優先級異常的服務例程。如果高優先級異常來得太晚,以至于已經執行了前一個異常的指令,則按普通的搶占處理,這會需要更多的處理器時間和額外32字節的堆棧空間。高優先級異常執行完畢后,以咬尾中斷方式執行之前被搶占的異常。
cortex-m里面開中斷、關中斷指令
臨界段:一段在執行的時候不能被中斷的代碼段(必須完整運行、不能被打斷的代碼段)。一般是對全局變量操作時候,用到臨界段。當一個任務在訪問某個全局變量時,如果被其他中斷打斷,改變了該全局變量,再回到上個任務時,全局變量已經不是當時的它了,這種情況可能會導致不可意料的后果。
臨界段被打斷的情況:系統調度(最終也是產生PendSV中斷);外部中斷。
freertos進入臨界段代碼時需要關閉中斷,處理完臨界段代碼再打開中斷。
首先看下面的代碼。
__asm void prvStartFirstTask( void ) { PRESERVE8 /* 在Cortex-M中,0xE000ED08是SCB_VTOR這個寄存器的地址, 里面存放的是向量表的起始地址,即MSP的地址 */ ldr r0, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] /* 設置主堆棧指針msp的值 */ msr msp, r0 /* 使能全局中斷 */ cpsie i cpsie f dsb isb /* 調用SVC去啟動第一個任務 */ svc 0 nop nop } __asm void vPortSVCHandler( void ) { extern pxCurrentTCB; PRESERVE8 ldr r3, =pxCurrentTCB /* 加載pxCurrentTCB的地址到r3 */ ldr r1, [r3] /* 加載pxCurrentTCB到r1 */ ldr r0, [r1] /* 加載pxCurrentTCB指向的值到r0,目前r0的值等于第一個任務堆棧的棧頂 */ ldmia r0!, {r4-r11} /* 以r0為基地址,將棧里面的內容加載到r4~r11寄存器,同時r0會遞增 */ msr psp, r0 /* 將r0的值,即任務的棧指針更新到psp */ isb mov r0, #0 /* 設置r0的值為0 */ msr basepri, r0 /* 設置basepri寄存器的值為0,即所有的中斷都沒有被屏蔽 */ orr r14, #0xd bx r14 } __asm void xPortPendSVHandler( void ) { extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 /* 當進入PendSVC Handler時,上一個任務運行的環境即: xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參) 這些CPU寄存器的值會自動保存到任務的棧中,剩下的r4~r11需要手動保存 */ /* 獲取任務棧指針到r0 */ mrs r0, psp isb ldr r3, =pxCurrentTCB /* 加載pxCurrentTCB的地址到r3 */ ldr r2, [r3] /* 加載pxCurrentTCB到r2 */ stmdb r0!, {r4-r11} /* 將CPU寄存器r4~r11的值存儲到r0指向的地址 */ str r0, [r2] /* 將任務棧的新的棧頂指針存儲到當前任務TCB的第一個成員,即棧頂指針 */ stmdb sp!, {r3, r14} /* 將R3和R14臨時壓入堆棧 */ mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 進入臨界段 */ msr basepri, r0 dsb isb bl vTaskSwitchContext /* 調用函數vTaskSwitchContext,尋找新的任務運行,通過使變量pxCurrentTCB指向新的任務來實現任務切換 */ mov r0, #0 /* 退出臨界段 */ msr basepri, r0 ldmia sp!, {r3, r14} /* 恢復r3和r14 */ ldr r1, [r3] ldr r0, [r1] /* 當前激活的任務TCB第一項保存了任務堆棧的棧頂,現在棧頂值存入R0*/ ldmia r0!, {r4-r11} /* 出棧 */ msr psp, r0 isb bx r14 nop }
cpsie i cpsie f msr basepri, r0
cpsie icpsie fmsrbasepri, r0
為了快速地開關中斷,CM3 專門設置了 CPS 指令,有 4 種用法。
CPSID I ;PRIMASK=1, ;關中斷 CPSIE I ;PRIMASK=0, ;開中斷 CPSID F ;FAULTMASK=1, ;關異常 CPSIE F ;FAULTMASK=0 ;開異常
可以看到,上面指令還是控制的PRIMASK和FAULTMASK寄存器。
如下圖所示,可以通過CPS 指令打開全局中斷或者關閉全局中斷。
basepri是中斷屏蔽寄存器,下面這個設置,優先級大于等于11的中斷都將被屏蔽。相當于關中斷進入臨界段。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 /* #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效,即等于0xb0,或者是11 */ 191轉成二進制就是11000000,高四位就是1100 */
下面這個代碼:優先級高于0的中斷被屏蔽,相當于是開中斷退出臨界段。
mov r0, #0 /* 退出臨界段 */ msr basepri, r0
關中斷和開中斷
下面這個代碼,帶返回值的意思是:往BASEPRI寫入新的值的時候,先將BASEPRI的值保存起來,更新完BASEPRI的值的時候,將之前保存好的BASEPRI的值返回,返回的值作為形參傳入開中斷函數。
不帶返回值的意思是:在往 BASEPRI 寫入新的值的時候,不用先將 BASEPRI 的值保存起來, 不用管當前的中斷狀態是怎么樣的,既然不用管當前的中斷狀態,也就意味著這樣的函數不能在中斷里面調用。
/*portmacro.h*/ /*不帶返回值的關中斷函數,不能嵌套,不能在中斷中使用*/ #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() /*不帶中斷保護的開中斷函數*/ #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) /*帶返回值的關中斷函數,可以嵌套,可以在中斷里面使用*/ #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI() /*帶中斷保護的開中斷函數*/ #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x) #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效,即等于0xb0,或者是11 */ /*不帶返回值的關中斷函數*/ static portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; /*不帶返回值的關中斷函數*/ static portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical section. */ msr basepri, ulNewBASEPRI dsb isb } } /*帶返回值的關中斷函數*/ static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ) { uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical section. */ mrs ulReturn, basepri msr basepri, ulNewBASEPRI dsb isb } return ulReturn; } /*不帶中斷保護的開中斷函數和帶中斷保護的開中斷函數,區別在于參數的值*/ static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ) { __asm { /* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */ msr basepri, ulBASEPRI } }
對于不帶中斷保護情況,vPortEnterCritical函數里面的uxCriticalNesting是一個全局變量,記錄臨界段嵌套次數,vPortExitCritical函數每次將uxCriticalNesting減一,只有當uxCriticalNesting = 0才會調用portENABLE_INTERRUPTS函數使能中斷。這樣的話,在有多個臨界段代碼的時候,不會因為某一個臨界段代碼的退出而打斷其他臨界段的保護,只有所有的臨界段代碼都退出后,才會使能中斷。
帶中斷保護的,主要就是往BASEPRI寫入新的值的時候,先將BASEPRI的值保存起來,更新完BASEPRI的值的時候,將之前保存好的BASEPRI的值返回,返回的值作為形參傳入開中斷函數。
/*進入臨界段,不帶中斷保護*/ #define taskENTER_CRITICAL() portENTER_CRITICAL() /*退出臨界段,不帶中斷保護*/ #define taskEXIT_CRITICAL() portEXIT_CRITICAL() /*進入臨界段,帶中斷保護,可以嵌套*/ #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() /*退出臨界段,帶中斷保護,可以嵌套*/ #define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) /*進入臨界段,不帶中斷保護*/ #define portENTER_CRITICAL() vPortEnterCritical() /*退出臨界段,不帶中斷保護*/ #define portEXIT_CRITICAL() vPortExitCritical() /*進入臨界段,帶中斷保護,可以嵌套*/ #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI() /*退出臨界段,帶中斷保護,可以嵌套*/ #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x) /*進入臨界段,不帶中斷保護*/ 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(); } } /*進入臨界段,帶中斷保護,可以嵌套*/ static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ) { uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical section. */ mrs ulReturn, basepri msr basepri, ulNewBASEPRI dsb isb } return ulReturn; } /*退出臨界段,帶中斷保護,可以嵌套*/ static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ) { __asm { /* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */ msr basepri, ulBASEPRI } }
/*臨界段代碼的應用場合*/ /* 在中斷場合,臨界段可以嵌套 */ { uint32_t ulReturn; /* 進入臨界段,臨界段可以嵌套 */ ulReturn = taskENTER_CRITICAL_FROM_ISR(); /* 臨界段代碼 */ /* 退出臨界段 */ taskEXIT_CRITICAL_FROM_ISR( ulReturn ); } /* 在非中斷場合,臨界段不能嵌套 */ { /* 進入臨界段 */ taskENTER_CRITICAL(); /* 臨界段代碼 */ /* 退出臨界段*/ taskEXIT_CRITICAL(); }
以上就是“freertos實時操作系統臨界段保護開關中斷及進入退出的方法”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。