目录
1.临界段代码保护简介
2.临界段代码保护函数介绍
2.1任务级临界区调用格式示例
2.2中断级临界区调用格式示例
2.3函数调用特点
2.4任务级进入和退出临界段函数
2.5中断级进入和退出临界段函数
3.任务调度器的挂起和恢复
3.1任务调度器挂起函数vTaskSuspendAll()
3.2任务调度器恢复函数xTaskResumeAll()
4.总结
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段。
适用的场合,例如:
1,外设:需严格按照时序初始化的外设:IIC、SPI等等;
2,系统:系统自身需求(FreeRTOS源码很多地方用到了临界段代码保护,属于系统自身的需求);
3,用户:用户需求(用户想某些代码段不想被打断);
可以打断当前程序运行的情况:1、中断;2、任务调度(高优先级的任务可以抢占低优先级的任务)。
如果不想被打断,则关闭中断。这样做有两个好处,1是关闭了中断,中断将不再打断正在运行的程序;2是PendSV属于最低优先级的中断,在PendSV中进行任务切换和调度,FreeRTOS关闭中断,5~15优先级的中断将不起作用,则不能进行任务切换,但是优先级为0~4的中断可以正常打断正在运行的程序。
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断,流程图如下所示:
函数 | 描述 |
---|---|
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段(中断服务函数中调用) |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段(中断服务函数中调用) |
进入或退出临界段,本质是关闭或开启中断。taskENTER_CRITICAL()和taskEXIT_CRITICAL()与【05】FreeRTOS的中断管理此篇文章中提到的关闭和开启中断,本质也是调用【05】文章中提到的关闭中断函数portENABLE_INTERRUPTS()和开启中断函数portDISABLE_INTERRUPTS(),只不过做了嵌套功能。
taskENTER_CRITICAL() ;//关闭中断
{
… … /* 临界区 */
}
taskEXIT_CRITICAL()//开启中断
taskENTER_CRITICAL_FROM_ISR()函数提前返回之前中断屏蔽寄存器的值,并关闭中断。taskEXIT_CRITICAL_FROM_ISR()函数退出临界区时,将之前保存的“值”赋值给中断屏蔽寄存器,保持原有的状态。
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
{
… … /* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );
1、成对使用
2、支持嵌套(使用两个进入临界段,则有两个退出临界段)
3、尽量保持临界段耗时短
进入临界段函数 taskENTER_CRITICAL()最底层为以下函数,portDISABLE_INTERRUPTS()关闭中断,变量uxCriticalNesting计算进入临界段的次数。
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* 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 );
}
}
退出临界段函数taskEXIT_CRITICAL()最低层函数为以下函数,将变量uxCriticalNesting递减,直至变量为0时开启中断。变量uxCriticalNesting不能为0,如果为0,减1后则变成负值。
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
configASSERT()断言功能是进行调试的,报一些提示、报错,判断x是否等于0,如果等于0则打印错误。
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
进入临界段函数taskENTER_CRITICAL_FROM_ISR()最底层为以下函数,返回无符号32位。
mrs ulReturn, basepri:将中断屏蔽寄存器中的值读取出来;
msr basepri, ulNewBASEPRI:关闭中断;
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;
}
退出临界段函数taskEXIT_CRITICAL_FROM_ISR()最低层函数为以下函数,将关闭中断时返回的值代入中断屏蔽寄存器中,保持进入临界段和退出临界段时中断状态一致。
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. */
/* *INDENT-OFF* */
msr basepri, ulBASEPRI
/* *INDENT-ON* */
}
}
挂起,意思就是暂停,任务调度器停止,代表任务将不能切换。挂起任务调度器, 任务将不能进行调度、切换(中断依旧可以响应),调用此函数不需要关闭中断。
函数 | 描述 |
---|---|
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器(具有返回值,判断是否需要进行任务切换) |
使用格式示例(首先挂起任务调度器,执行内容不允许被其他内容打断,中断可以正常打断执行的内容,防止任务与任务之间的资源抢夺,恢复任务调度器):
vTaskSuspendAll() ;
{
… … /* 内容 */
}
xTaskResumeAll() ;
1、与临界区不一样的是,挂起任务调度器,未关闭中断;
2、它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;
3、挂起调度器的方式,适用于临界区位于任务与任务之间(任务与任务之间不抢夺,属于任务与任务之间的临界区);既不用去延时中断,又可以做到临界区的安全(如果临界区代码量过大,执行时间较长,将导致延时中断响应,使用此方式可以避免延时中断)
任务调度器挂起函数只是将变量uxSchedulerSuspended自加,根本上任务调度器是进行任务的切换,任务切换是PendSV中断进行的,在嘀嗒定时器中断服务函数SysTick_Handler()中触发PendSV中断。 变量uxSchedulerSuspended默认为0,pdFALSE。
void vTaskSuspendAll( void )
{
/* A critical section is not required as the variable is of type
* BaseType_t. Please read Richard Barry's reply in the following link to a
* post in the FreeRTOS support forum before reporting this as a bug! -
* https://goo.gl/wu4acr */
/* portSOFTWARE_BARRIER() is only implemented for emulated/simulated ports that
* do not otherwise exhibit real time behaviour. */
portSOFTWARE_BARRIER();
/* The scheduler is suspended if uxSchedulerSuspended is non-zero. An increment
* is used to allow calls to vTaskSuspendAll() to nest. */
++uxSchedulerSuspended;
/* Enforces ordering for ports and optimised compilers that may otherwise place
* the above increment elsewhere. */
portMEMORY_BARRIER();
}
嘀嗒定时器中断服务函数SysTick_Handler():
void SysTick_Handler(void)
{
HAL_IncTick();
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED) //OS开始跑了,才执行正常的调度处理
{
xPortSysTickHandler();
}
}
xPortSysTickHandler()函数中,只要xTaskIncrementTick()函数返回值不为pdFALSE ,则触发PendSV中断。
void xPortSysTickHandler( void )
{
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
* executes all interrupts must be unmasked. There is therefore no need to
* save and then restore the interrupt mask value as its value is already
* known - therefore the slightly faster vPortRaiseBASEPRI() function is used
* in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
* the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
由于变量uxSchedulerSuspended已经自加,则不能进入第一个if,则最终返回变量xSwitchRequired的值为pdFALSE。由以上函数知则触发不了PendSV中断,任务调度器被挂起。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/* Called by the portable layer each time a tick interrupt occurs.
* Increments the tick then checks to see if the new tick value will cause any
* tasks to be unblocked. */
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Minor optimisation. The tick count cannot change in this
* block. */
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
/* Increment the RTOS tick, switching the delayed and overflowed
* delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* See if this tick has made a timeout expire. Tasks are stored in
* the queue in the order of their wake time - meaning once one task
* has been found whose block time has not expired there is no need to
* look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The delayed list is empty. Set xNextTaskUnblockTime
* to the maximum possible value so it is extremely
* unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass
* next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
}
else
{
/* The delayed list is not empty, get the value of the
* item at the head of the delayed list. This is the time
* at which the task at the head of the delayed list must
* be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
* item value is the time at which the task at the head
* of the blocked list must be removed from the Blocked
* state - so record the item value in
* xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* It is time to remove the item from the Blocked state. */
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
/* Is the task waiting on an event also? If so remove
* it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Place the unblocked task into the appropriate ready
* list. */
prvAddTaskToReadyList( pxTCB );
/* A task being unblocked cannot cause an immediate
* context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
* only be performed if the unblocked task has a
* priority that is equal to or higher than the
* currently executing task. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/* Tasks of equal priority to the currently running task will share
* processing time (time slice) if preemption is on, and the application
* writer has not explicitly turned time slicing off. */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
#if ( configUSE_TICK_HOOK == 1 )
{
/* Guard against the tick hook being called when the pended tick
* count is being unwound (when the scheduler is being unlocked). */
if( xPendedTicks == ( TickType_t ) 0 )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
else
{
++xPendedTicks;
/* The tick hook gets called at regular intervals, even if the
* scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
return xSwitchRequired;
}
taskENTER_CRITICAL()函数进入临界区,关闭中断(避免执行后面程序时,被中断或任务切换所打断)。
BaseType_t xTaskResumeAll( void )
{
TCB_t * pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
/* If uxSchedulerSuspended is zero then this function does not match a
* previous call to vTaskSuspendAll(). */
configASSERT( uxSchedulerSuspended );
/* It is possible that an ISR caused a task to be removed from an event
* list while the scheduler was suspended. If this was the case then the
* removed task will have been added to the xPendingReadyList. Once the
* scheduler has been resumed it is safe to move all the pending ready
* tasks from this list into their appropriate ready list. */
taskENTER_CRITICAL();
{
将变量uxSchedulerSuspended自减,如果变量减至0,则表明任务调度器已经被恢复。
--uxSchedulerSuspended;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
判断当前任务总量是否大于0,如果当前创建任务总数不大于0,则恢复任务调度器无意义。
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
{
/* Move any readied tasks from the pending list into the
* appropriate ready list. */
判断等待就绪列表中是否有任务,如果有任务则将列表中任务全部移除,添加到就绪列表。在中断级恢复任务中,当调度器被挂起时,恢复任务不是恢复到就绪列表,而是等待就绪列表中。
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
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 )
{
/* A task was unblocked while the scheduler was suspended,
* which may have prevented the next unblock time from being
* re-calculated, in which case re-calculate it now. Mainly
* important for low power tickless implementations, where
* this can prevent an unnecessary exit from low power
* state. */
prvResetNextTaskUnblockTime();
}
恢复滴答定时器,在任务调度器被挂起时丢失的节拍数(只有任务调度器未被挂起时,节拍数才会自加)。任务被挂起时自加的变量xPendedTicks,恢复后将变量的值赋值给变量xPendedCounts。
/* 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. */
{
TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */
如果xPendedCounts大于0,则调用函数xTaskIncrementTick(),将丢失的节拍数进行加回。通过函数xTaskIncrementTick()的返回值判断是否需要任务切换,每补齐一次,则xPendedCounts自减。
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();
}
}
如果xYieldPending不等于pdFALSE则进行任务切换。宏configUSE_PREEMPTION(在FreeRTOSCofig.h中配置)判断是否是抢占式任务调度器,是则将xAlreadyYielded置1。函数taskYIELD_IF_USING_PREEMPTION()进行任务切换。
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;
}