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);
}
}
lhshengshi 发布了4 篇原创文章 · 获赞 1 · 访问量 514 私信 关注标签:INTERRUPTS,freertos,中断,basepri,void,临界,CRITICAL,代码 来源: https://blog.csdn.net/lhshengshi/article/details/104536583