【5】STM32·FreeRTOS·临界段保护与调度器挂起
目录
一、临界段代码保护简介
二、临界段代码保护函数介绍
2.1、调用示例
2.2、内部实现
三、任务调度器的挂起和恢复
3.1、调用示例
3.2、内部实现
一、临界段代码保护简介
什么是临界段:临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段
适用场合如:
1、外设 | 需严格按照时序初始化的外设:IIC、SPI等等 |
2、系统 | 系统自身需求 |
3、用户 | 用户需求 |
中断和任务调度可以打断当前程序的运行
二、临界段代码保护函数介绍
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段以后再开中断
函数 | 描述 |
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段 |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段 |
2.1、调用示例
任务级临界区调用格式示例:
taskENTER_CRITICAL();
{
... /* 临界区 */
}
taskEXIT_CRITICAL();
中断级临界区调用格式示例:
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
{
... /* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status);
特点
1、成对使用
2、支持嵌套
3、尽量保持临界段耗时短
2.2、内部实现
任务级进入临界段:taskENTER_CRITICAL()
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
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 );
}
}
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
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. */
/* *INDENT-OFF* */
msr basepri, ulNewBASEPRI
dsb
isb
/* *INDENT-ON* */
}
}
任务级退出临界段:taskEXIT_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
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* */
}
}
中断级进入临界段:taskENTER_CRITICAL_FROM_ISR()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
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()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#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. */
/* *INDENT-OFF* */
msr basepri, ulBASEPRI
/* *INDENT-ON* */
}
}
三、任务调度器的挂起和恢复
挂起任务调度器,调用此函数不需要关闭中断
函数 | 描述 |
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
3.1、调用示例
vTaskSuspendAll();
{
··· /* 内容 */
}
xTaskResumeAll();
1、与临界区不一样的是,挂起任务调度器,未关闭中断
2、它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应
3、挂起调度器的方式,适用于临界区位于任务与任务之间
4、既不用去延时中断,又可以做到临界区的安全
3.2、内部实现
挂起任务调度器:vTaskSuspendAll()
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();
}
当变量 uxSchedulerSuspended 的值不为 0,将会导致 Systick 无法触发 PendSV 中断,即挂起任务调度器
恢复任务调度器:xTaskResumeAll()
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;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
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();
}
/* 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. */
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;
}
当变量 uxSchedulerSuspended 的值等于 0,则允许调度
1、当任务数量大于 0 时,恢复调度器才有意义,如果没有一个已创建的任务就无意义
2、移除等待就绪列表中的列表项,恢复至就绪列表,直到 xPendingReadyList 列表为空
3、如果恢复的任务优先级比当前正在执行任务优先级更高,则将 xYieldPending 赋值为 pdTRUE,表示需要进行一次任务切换
4、在调度器被挂起的期间内,是否有丢失未处理的滴答数。xPendedTicks 是丢失的滴答数,有则调用 xTaskIncrementTick() 补齐丢失的滴答数
5、判断是否允许任务切换
6、返回任务是否已经切换,已经切换返回 pdTRUE,反之返回 pdFALSE