1. 程式人生 > 其它 >ucos(五)臨界區

ucos(五)臨界區

一、概述

程式碼的臨界段也稱為臨界區,指處理時不可分割的程式碼。一旦這部分程式碼開始執行,則不允許執行被打斷。 大多數系統為確保臨界段程式碼的執行不被中斷,在進入臨界段之前必須關中斷,而臨界段程式碼執行完後,要立即開中斷。常見案例為喂狗、FLASH的寫入、獲取當前時鐘節拍計數器等保護操作。 在UCOSIII中存在大量的臨界區程式碼,分以下情況:
  • 情況1:中斷處理程式和任務都會訪問的臨界段程式碼,需要用關中斷的方法加以保護。一旦使用關中斷的方法,臨界區程式碼必須快速完成,否則會導致中斷延遲影響嚴重,例如串列埠資料丟包現象。
  • 情況2:僅由任務訪問的臨界區程式碼,可以通過給排程器上鎖的方法來保護。一旦使用排程器上鎖,核心會禁止使用者進行阻塞型呼叫。如果某些應用能夠進行阻塞呼叫,則該應用很可能已經崩潰了。
在os.h標頭檔案有以下函式:
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u                             /* Deferred ISR Posts ------------------------------ */
                                                                 /* Lock the scheduler                                */
#define  OS_CRITICAL_ENTER()                                       \
         do {                                                      \
             CPU_CRITICAL_ENTER();                                 \
             OSSchedLockNestingCtr++;                              \
             if (OSSchedLockNestingCtr == 1u) {                    \
                 OS_SCHED_LOCK_TIME_MEAS_START();                  \
             }                                                     \
             CPU_CRITICAL_EXIT();                                  \
         } while (0)
         
  #define  OS_CRITICAL_EXIT()                                        \
         do {                                                      \
             CPU_CRITICAL_ENTER();                                 \
             OSSchedLockNestingCtr--;                              \
             if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) {     \
                 OS_SCHED_LOCK_TIME_MEAS_STOP();                   \
                 if (OSIntQNbrEntries > (OS_OBJ_QTY)0) {           \
                     CPU_CRITICAL_EXIT();                          \
                     OS_Sched0();                                  \
                 } else {                                          \
                     CPU_CRITICAL_EXIT();                          \
                 }                                                 \
             } else {                                              \
                 CPU_CRITICAL_EXIT();                              \
             }                                                     \
         } while (0)       
#else         
#define  OS_CRITICAL_ENTER()                    CPU_CRITICAL_ENTER()
#define  OS_CRITICAL_EXIT()                     CPU_CRITICAL_EXIT()   
#endif      


#define  CPU_CRITICAL_ENTER()  do { CPU_INT_DIS(); } while (0)          /* Disable   interrupts.                        */
#define  CPU_CRITICAL_EXIT()   do { CPU_INT_EN();  } while (0)          /* Re-enable interrupts.                        */

#define  CPU_INT_DIS()         do { cpu_sr = CPU_SR_Save(); } while (0) /* Save    CPU status word & disable interrupts.*/
#define  CPU_INT_EN()          do { CPU_SR_Restore(cpu_sr); } while (0) /* Restore CPU status word.                     */
上述程式碼:OS_CFG_ISR_POST_DEFERRED_EN該巨集定義決定進入臨界區時的操作
  • OS_CFG_ISR_POST_DEFERRED_EN的值為0:要遮蔽除NMI和fault以外的所有異常和中斷
  • OS_CFG_ISR_POST_DEFERRED_EN的值為1:是否要對排程器上鎖,預設值為1。這種在情況2特別合適,特別注意的是,若排程器上鎖,禁止在臨界區呼叫各種引起任務排程的函式,如睡眠延時、阻塞等待訊號量/互斥型訊號量/事件標誌組/訊息佇列/多個核心物件等,否則引起核心執行異常。
注: OS_CFG_ISR_POST_DEFERRED_EN使能的延遲中斷提交已經不被推薦使用了。在uC/OS-III v3.06.00釋出的更新記錄中,已經將延遲中斷提交列為了過時的方法並且去除了,推測可能是出於實時性和穩定性的考慮。
Deferred interrupt processing is now obsolete and has been removed。

二、如何使用

情況1:若該臨界區不希望受到異常與中斷的影響,直接使用CPU_CRITICAL_ENTER與CPU_CRITICAL_EXIT函式,步驟如下:
CPU_SR_ALLOC();

CPU_CRITICAL_ENTER();//遮蔽除NMI和fault以外的所有異常和中斷,即將進入臨界區

臨界區程式碼,該程式碼涉及中斷訪問

CPU_CRITICAL_EXIT() ;//開啟除NMI和fault以外的所有異常和中斷,退出臨界區

 

情況2:OS_CFG_ISR_POST_DEFERRED_EN為1,若臨界區即使被中斷打斷,也不會影響結果,只是用於任務之間的互斥訪問,步驟如下:
CPU_SR_ALLOC();

OS_CRITICAL_ENTER();//鎖定排程器,即將進入臨界區

臨界區程式碼,該程式碼不涉及中斷訪問

OS_CRITICAL_EXIT() ;//解鎖排程器,退出臨界區
使用示例:獲取當前時鐘節拍計數器
OS_TICK  OSTimeGet (OS_ERR  *p_err)
{
    OS_TICK  ticks;
    CPU_SR_ALLOC();
    :
    :     
    CPU_CRITICAL_ENTER();
    ticks = OSTickCtr;
    CPU_CRITICAL_EXIT();
   *p_err = OS_ERR_NONE;
    return (ticks);
}

三、關所有異常和中斷

1.遮蔽除NMI和fault以外的所有異常和中斷,最後會呼叫以下程式碼:
#define  CPU_INT_DIS()         do { cpu_sr = CPU_SR_Save(); } while (0) /* Save    CPU status word & disable interrupts.*/
#define  CPU_INT_EN()          do { CPU_SR_Restore(cpu_sr); } while (0) /* Restore CPU status word.                     */

CPU_SR_Save
        MRS     R0, PRIMASK                     ; Set prio int mask to mask all (except hard faults)
        CPSID   I
        BX      LR

CPU_SR_Restore                                  ; See Note #2.
        MSR     PRIMASK, R0
        BX      LR
2.Cortex-M 處理器的異常中,編號 1~15 的為系統異常,16及以上的則為中斷異常。 3.PRIMASK用於遮蔽除NMI和fault以外的所有異常和中斷。該暫存器可以通過 MRS和MSR以下例方式訪問: 《Cortex M3與M4權威指南》章節7.10.1 P265 In many applications you might need to temporarily disable all interrupts to carry out some timing critical tasks. You can use the PRIMASK register for this purpose. The PRIMASK register can only be accessed in privileged state. The PRIMASK register is used to disable all exceptions except NMI and Hard Fault. It effectively changes the current priority level to 0 (highest programmable level). In C programming, you can use the functions provided in CMSIS-Core to set and clear PRIMASK: 1)關中斷
MOV R0,#1

MSR PRIMASK,R0
或者:
CPSID I;等價上面語句
2)開中斷
MOV R0,#0 MSR PRIMASK,R0
或者:
CPSIE I;等價上面語句