一、临界段代码保护简介
临界段:临界段代码也叫临界区,指那些必须完整运行,不能被打断的代码段
什么场合需要用到临界段?
1.外设:需要严格按照时序初始化的外设:IIC、SPI等
2.系统:系统自身需求
3.用户:自身需求
除了中断还有什么可以打断当前程序运行?
任务调度(pendsv):如果处于低优先级,则可能会出现被抢占情况
二、临界段代码保护函数介绍
FreeRTOS在进入临界段代码时需要关闭中断,直到处理完临界段代码才能再打开
函数 | 描述 |
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段 |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段 |
任务级临界区调用格式示例: 中断级临界区调用格式示例:
特点:
1、成对使用
2、支持嵌套
3、尽量保持临界段耗时短
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();//中断关闭
uxCriticalNesting++;
//临界区嵌套计数器,初始化为0,进入一次++进行计数,在退出临界段函数中--,减到0才会开启中断,也就是进入n次临界段的次数=退出n次临界段次数,
/* This is not the interrupt safe version of the enter critical function so
* assert() if it is being called from an interrupt context. Only API
* functions that end in "FromISR" can be used in an interrupt. Only assert if
* the critical nesting count is 1 to protect against recursive calls if the
* assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
taskENTER_CRITICAL()中包含的portDISABLE_INTERRUPTS()(关闭中断)函数原理为将最高为5的优先级设入basepri寄存器里(中断屏蔽寄存器),这样关闭优先级小于5的所有中断
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );//段凝功能:提示作用,保证成对出现
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
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. */
/* *INDENT-OFF* */
mrs ulReturn, basepri//读取中断屏蔽寄存器
msr basepri, ulNewBASEPRI//关闭中断
dsb
isb
/* *INDENT-ON* */
}
return ulReturn;//返回值进入出口参数的中断屏蔽寄存器,保证进临界区和退出临界区的中断状态是一致的
}
三、任务调度器的挂起与恢复
挂起任务调度器, 调用此函数不需要关闭中断
函数 | 描述 |
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
使用格式实例:
1、与临界区不一样的是,挂起任务调度器,未关闭中断;
2、它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;
3、挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全
#if ( INCLUDE_vTaskDelay == 1 )
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll();//任务挂起,函数内部通过pendsv ++SchedulerSuspended(初始0)进行任务切换,pendsv在滴答定时器中断服务函数里面,只要xTaskIncrementTick返回值不为pdFALSE(初始值为pdFALSE,只要++就不会返回pdFALSE)
{
traceTASK_DELAY();
/* A task that is removed from the event list while the
* scheduler is suspended will not get placed in the ready
* list or removed from the blocked list until the scheduler
* is resumed.
*
* This task cannot be in an event list as it is the currently
* executing task. */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();//是否进行了任务切换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Force a reschedule if xTaskResumeAll has not already done so, we may
* have put ourselves to sleep. */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelay */
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;//pendsv中断触发不了,任务切换不了,调度器被挂起
}
}
vPortClearBASEPRIFromISR();
BaseType_t xTaskResumeAll( void )
{
TCB_t * pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
configASSERT( uxSchedulerSuspended );
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;//挂起时函数++,恢复时--
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//=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 the moved task has a priority higher than or equal to
* the current task then a yield must be performed. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )//判断恢复任务优先级是否大于等于正在执行任务,如大于则进行一次任务切换
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( pxTCB != NULL )
{
prvResetNextTaskUnblockTime();//更行下一次阻塞时间
}
/* If any ticks occurred while the scheduler was suspended then
* they should be processed now. This ensures the tick count does
* not slip, and that any delayed tasks are resumed at the correct
* time. */
/* 恢复滴答定时器在任务调度器被挂起的时候所丢失的节拍数 */
/* FreeRTOS中的心跳节拍都是通过xTickCount来实现,但只有在调度器没被挂起才会进入函数,所以FreeRTOS在挂起时有一个新的函数pendTicks来计数被挂起的时间有,用于恢复 */
{
TickType_t xPendedCounts = xPendedTicks;//加回丢失的节拍数
if( xPendedCounts > ( TickType_t ) 0U )
{
do
{
if( xTaskIncrementTick() != pdFALSE )//补齐丢失的节拍数
{
xYieldPending = pdTRUE;//判断是否进行任务切换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--xPendedCounts;//补齐一次进行--
} while( xPendedCounts > ( TickType_t ) 0U );//补齐时退出
xPendedTicks = 0;//清零
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( xYieldPending != pdFALSE )//不等于(优先级大于)则进行任务切换
{
#if ( configUSE_PREEMPTION != 0 )//判断是否使用了抢占式调度器
{
xAlreadyYielded = pdTRUE;
}
#endif
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xAlreadyYielded;