FreeRTOS临界段代码保护及调度器挂起与恢复
FreeRTOS临界段代码保护及调度器挂起与恢复
临界保护区
什么是临界段:临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段(在rtos中优先级高于系统管理的中断还是可以打断保护区内的代码)
使用场景:时序产生时(IIC,SPI等),系统自身需求,用户需求。
临界区是直接屏蔽了中断,系统任务调度靠中断,ISR也靠中断
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断
以下函数都是使用开关中断的方式来实现保护
函数 | 描述 |
---|---|
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段 |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段 |
使用示例
任务级
taskENTER_CRITICAL() ;
{
… … /* 临界区 */
}
taskEXIT_CRITICAL() ;
中断级
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
{
… … /* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );
注意事项:
- 尽量保持临界段耗时短(因为该函数是用==开关中断的方式==,所以保护区内的代码耗时尽量短,若耗时久则会造成延时中断)
- 成对使用
- 支持嵌套
嵌套实现
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS(); //关中断函数
uxCriticalNesting++; //该变量在任务调度函数vTaskStartScheduler中初始化为0 通过该变量来实现保护区嵌套 同时达到成对使用的效果
/* 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 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting ); //判断uxCriticalNesting是否为0 ,为0则报错 调试作用
uxCriticalNesting--;
if( uxCriticalNesting == 0 ) //通过该变量来实现保护区嵌套
{
portENABLE_INTERRUPTS(); //开中断函数
}
}
任务调度器挂起与恢复
挂起任务调度器, 调用此函数不需要关闭中断(用于保护代码执行,使其不被打断)
函数 | 描述 |
---|---|
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器(有返回值,用于判断是否要任务切换) |
使用格式
vTaskSuspendAll() ;
{
… … /* 内容 对该内容进行保护*/
}
xTaskResumeAll();
特点
- 与临界区不一样的是,挂起任务调度器,未关闭中断;
- 它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应
- 挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全
函数内部实现
vTaskSuspendAll() ;
vTaskDelay(10); 延时函数内部使用了任务调度器,下图所示
vTaskSuspendAll() ;再跳转到其 内部实现 ,可见只有一个变量在自增,
前面我们提到过任务调度是由**PendSV中断来实现任务调度的,故此我们可以猜想是不是通过uxSchedulerSuspended变量的自增来实现PendSV**的悬起 来挂起任务调度器呢
为了验证这个猜想我们调到ystick中断服务函数
我们可以发现这一步操作就是给PendSV悬起的操作(使Systick无法触发该中断)
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
跳转到其宏定义可发先,该操作就是再给0xe000ed04寄存器的28位(1UL << 28UL)置1
#define portNVIC_INT_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
该为正好是PendSV的悬起开关(下图Cortex-M3内核手册131)
判断了其内部操作,我们再看看它的进入条件
if( xTaskIncrementTick() != pdFALSE )
该函数是一个返回值为xSwitchRequired变量的函数
跳转到其函数内部可以发现 它对uxSchedulerSuspended变量的值进行了判断
而 uxSchedulerSuspended 的初值在系统中早有定义为pdFALSE
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE;
但是在我们调用挂起函数vTaskSuspendAll() 后该变量会自增故不会执行if中的语句
我们再跳转到该if对应的else
发现他并没有对返回值xSwitchRequired进行操作即返回原值pdFALSE
总结:
vTaskSuspendAll() 该函数是通过使uxSchedulerSuspended自增来实现任务调度器(PendSV)的挂起
xTaskResumeAll();
同样进入函数内部
与之前的与之前任务保护区的实现由异曲同工之妙
简述一下该函数内部实现的功能(初次接触还未能全部理解,稍作总结)
- 等待就绪列表的任务恢复到就绪列表中再按优先级给任务排序(任务挂起后是把任务放到等待就绪列表中)
- 更新阻塞时间(任务创建中有提到过)
- 恢复滴答定时器在任务调度器被挂起时所缺失的节拍数(xTaskIncrementTick()函数实现)
- 任务切换
如有不对 欢迎各位大神指正