03_临界段的保护

一、临界段的概念

所谓的临界段就是在执行时不能被中断的代码段。在FreeRTOS中,临界段最常出现的地方就是对全局变量的操作。那么什么情况下临界段会被打断?一个是系统调度,还有一个是外部中断。在FreeRTOS中,系统调度最终也是产生PendSV中断,在PendSV Handler中实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS对临界段的保护最终还是回归到对中断的开和关的控制。

二、Cortex-M内核快速关中断指令

为了快速地开关中断,Cortex-M内核专门设置了一条CPS指令,它有四种用法,具体如下:

CPSID I;PRIMASK = 1;关中断
CPSIE I;PRIMASK = 0;开中断
CPSID F;FAULTMASK = 1;关异常
CPSIE F;FAULTMASK = 1;开异常

在FreeRTOS中,对中断的开关是通过BASEPRI寄存器来实现的,即大于等于BASEPRI寄存器的值的中断会被屏蔽,小于BASEPRI的值的中断不会被屏蔽,不受FreeRTOS管理。用户可以设置BASEPRI的值来选择性地给一些非常紧急的中断留出余地。《Cortex M3与M4权威指南》中对BASEPRI寄存器的描述如下:

In some cases, you might only want to disable interrupts with priority lower than a certain level. In this case, you could use the BASEPRI register. To do this, simply write the required masking priority level to the BASEPRI register. For example, if you want to block all exceptions with priority level equal to or lower than 0x60,you can write the value to BASEPRI:

__set_BASEPRI(0x60); // Disable interrupts with priority 0x60-0xFF using CMSIS-Core function

For users of assembly language, the same operation can be written as:

MOVS R0, #0x60

**MSR BASEPRI, R0 **; Disable interrupts with priority

三、关中断

FreeRTOS中关中断的函数在portmacro.h文件中实现,分为不带返回值和带返回值两种。

1.不带返回值的关中断函数

/* 不带返回值的关中断函数不能嵌套,不能在中断中使用 */
#define portDISABLE_INTERRUPTS()   vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    __asm
    {
        msr basepri,ulNewBASEPRI
        dsb
        isb
    }
}

不带返回值的意思是在向BASEPRI中写入新值时,不用先将BASEPRI的值保存起来,即不用考虑当前的中断状态是怎么样的,这意味着这个函数不能在中断中使用。第6行的configMAX_SYSCALL_INTERRUPT_PRIORITY是在FreeRTOSConfig.h中定义的一个宏,该宏默认是191,高四位有效即等于0xb0,即十进制的11,所以优先级大于等于11的中断都会被屏蔽,11以内的中断不受FreeRTOS管理。

2.带返回值的中断

/* 带返回值的关中断函数可以嵌套,可以在中断中使用 */
#define portSET_INTERRUPT_MASK_FROM_ISR()   ulPortRaiseBASEPRI()
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的值保存起来,在更新完BASEPRI的值时,将之前的BASEPRI的值返回,返回的值作为参数传入开中断函数中,以此来恢复之前的状态。

四、开中断

开中断函数在portmacro.h中定义。

/* 不带中断保护的开中断函数 */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
/* 带中断保护的开中断函数 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    vPortSetBASEPRI(x)

static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
    __asm
    {
        msr basepri,ulBASEPRI
    }
}

第2行为不带中断保护的开中断函数,直接将BASEPRI的值设置为0,与不带返回值的关中断函数portDISABLE_INTERRUPTS()成对使用。第4行是带中断保护的开中断函数,将上一次关中断时返回的值作为形参重新写入BASEPRI寄存器中,与带返回值的关中断函数portSET_INTERRUPT_MASK_FROM_ISR()成对使用。

五、进入、退出临界段的宏

进入和退出临界段的宏在task.h中定义,具体如下:

/* 进入和退出临界段的宏定义 */
#define taskENTER_CRITICAL()            portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR()   portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL()             portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR(x)   portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    

进入和退出临界段的宏分为中断保护和非中断保护两种,但最终都是通过开关中断来实现的。这样多次定义的目的是为了兼容不同的处理器,因为不同的处理器的开关中断的实现是不同的,在task.h中的最终调用的,在port.h中的是根据不同的处理器来实现的。

1.进入临界段

(1)不带中断保护,不能嵌套

/* 在portmacro.h中定义 */
#define portENTER_CRITICAL()        vPortEnterCritical()
/* 在port.c中定义 */
void vPortEnterCritical(void)
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;
    if(uxCriticalNesting==1)
    {
        configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);
    }
}

第7行中的uxCriticalNesting是一个port.c中定义的静态变量,默认初始化为0xaaaaaaaa,在调度器启动后会被重新初始化为0,其作用是对临界段嵌套的计数。第8行,如果uxCriticalNesting等于表示当前有一层嵌套,要确保当前没有中断活跃,即啮合外设SCB中的中断和控制寄存器ICSR的低8位要等于0。

(2)带中断保护,可以嵌套

#define portSET_INTERRUPT_MASK_FROM_ISR()   ulPortRaiseBASEPRI()

定义的函数在前面关中断的时候已经提及。

2.退出临界段

(1)不带中断保护,不能嵌套

/* 在portmacro.h中定义 */
#define portEXIT_CRITICAL()         vPortExitCritical()
void vPortExitCritical(void)
{
    configASSERT(uxCriticalNesting);
    uxCriticalNesting--;
    if(uxCriticalNesting == 0)
    {
        portENABLE_INTERRUPTS();
    }
}

(2)带中断保护,可以嵌套

/* 在portmacro.h中定义 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    vPortSetBASEPRI(x)

六、临界段代码的应用

在FreeRTOS中,临界代码的应用出现在两种场合,一种是在中断场合,另一种是在非中断场合。应用示例代码如下:

/* 在中断场合使用,临界段可以嵌套 */
{
    uint32_t ulReturn;
    /* 进入临界段,临界段可以嵌套 */
    ulReturn = taskENTER_CRITICAL_FROM_ISR();
    
    /* 临界段代码 */
    
    /* 退出临界段 */
    taskEXIT_CRITICAL_FROM_ISR();
}
/* 在非中断场合,临界段不能嵌套 */
{
    /* 进入临界段 */
    taskENTER_CRITICAL();
    
    /* 临界段代码 */
    
    /* 退出临界段 */
    taskEXIT_CRITICAL();
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忆昔z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值