目录
从上一节”任务定义及切换“其中进入临界保护断的操作可以了解到:临界段就是通过操作basepri寄存器,来配置各个中断的禁用使能,以此达到操作系统在执行重要程序时不会被不重要的中断打断的目的。(这节也是同样的道理,只不过把配置basepri寄存器进入临界保护段的过程封装成了一个个不同功能的函数接口以此方便调用,下面将介绍各个函数的功能)
3.临界段保护
3.1任务级临界区代码
taskENTER_CRITICAL() “任务进入临界段函数”和taskEXIT_CRITICAL()“任务退出临界段函数”是成对使用的一段可以进行临界段嵌套的函数,它们的定义如下所示:(注意:这两个函数不能在中断中使用)
/* portmacro.h头文件中 */
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
#ifndef portFORCE_INLINE
#define portFORCE_INLINE __forceinline
#endif
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
/* task.h头文件中的宏定义 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
/* FreeRTOSConfig.h头文件中的宏定义 */
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
/************** port.c源文件中 **************/
#define portVECTACTIVE_MASK ( 0xFFUL )
static UBaseType_t uxCriticalNesting = 0xaaaaaaaa;//初始化44
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();
}
}
这里说明一点:__forceinline是强制内联的意思(意思是被内联的函数不会被编译器优化,如果是普通内联的话若编译器认为不合理则那个内联不会生效),若函数是内联函数,则编译器会自动的将函数的代码加载到以后会调用到函数的地方,若此时内联函数中的代码量很小,则效率会比调用函数时高(调用函数时需要用到栈空间来保存调用前的信息,这会使用掉一部分的栈空间)。但当内联函数的代码量很大时,这会导致程序的代码量急剧增大,此时内联的效率反而更低,这时代码量大的函数用普通调用就可以了。(由于进入和退出临界段只是操作了basepri寄存器,代码量很小,则此时用强制内联函数的效率要高一些)
3.1.1任务级进入临界段函数
其中任务级进入临界段函数portENTER_CRITICAL()也就是taskENTER_CRITICAL()做了以下操作:
1.首先执行的是函数也就是vPortRaiseBASEPRI这个函数,在这个函数中首先将0xb0也就是高4位为11传给变量ulNewBASEPRI,之后再将这个值赋给basepri寄存器,此时优先级小于中断号为11的中断都将被屏蔽。(具体为什么“任务切换”那节说过);
2.给全局变量uxCriticalNesting+1(它表示当前临界段的嵌套深度,意思就是一个任务内,可能有几个函数都调用了临界段,只有当最外层的临界段退出时,才能此任务才会真正退出临界段),这个变量的初始值是0xaaaaaaaa,在启动调度器时会被初始化为0此时已经是0了,至于为什么要+1它表示调用临界段的嵌套深度,具体如下:
调用任务级进入临界段函数后,若此任务有多个任务嵌套,且这几个嵌套都有进入临界段的操作,如果最内层函数调用了退出临界段的函数,若此时退出临界段,则最外层函数也要退出临界段了,但最外层函数此时都还没有退出临界段的需求,所以不能这莫做。需要加以个全局变量来记录这个嵌套深度,只有当最外层函数要退出的时才可以真正的退出临界段。
3.只有第一次调用任务级进入临界段函数时,才会直接配置baspri寄存器,否则省去这一步,因为此时系统已经在临界段中了,已经不需要重新配置basepri寄存器了;(其中configASSERT这个函数的意义为,若现在有任何中断在进行中则会返回错误信息,但此函数在此时并没有阻止basepri的配置)
3.1.2任务级退出临界段函数
任务级退出临界段函数portEXIT_CRITICAL()也就是taskEXIT_CRITICAL()做了以下操作:
1.首先通过configASSERT函数确认现在是在任务级临界段的情况下也就是uxCriticalNesting ≠0(若这里=0了则-1之后会变成负的,显然有错),若uxCriticalNesting =0则输出错误信息。若uxCriticalNesting ≠0,则调用任务级退出临界段函数,全局变量uxCriticalNesting 就-1;
2.确定uxCriticalNesting已经等于0了,表示现在最外层的临界段要退出临界段,可以进行basepri配置以恢复禁用的中断,此时系统正式退出了临界段;
3.1.3任务级临界段函数用法
一般用法如下:
{ /* 进入临界段 */
taskENTER_CRITICAL();
/* 临界段代码 */
/* 退出临界段*/
taskEXIT_CRITICAL();
}
3.2 中断级临界区代码
/*********************在task.h中***********************/
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
/*********************在portmacro.h中***********************/
#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
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
taskENTER_CRITICAL_FROM_ISR()中断级进入临界段函数,taskEXIT_CRITICAL_FROM_ISR中断级退出临界段函数,这两个函数是成对使用的,且在中断中使用(因为taskENTER_CRITICAL_FROM_ISR这个函数有保存进入中断前basepri寄存器值的功能,这个功能能让中断在退出临界段时恢复到系统再中断中进入临界段的前系统所处的中断禁用使能状态,这样在中断中退出临界段时,其它中断才能按先前的优先级正常运行)
3.2.1中断级进入临界段函数
taskENTER_CRITICAL_FROM_ISR函数具体做了以下操作:
1.将要配置basepri系统中断的值赋予给ulNewBASEPRI变量,之后将当前basepri寄存器中的值存储到ulReturn变量中(方便退出临界段时,恢复到之前的中断禁用使能状态);
2.将ulNewBASEPR变量中的值赋给basepri寄存器(此操作直接配置了basepri寄存器,使得中断优先级在中断号11的中断以下的中断禁用),此时系统在中断中进入了临界段;
3.返回中断进入临界段前的basepri寄存器配置;
3.2.2中断级退出临界段函数
这个函数的形参就是进入中断前basepri的配置,意为恢复到进入临界段前的basepri配置;
taskEXIT_CRITICAL_FROM_ISR函数具体做了以下操作:
1.将形参ulBASEPRI,给basepri寄存器,恢复到在中断中进入临界段以前的basepri配置;
3.2.3中断级临界段函数用法
一般用法如下:
{
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
/* 临界段代码 */
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}