一、临界段代码保护
1.1.临界段代码保护简介
临界段代码也叫做临界区,是指那些必须完整运行,不能被中断、任务调度所打断的代码段,适用的场景如:
- 外设:需严格按照时序初始化的外设,IIC、SPI 等
- 系统:系统自身需求
- 用户:用户需求,如 start_task 任务里面创建 task1、2、3,使用临界段代码保护,让这三个任务创建完毕,再按照优先级顺序执行
临界段代码保护的特点:
- 成对使用
- 支持嵌套
- 尽量保持临界耗时短
1.2.临界段代码保护函数介绍
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);
二、任务调度器的挂起与恢复
2.1.任务调度器的挂起与恢复函数介绍
挂起任务调度器,调用此函数不需要关闭中断,下表是相关函数:
函数 | 描述 |
---|---|
vTaskSuspendAll( ) | 挂起任务调度器 |
xTaskResumeAll( ) | 恢复任务调度器 |
任务调度器的挂起与恢复的作用:
- 与临界区不一样的是,挂起任务调度器,不会关闭中断
- 防止了任务之间的资源争夺,中断照样可以直接响应
- 挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以安全做到临界区的安全
下面代码是使用格式:
vTaskSuspendAll();
{
//内容代码
}
xTaskResumeAll();
2.2.源码解析
- 找到
vTaskDelay( x )
函数,右键进入定义它的地方,查看它的源码:
- 发现里面有一对临界段代码保护,恢复任务调度器函数有一个返回值,以此判断是否进行任务切换,如果没有任务切换,但是恢复的任务优先级比较高,也会进行一次任务切换,下图就是进行任务切换的函数:
- 右键进入
vTaskSuspendAll( )
函数定义的地方,第一个函数其实没有定义什么,只是一个接口,真正有用的是红色框的函数,一个变量的自加就可以挂起任务调度器,自加意味着可以支持嵌套,其中实现的原因是:任务调度器根本上执行的是任务切换,任务切换由 PendSV 的中断实现。
- PendSV 的中断又在 delay.c 文件里的 SysTick 的中断服务函数里,右键进入红色框的定义处。
- 进入之后发现,如果
xTaskIncrementTick( )
函数不等于 pdFALSE,就会触发 PendSV 的中断。
- 下面寄存器的第 28 位是有关触发 PendSV 的中断,下图是它的实现原理:
- 如果
xTaskIncrementTick( )
函数等于 pdFALSE,它函数里面会执行一下代码,xSwitchRequired
变量和uxSchedulerSuspended
变量初始化为 pdFALSE,如果uxSchedulerSuspended
变量在别的文件里进行了自加,就不会等于 pdFALSE,也不会进入if
语句了,而是进入下面的else
语句。
- 而
else
语句返回的值就是前面定义的 pdFLASE,如果xTaskIncrementTick( )
函数等于 pdFALSE,就不会触发 PendSV 的中断,任务切换也执行不了,相当于任务调度器被挂起
- 接下来研究恢复任务调度器的函数,右键进入恢复函数:
- 在前面进入挂起函数有一个变量自加,来记录被挂起多少次,接着恢复函数里就把该变量自减:
- 如果有任务被挂起了,就判断任务数量是否大于 0,如果等于 0 就意味着没有任务,接着进入
while
循环,判断就绪列表里面是否有任务,如果有任务,就清除:
- 接着判断恢复的任务优先级是否大于等于当前正在执行的任务优先级,如果是的话,就进行一次任务切换,在更新一次阻塞的时间:
- 当任务调度器被挂起来,它的滴答定时器随之也暂停的,恢复的时候就通过下图来补齐挂起的时候丢失的滴答节拍数: