目录
一、概述
当一个任务在使用某个资源的过程中,即还没有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态。如果这个时候有另一个任务或者中断来访问这个资源,则会导致数据损坏或是其它相似的错误。如下
- 任务 A 运行,并往 LCD 写字符串”Hello world”。
- 任务 A 被任务 B 抢占,但此时字符串才输出到”Hello w”。
- 任务 B 往 LCD 写”Abort, Retry, Fail?”,然后进入阻塞态。
- 任务 A 从被抢占处继续执行,完成剩余的字符输出——“orld”。
- 现在 LCD 显示的是被破坏了的字符串”Hello wAbort, Retry, Fail?orld”。
因为在程序运行时,是将语句拆分若干条指令,比如a++操作,执行读->加->写,CPU每一行一条指令,就会判断一下有没有中断或异常触发。
如下,当任务A_Task调用add_Task时候,执行到“读”操作,此时被B_Task抢占执行,B_Task执行完读->加->写操作后,此时a=1,那么A_Task再次去执行的时候,执行完读->加->写后,那么a到底等于1还是等于2?此时就会出现问题。那么如何避免?其中一个方法,我们加入临界区。
uint8_t a=0;
void add_Task(void)
{
a++;
}
void A_Task(void *p)
{
while(1)
{
add_Task();
}
}
void B_Task(void *p)
{
while(1)
{
add_Task();
}
}
二、应用
相关函数
/*进入临界区*/
void taskENTER_CRITICAL( void );
/*退出临界区*/
void taskEXIT_CRITICAL( void );
当A_Task执行调用add_Task函数,进入临界区保护后,a++就被保护起来了,可以理解为关中断。如果所使用的 FreeRTOS 移植使用了configMAX_SYSCALL_INTERRUPT_PRIORITY 内核配置常量,那么将会禁用优先级低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义设置的优先级的中断,并启用所有高于此优先级的中断。抢占式上下文切换仅在中断内发生, 在中断被禁用时不会发生。
uint8_t a=0;
void add_Task(void)
{
//进入临界区
taskENTER_CRITICAL();
//临界资源
a++;
//退出临界区
taskEXIT_CRITICAL();
}
void A_Task(void *p)
{
while(1)
{
add_Task();
}
}
void B_Task(void *p)
{
while(1)
{
add_Task();
}
}
三、源码分析
1、进入临界区
/*进入临界区*/
void taskENTER_CRITICAL( void );
经过层层调用,最后来到vPortEnterCritical函数。如下,portDISABLE_INTERRUPTS()函数作用表示将中断优先级设置为configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义所设置的值(后续会讲)。uxCriticalNesting之所以执行++,是因为临界区可以嵌套使用。
void vPortEnterCritical( void )
{
/*关闭中断*/
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/*可以理解为:设置中断优先级,大于可屏蔽中断优先级,这样就不会被抢占*/
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
如下,portDISABLE_INTERRUPTS()经过调用后,来到了vPortRaiseBASEPRI函数。将basepri强制设置成configMAX_SYSCALL_INTERRUPT_PRIORITY参数。
basepri寄存器的作用:禁止优先级低于某个特定等级的中断,只需要将所需的屏蔽优先级写入basepri寄存器即可。通俗一点讲,禁止优先级低于等于basepri的中断。
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
/*configMAX_SYSCALL_INTERRUPT_PRIORITY中断优先级*/
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/*将ulNewBASEPRI写进basepri*/
msr basepri, ulNewBASEPRI
/*确保指令完全执行*/
dsb
isb
}
}
那么我们就可以理解,当我们定义了configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义之后。进入临界区,就是将禁止优先级低于等于configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义的中断触发。
2、退出临界区
/*退出临界区*/
void taskEXIT_CRITICAL( void );
如下,经过层层调用,最后来到vPortExitCritical函数。portENABLE_INTERRUPTS()函数作用表示将basepri设置为0。因为临界区可以嵌套使用,执行uxCriticalNesting--,当xCriticalNesting等于0的时候表示完全退出临界区。
/*退出临界区*/
void vPortExitCritical( void )
{
/*检查作用*/
configASSERT( uxCriticalNesting );
/*确保嵌套结束*/
uxCriticalNesting--;
/*退出中断,将中断优先级设置为0,这样相当于退出中断*/
if( uxCriticalNesting == 0 )
{
/*将basepri置0*/
portENABLE_INTERRUPTS();
}
}
如下,portENABLE_INTERRUPTS()经过调用后,来到了vPortSetBASEPRI函数。说明此时将要完全退出临界区,故将将basepri强制设置成0。
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
那么我们就可以理解,退出临界区就是将basepri设置为0即可。
3、中断临界区
(1)应用
UBaseType_t taskENTER_CRITICAL_FROM_ISR( void );
void taskEXIT_CRITICAL_FROM_ISR( UBaseType_t uxSavedInterruptStatus );
中断临界区函数使用和临界区函数使用有一点区别,但是核心一样,都是将其basepri寄存器设置为configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义数值。只不过在设置之前需要保存当前中断掩码。
//应用方法
void vDemoISR( void )
{ /*定义保存中断掩码变量*/
UBaseType_t uxSavedInterruptStatus;
/*保存返回值(中断掩码)*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
//资源
/*写入中断掩码,用于退出临界区*/
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}
(2)进入中断临界区
经过taskENTER_CRITICAL_FROM_ISR()的层层调用,来到了ulPortRaiseBASEPRI函数。先保存旧的中断掩码等级,随后在设置新的BASEPRI等级。
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
/*设置新的ulNewBASEPRI等级,与之前步骤相同*/
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/*保存当前basepri等级*/
mrs ulReturn, basepri
/*设置新的basepri*/
msr basepri, ulNewBASEPRI
/*确保指令完全执行*/
dsb
isb
}
/*返回原始中断掩码*/
return ulReturn;
}
(3)退出中断临界区
经过taskEXIT_CRITICAL_FROM_ISR(参数)的层层调用,来到了vPortSetBASEPRI函数。ulBASEPRI传入的原始中断掩码参数
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{ /*将原始中断掩码写入basepri寄存器*/
msr basepri, ulBASEPRI
}
}
四、注意事项
1、临界区必须简短,因为长时间关闭中断会极大影响系统性能。
2、不得从临界区调用Freertos api函数。
3、中断服务程序(ISR)中想要使用临界区要使用专门的函数。
4、不能在临界区出现阻塞等任务操作。
标签:CRITICAL,Task,Freertos,中断,basepri,void,临界,源码 From: https://blog.csdn.net/2301_80348599/article/details/142815078