根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
临界值的保护
临界段简单说明:一段在指向的时候不能被中断的代码段。
那么什么情况下临界段会被打断?
系统调度。在FreeRTOS,系统调度最终也是产生PendSV中断,在PendSV Handler里面实现任务的切换。(实际上也是中断)
外部中断。
所以希望临界段是否被中断取决于中断。
中断一打开,临界值可以被中断。
中断一关闭,临界值不可以被中断。
在初谈CM3内核时,有讲过PRIMASK、FAULTMASK和BASEPRI这三个特殊寄存器。FreeRTOS中断与它们有关。
关中断
FreeRTOS关中断的函数分不带返回值和带返回值两种。
关中断不带返回值
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
//不带返回值的关中断函数,不能嵌套,不能在中断里面使用
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;//待写入BASEPRI的值,即新值,优先级的阀门
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}//将configMAX_SYSCALL_INTERRUPT_PRIORITY的值写入BASEPRI寄存器,实现部分关中断。(依据阀门判断)
}
在往BASEPRI写入新值时,不用先将BASEPRI的旧值保存起来,即不管当前中断状态,所以自然不能在中断里面调用。
关中断带返回值
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
//带返回值的关中断函数,能嵌套,能在中断里面使用
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
在往BASEPRI写入新值时,先将BASEPRI的值保存起来,在更新完BASEPRI的新值时,将之前保存好的BASEPRI的旧值返回,返回的值作为形参传入开中断函数。
开中断
//不带中断保护的开中断函数,与portDISABLE_INTERRUPTS()成对使用
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
//带中断保护的开中断函数,与portSET_INTERRUPT_MASK_FROM_ISR()成对使用
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
开关中断函数我们已经有了,那么我们可以计划了解进入/退出临界值
进入临界值
进入临界值,不带中断保护版本,不能嵌套
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
/* 在 portmacro.h 中定义 */
#define portENTER_CRITICAL() vPortEnterCritical()
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++; //临界段嵌套计数器,默认初始化0xaaaa aaaa
if( uxCriticalNesting == 1 ) //即一层嵌套
{
//要确保当前没有中断活动,即内核外设SCB中的中断和控制寄存器SCB_ICSR的低8位为0
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
临界段嵌套计数器在调度器启动时会被重新初始化为0:
vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0。
进入临界值,带中断保护版本,能嵌套
实际上就是上面的关中断带返回值函数。即要想进入临界值,关带返回值中断。
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/* 在 portmacro.h 中定义 */
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
退出临界值
退出临界值,不带中断保护版本,不能嵌套
/* 在 task.h 中定义 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
/* 在 portmacro.h 中定义 */
#define portEXIT_CRITICAL() vPortExitCritical()
void vPortExitCritical( void )
{
//configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
退出临界值,带中断保护版本,能嵌套
实际上就是上面的开中断带返回值函数。即要想退出临界值,开带返回值中断。
/* 在 task.h 中定义 */
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
/* 在 portmacro.h 中定义 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
临界值的应用
在FreeRTOS中,对临界值的保护出现在两种场合。
中断场合
非中断场合
中断场合的临界值应用
/* 在中断场合,临界段可以嵌套 */
{
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
/* 临界段代码 */
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
非中断场合的临界值应用
/* 在非中断场合,临界段不能嵌套 */
{
/* 进入临界段 */
taskENTER_CRITICAL();
/* 临界段代码 */
/* 退出临界段*/
taskEXIT_CRITICAL();
}