目录
一、临界段代码保护简介
二、临界段代码保护函数介绍
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, ulNewBASEPRIdsbisb
/* *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, baseprimsr basepri, ulNewBASEPRIdsbisb
/* *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;}#endiftaskYIELD_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