去年寫過一篇介紹 uCOS-II 在Cortex-M3平台移植的文章:
http://blog.csdn.net/liyuanbhu/article/details/9082767
最近閑下來,研究了一下 FreeRTOS 官方的Cortex-M3平台的移植代碼,很有收穫,發現了幾處比 uCOS-II 移植代碼寫的好的地方。這裡簡單總結一下,算是給自己做個備忘。
對臨界區的處理
uCOS-II 移植代碼中對臨界區的處理是這樣的。
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();} #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);} __asm OS_CPU_SR OS_CPU_SR_Save(void) { MRS R0, PRIMASK ; Set prio int mask to mask all (except faults) CPSID I BX LR } __asm void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr) { MSR PRIMASK, R0 BX LR }
這種處理沒有什麼錯,但是處理的有點簡單。實際上 Cortex M3 核心支援對優先順序分級,最多可以分255級。像STM32系列的Cortex M3 核心單片機支援16個優先順序。uCOS-II 移植代碼中這樣已進入臨界區就將所有的中斷都給屏蔽掉會導致有些很緊急的事情無法及時處理。FreeRTOS移植代碼處理的就聰明的多。FreeRTOS移植代碼要求受作業系統管理的中斷使用較低優先順序,不受作業系統管理的中斷可以使用任意優先順序。臨界區中只屏蔽掉可以產生任務切換的中斷,也就是較低優先順序的中斷,而高優先順序不受影響。這樣可以提高對重要中斷的響應速度。
FreeRTOS 中定義了一個宏 configMAX_SYSCALL_INTERRUPT_PRIORITY
所有用到作業系統功能的中斷優先順序數字都要大於這個宏定義的值。(也就是優先順序比它低,Cortex M3中低優先順序對應的數字大)
進入臨界區只是屏蔽比 configMAX_SYSCALL_INTERRUPT_PRIORITY優先順序低的中斷。對應的代碼如下。
#define portENTER_CRITICAL()vPortEnterCritical()#define portEXIT_CRITICAL()vPortExitCritical()#define portDISABLE_INTERRUPTS()ulPortSetInterruptMask()#define portENABLE_INTERRUPTS()vPortClearInterruptMask( 0 )void vPortEnterCritical( void ){portDISABLE_INTERRUPTS();uxCriticalNesting++;__dsb( portSY_FULL_READ_WRITE );__isb( portSY_FULL_READ_WRITE );}void vPortExitCritical( void ){uxCriticalNesting--;if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();}}__asm unsigned long ulPortSetInterruptMask( void ){PRESERVE8mrs r0, baseprimov r1, #configMAX_SYSCALL_INTERRUPT_PRIORITYmsr basepri, r1bx r14}__asm void vPortClearInterruptMask( unsigned long ulNewMask ){PRESERVE8msr basepri, r0bx r14}
這裡可能有些人會覺得代碼有問題。退出臨界區時應該恢複進入臨界區前的狀態。如果進入臨界區之前就屏蔽了一小部分優先順序很低的中斷,那麼退出臨界區後這一小部分中斷還是應該被屏蔽的。FreeRTOS 網站對此的解釋如下:“Many bug reports are received that claim BASEPRI should be returned to its original value on exit, and not just set to zero, but the Cortex-M NVIC will never accept an interrupt that has a priority below that of the currently executing interrupt - no matter what BASEPRI is set to. An implementation that always sets BASEPRI to zero will result in faster code execution than an implementation that stores, then restores, the BASEPRI value (when the compiler's optimiser is turned on). ”
說的有些道理,其實將PRIMASK的值儲存下來也增加不了多少負擔。因此,我覺得可以這麼寫:
__asm OS_CPU_SR OS_CPU_SR_Save(void) { MRS R0, PRIMASK ; MOV R1, #configMAX_SYSCALL_INTERRUPT_PRIORITY MSR BASEPRI, R1 BX LR } __asm void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr) { MSR PRIMASK, R0 BX LR }
再多說一句,如果使用的是STM32系列單片機,在啟動核心之前,應該如下設定:
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
保證所有的優先順序都是搶佔式優先順序。
任務切換
uCOS-II 移植代碼中任務切換的代碼(虛擬碼)如下。
OS_CPU_PendSVHandler { if (PSP != NULL) { Save R4-R11 onto task stack; OSTCBCur->OSTCBStkPtr = SP; } OSTaskSwHook(); OSPrioCur = OSPrioHighRdy; OSTCBCur = OSTCBHighRdy; PSP = OSTCBHighRdy->OSTCBStkPtr; Restore R4-R11 from new task stack; Return from exception; }
代碼中需要根據PSP 的值來判斷是否是從 OSStartHighRdy() 函數過來的。因為OSStartHighRdy()的最後無法類比一次中斷返回,所以用了這麼個法子。FreeRTOS 移植代碼就聰明的多。反正 Cortex M3 系列支援的中斷很多,根本用不完,浪費點也無所謂。OSStartHighRdy() 的最後不觸發 PendSV 中斷,換個其他不用的中斷用不就可以了。FreeRTOS 移植代碼用的是 SVC 中斷。
下面是採用這種方法重寫的OSStartHighRdy() 函數,其中注釋掉的幾行是原始的uCOS-II 移植代碼採用的方法。
OSStartHighRdy LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0]; MOVS R0, #0 ; Set the PSP to 0 for initial context switch call;MSR PSP, R0 LDR R0, =OSRunning ; OSRunning = TRUE MOVS R1, #1 STRB R1, [R0]SVC 0x00; LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch); LDR R1, =NVIC_PENDSVSET; STR R1, [R0] CPSIE I ; Enable interrupts at processor levelOSStartHang B OSStartHang ; Should never get here
這樣的話,任務切換就分配到了兩個中斷中,好像有點浪費,不夠SVC 中斷不用更是浪費。
PendSV_Handler CPSID I ; Prevent interruption during context switch MRS R0, PSP ; PSP is process stack pointer; CBZ R0, PendSVHandler_nosave ; Skip register save the first time SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack STM R0, {R4-R11} LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP; LDR R1, [R1] STR R0, [R1] ; R0 is SP of process being switched out ; At this point, entire context of process has been savedPendSVHandler_nosave PUSH {R14} ; Save LR exc_return value LDR R0, =OSTaskSwHook ; OSTaskSwHook(); BLX R0 POP {R14} LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy; LDR R1, =OSPrioHighRdy LDRB R2, [R1] STRB R2, [R0] LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy; LDR R1, =OSTCBHighRdy LDR R2, [R1] STR R2, [R0] LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr; LDM R0, {R4-R11} ; Restore r4-11 from new process stack ADDS R0, R0, #0x20 MSR PSP, R0 ; Load PSP with new process SP ORR LR, LR, #0x04 ; Ensure exception return uses process stack CPSIE I BX LR ; Exception return will restore remaining contextSVC_HandlerCPSID I ; Prevent interruption during context switch LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy; LDR R1, =OSPrioHighRdy LDRB R2, [R1] STRB R2, [R0] LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy; LDR R1, =OSTCBHighRdy LDR R2, [R1] STR R2, [R0] LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr; LDM R0, {R4-R11} ; Restore r4-11 from new process stack ADDS R0, R0, #0x20 MSR PSP, R0 ; Load PSP with new process SP ORR LR, LR, #0x04 ; Ensure exception return uses process stack CPSIE I BX LR ; Exception return will restore remaining context
這樣的話,任務切換中斷中少了一個條件跳躍陳述式,可以節省幾個刻度,代碼也稍微簡單一些,這樣做還是值得的。
加上這些改進,這個移植代碼應該就比較完美了。