参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、临界段代码保护
1、临界段
(1)临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段。
(2)临界段适合于需严格按照时序的场合,如软件通信协议的实现,如果其实现过程中被中断打断,将会扰乱时序,从而引发通讯错误。
(3)临界区外,中断与任务调度可以打断当前程序的运行,而临界区内直接屏蔽了中断(实际上,任务调度也是依靠中断实现的)。
2、临界段代码保护函数
(1)FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。
(2)临界段代码保护函数分为任务级和中断级,也就是分为在任务函数中使用或者和在中断服务函数中使用。
函数 | 描述 |
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段 |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段 |
(3)临界段代码保护函数使用方法:
①任务级临界区调用格式(可嵌套使用):
taskENTER_CRITICAL();
{
… … /* 临界区 */
}
taskEXIT_CRITICAL();
②中断级临界区调用格式(可嵌套使用):
uint32_t save_status; //存储中断屏蔽寄存器的值
save_status = taskENTER_CRITICAL_FROM_ISR();
{
… … /* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status);
(4)需注意,临界区中是屏蔽了中断的,而在实际运用中,中断服务程序往往需要执行非常重要的动作,为了防止这些“非常重要的动作”迟迟没有执行,临界区的代码段不宜过长(或者说执行时间不宜过长)。
二、任务调度器挂起和恢复
1、任务调度器挂起与进入临界区
与进入临界区不一样的是,挂起任务调度器之后,中断并未被关闭,它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应。
2、任务调度器挂起和恢复函数
(1)函数概览:
函数 | 描述 |
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
(2)任务调度器挂起和恢复函数使用方法:
vTaskSuspendAll() ;
{
… … /* 内容 */
}
xTaskResumeAll();
三、相关函数源码剖析
1、taskENTER_CRITICAL函数
(1)通过层层Go To Definition,可发现该函数的底层是vPortEnterCritical函数。
(2)vPortEnterCritical函数源码剖析:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS(); //关中断(BASEPRI设置为0x50)
uxCriticalNesting++; //临界区嵌套计数自增(其初始值为0)
if( uxCriticalNesting == 1 )
{
configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);
}
}
2、taskEXIT_CRITICAL函数
(1)通过层层Go To Definition,可发现该函数的底层是vPortExitCritical函数。
(2)vPortExitCritical函数源码剖析:
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--; //临界区嵌套计数自减(其初始值为0)
if( uxCriticalNesting == 0 ) //如果退出临界区后没有下一层嵌套
{
portENABLE_INTERRUPTS(); //开中断(BASEPRI设置为0x00)
}
}
3、vTaskSuspendAll函数
void vTaskSuspendAll( void )
{
traceENTER_vTaskSuspendAll();
#if ( configNUMBER_OF_CORES == 1 )
{
portSOFTWARE_BARRIER();
portMEMORY_BARRIER();
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
UBaseType_t ulState;
portASSERT_IF_IN_ISR();
if( xSchedulerRunning != pdFALSE )
{
ulState = portSET_INTERRUPT_MASK();
configASSERT( portGET_CRITICAL_NESTING_COUNT() == 0 );
portSOFTWARE_BARRIER();
portGET_TASK_LOCK();
if( uxSchedulerSuspended == 0U )
{
prvCheckForRunStateChange();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
portGET_ISR_LOCK();
++uxSchedulerSuspended; //该变量只要不为0,PendSV中断关闭,不会执行任务切换
portRELEASE_ISR_LOCK();
portCLEAR_INTERRUPT_MASK( ulState );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
traceRETURN_vTaskSuspendAll();
}
4、xTaskResumeAll函数
BaseType_t xTaskResumeAll( void )
{
TCB_t * pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
traceENTER_xTaskResumeAll();
#if ( configNUMBER_OF_CORES > 1 )
if( xSchedulerRunning != pdFALSE )
#endif
{
taskENTER_CRITICAL(); //进入临界区
{
BaseType_t xCoreID;
xCoreID = ( BaseType_t ) portGET_CORE_ID();
configASSERT( uxSchedulerSuspended != 0U );
uxSchedulerSuspended = ( UBaseType_t ) ( uxSchedulerSuspended - 1U ); //挂起嵌套值减1
portRELEASE_TASK_LOCK();
if( uxSchedulerSuspended == ( UBaseType_t ) 0U ) //挂起嵌套值为0,说明需要恢复任务调度器
{
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U ) //判断已创建的任务数量是否大于0
{
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ) //判断等待就绪列表中是否有任务,有则全部移出去
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
portMEMORY_BARRIER();
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB ); //从等待就绪列表中移出的任务全部加进就绪列表中
#if ( configNUMBER_OF_CORES == 1 )
{
//判断是否需要进行任务切换
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
xYieldPendings[ xCoreID ] = pdTRUE;
else
mtCOVERAGE_TEST_MARKER();
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
}
if( pxTCB != NULL )
{
//更新下一个任务的阻塞超时时间
prvResetNextTaskUnblockTime();
}
/* 恢复滴答定时器在任务调度器被挂起时丢失的节拍数 */
{
TickType_t xPendedCounts = xPendedTicks;
if( xPendedCounts > ( TickType_t ) 0U )
{
do
{
//补偿丢失的节拍数并判断是否需要任务切换
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPendings[ xCoreID ] = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--xPendedCounts;
} while( xPendedCounts > ( TickType_t ) 0U );
xPendedTicks = 0;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if(xYieldPendings[xCoreID] != pdFALSE) //判断是否需要任务切换
{
#if ( configUSE_PREEMPTION != 0 )
{
xAlreadyYielded = pdTRUE;
}
#endif /* #if ( configUSE_PREEMPTION != 0 ) */
#if ( configNUMBER_OF_CORES == 1 )
{
taskYIELD_TASK_CORE_IF_USING_PREEMPTION( pxCurrentTCB );
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); //退出临界区
}
traceRETURN_xTaskResumeAll( xAlreadyYielded );
return xAlreadyYielded;
}