freertos临界段代码保护机制

15.1  临界段

代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。

 

l  FreeRTOS临界段相关知识补充

FreeRTOS的源码中有多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时性。比如此时某个任务正在调用系统API函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行,如果关中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。

 

FreeRTOS源码中就有多处临界段的处理,跟FreeRTOS一样,uCOS-II和uCOS-III源码中都是有临界段的,而RTX的源码中不存在临界段。另外,除了FreeRTOS操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:

u  读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。

u  调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。

总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。

15.2 任务代码临界段处理

FreeRTOS任务代码中临界段的进入和退出主要是通过操作寄存器basepri实现的。进入临界段前操作寄存器basepri关闭了所有小于等于宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY所定义的中断优先级,这样临界段代码就不会被中断干扰到,而且实现任务切换功能的PendSV中断和滴答定时器中断是最低优先级中断,所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。退出临界段时重新操作basepri寄存器,即打开被关闭的中断(这里我们不考虑不受FreeRTOS管理的更高优先级中断)。FreeRTOS进入和退出临界段的函数如下:

#define taskENTER_CRITICAL()         portENTER_CRITICAL()

#define taskEXIT_CRITICAL()          portEXIT_CRITICAL()

上面这两个函数是供用户调用的,其中函数taskENTER_CRITICAL是进入临界段,函数taskEXIT_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();

     }

}

通过上面的两个函数vPortEnterCritical和vPortExitCritical可以看出,进入临界段和退出临界段是通过函数调用开关中断函数portENABLE_INTERRUPTS和portDISABLE_INTERRUPTS实现的。细心的读者还会发现上面的这两个函数都对变量uxCriticalNesting进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。通过嵌套计数就有效地防止了用户嵌套调用函数taskENTER_CRITICAL和taskEXIT_CRITICAL时出错。

l  直接的开关中断方式不支持嵌套调用例子说明

比如下面的例子:

void FunctionA()

{

taskDISABLE_INTERRUPTS();  关闭中断

FunctionB(); 调用函数B

FunctionC(); 调用函数C

taskENABLE_INTERRUPTS();  打开中断

}

 

void FunctionB()

{

taskDISABLE_INTERRUPTS();  关闭中断

代码

taskENABLE_INTERRUPTS();  打开中断

}

工程中调用了FunctionA就会出现执行完FunctionB后中断被打开的情况,此时FunctionC将

不被保护了。

 

接下来继续说明开关中断的实现,我们要打破砂锅问到底:

#define portDISABLE_INTERRUPTS()          vPortRaiseBASEPRI()

#define portENABLE_INTERRUPTS()           vPortSetBASEPRI( 0 )

函数vPortRaiseBASEPRI和vPortSetBASEPRI的源码实现如下:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )

{

     __asm

     {

        

         msr basepri, ulBASEPRI

     }

}

 

 

static portFORCE_INLINE void vPortRaiseBASEPRI( void )

{

uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

 

     __asm

     {

         

         msr basepri, ulNewBASEPRI

         dsb

         isb

     }

}

经过这么多次的宏定义后,终于来到了最终的原始函数。FreeRTOS的这种层层调用宏定义的方法在带来便利操作的同时,却让用户在分析源码的时候非常不方便。

通过上面的源码实现可以看出,FreeRTOS的开关全局中断是通过操作寄存器basepri实现的,关于这个寄存器,我们已经在第12章进行了详细的讲解,这里不再赘述。

使用举例:

使用的时候一定要保证成对使用

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

            

         taskENTER_CRITICAL();

         printf("任务vTaskLED正在运行\r\n");

            

         taskEXIT_CRITICAL();

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

       

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

嵌套使用举例:

void FunctionB()

{

     taskENTER_CRITICAL()

     临界段代码

     taskEXIT_CRITICAL();

 

}

 

void FunctionA()

{

     taskENTER_CRITICAL(); 

FunctionB();            

FunctionC();               

     taskEXIT_CRITICAL();   

}

15.3 中断服务程序临界段处理

与任务代码里临界段的处理方式类似,中断服务程序里面临界段的处理也有一对开关中断函数。

#define taskENTER_CRITICAL_FROM_ISR()     portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL_FROM_ISR( x )   portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

进一步跟踪宏定义的实现如下:

#define portSET_INTERRUPT_MASK_FROM_ISR()      ulPortRaiseBASEPRI()

#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)   vPortSetBASEPRI(x)

再进一步跟踪宏定义的实现如下:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )

{

     __asm

     {

        

         msr basepri, ulBASEPRI

     }

}

 

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )

{

uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

 

     __asm

     {

        

         mrs ulReturn, basepri

         msr basepri, ulNewBASEPRI

         dsb

         isb

     }

 

     return ulReturn;

}

通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器basepri实现的。

初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器basepri的数值就可以实现嵌套使用。如果大家研究过uCOS-II或者III的源码,跟这里的实现方式是一样的,具体看下面的使用举例。

使用举例:

使用的时候一定要保证成对使用

 

void TIM6_DAC_IRQHandler( void )

{

UBaseType_t uxSavedInterruptStatus;

 

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

     临界区代码

portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

 

}

嵌套使用举例:

void FunctionB()

{

UBaseType_t uxSavedInterruptStatus;

 

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

     临界区代码

portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

}

 

 

 

void TIM6_DAC_IRQHandler( void )

{

UBaseType_t uxSavedInterruptStatus;

 

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();

FunctionB(); 

FunctionC();     

portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

}

15.4开关中断的实现

FreeRTOS也专门提供了一组开关中断函数,实现比较简单,其实就是前面15.2小节里面临界段进入和退出函数的精简版本,主要区别是不支持中断嵌套。具体实现如下:

#define taskDISABLE_INTERRUPTS()   portDISABLE_INTERRUPTS()

#define taskENABLE_INTERRUPTS()    portENABLE_INTERRUPTS()

进一步跟踪宏定义的实现如下:

#define portDISABLE_INTERRUPTS()          vPortRaiseBASEPRI()

#define portENABLE_INTERRUPTS()           vPortSetBASEPRI( 0 )

函数vPortRaiseBASEPRI和vPortSetBASEPRI的源码实现如下:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )

{

     __asm

     {

        

         msr basepri, ulBASEPRI

     }

}

 

 

static portFORCE_INLINE void vPortRaiseBASEPRI( void )

{

uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

 

     __asm

     {

         

         msr basepri, ulNewBASEPRI

         dsb

         isb

     }

}

从上面的源码可以看出,FreeRTOS的全局中断开关是通过操作寄存器basepri实现的,关于这个寄存器,我们已经在第12章进行了详细的讲解,这里不再赘述。

使用举例:

使用的时候一定要保证成对使用

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

              

         taskDISABLE_INTERRUPTS();

         printf("任务vTaskLED正在运行\r\n");

              

          taskENABLE_INTERRUPTS();

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

       

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
stm32代码临界保护是指在使用FreeRTOS操作系统时,保护临界区代码不被打断的一种机制。当进入临界区代码时,FreeRTOS停止对其他任务的调度,关闭中断,以保证临界区代码的正常执行。这个过程类似于拥有一个保护伞,直到任务退出临界区恢复中断响应。 在stm32代码中,使用portDISABLE_INTERRUPTS()函数来实现关闭中断的功能。同时,使用uxCriticalNesting变量来记录临界区嵌套的次数。只有当uxCriticalNesting等于0时,才可以开启中断。这样可以确保在有多个临界区代码保护的情况下,不因为一个临界区代码保护退出而打开中断,影响其他的临界区代码保护。只有当所有临界区代码保护退出后,中断才被打开。 在stm32中,中断服务函数也可以进入临界区。调用taskENTER_CRITICAL_FROM_ISR()函数可以实现这一功能。该函数的返回值ulReturn需要保存,以便在退出临界区时写入。通过设置BASEPRI寄存器的值为configMAX_SYSCALL_INTERRUPT_PRIORITY,可以达到关闭中断的效果。在退出临界区时,将保存的ulReturn值重新写入BASEPRI寄存器,以恢复中断。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [FreeRTOS临界代码保护[STM32]](https://blog.csdn.net/m0_63741844/article/details/124067707)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值