目录
1、临界段代码保护简介
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码, 这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段 代码保护。
2、临界段代码保护函数介绍
临界区是通过禁用任务调度来屏蔽中断的
FreeRTOS 与 临 界 段 代 码 保 护 有 关 的 函 数 有 4 个 :
taskENTER_CRITICAL() 、 taskEXIT_CRITICAL() 、
taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR()
2.1、任务临界代码保护
taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临 界段,一个是退出临界段,这两个函数是成对使用的,这函数的定义如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
而 portENTER_CRITICAL()和 portEXIT_CRITICAL()也是宏定义,在文件 portmacro.h 中有 定义,如下:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
函数 vPortEnterCritical()和 vPortExitCritical()在文件 port.c 中
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
//这段代码是关于一个进入临界区的函数的注释。它指出这个函数不是中断安全的版本,如果在中断上下文中调用该函数,则会断言(assert)。只有以 "FromISR" 结尾的 API 函数可以在中断中使用。此外,只有在临界区嵌套计数为 1 时才断言,以防止在 assert 函数本身也使用临界区的情况下产生递归调用。
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
/*-----------------------------------------------------------*/
可以看出在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量 uxCriticalNesting 加一,uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是 退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才 会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不 会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后 才会使能中断!
ps:就好像每次调用vPortEnterCritical()这个函数后会对变量 uxCriticalNesting加一,证明此时不能被打断的代码段数量加一。而当临界段代码执行后会执行这段代码后的退出临界区的函数vPortExitCritical( )。在这里面会先对全局变量uxCriticalNesting减一,证明已经有一段临界区内的代码运行完毕。然后利用 if( uxCriticalNesting == 0 )对这段代码进行判断(如果没有这个判断就无法确认是否还有其他地方的临界区代码正在运行,贸然恢复会产生不可预估的影响)。如果没有临界区内的代码在执行才会执行恢复中断函数portENABLE_INTERRUPTS()对所有可管理的中断解除屏蔽,也就是恢复中断。
uxCriticalNesting 的定义在
在main.c中通过任务调度的函数 vTaskStartScheduler() 会对uxCriticalNesting进行初始化
可以找到
往下滑
go to 过去
在这个函数内往下滑
这段代码是用于初始化临界区嵌套计数的变量。通过将 uxCriticalNesting
设置为 0,准备好给第一个任务使用。这个变量用于跟踪当前处于临界区的嵌套层数。在进入临界区时,嵌套层数会递增,在退出临界区时,嵌套层数会递减。通过初始化为 0,表示当前没有任务处于临界区。
2.2 中断级临界段代码保护
函数 taskENTER_CRITICAL_FROM_ISR()和 taskEXIT_CRITICAL_FROM_ISR()中断级别 临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于 configMAX_SYSCALL_INTERRUPT_PRIORITY!原因前面已经说了。这两个函数在文件 task.h 中有如下定义:
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
portSET_INTERRUPT_MASK_FROM_ISR()和portCLEAR_INTERRUPT_MASK_FROM_ISR()这两个在文件 portmacro.h 中有如下定义:
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
}
}
vPortSetBASEPRI()前面已经讲解了,就是给 BASEPRI 寄存器中写入一个值。
/*-----------------------------------------------------------*/
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
}
}
/*-----------------------------------------------------------*/
ulPortRaiseBASEPRI()
(1)、先读出 BASEPRI 的值,保存在 ulReturn 中。
(2)、将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入到寄存器 BASEPRI 中。
(3)、返回 ulReturn,退出临界区代码保护的时候要使用到此值!