5.9 调度算法
调度算法
1 重要概念
在 FreeRTOS 中,任务在任意时刻只会处于一种状态。
对于单核单片机系统来说,同一时刻只能有 一个任务处于运行态,也就是正在使用处理器。
其余没有运行的任务,通常处于以下三种状态之一:
- 就绪态(Ready):任务已经具备运行条件,等待调度器选中
- 阻塞态(Blocked):任务在等待某个事件发生
- 挂起态(Suspended):任务被人为暂停,不参与调度
其中,调度器只会从 就绪态任务 中选择任务运行,而且规则是:
永远选择优先级最高的就绪态任务进入运行态。
2 阻塞态任务等待什么
阻塞态任务本质上是在等待“事件”。
这些事件主要分为两类:
2.1 时间相关事件
例如:
- 延时一段时间
- 等待超时
这类事件的特点是:
时间到了,任务就从阻塞态进入就绪态。
它常用于:
- 周期性任务
- 超时处理
2.2 同步事件
例如任务在等待某些信息或资源:
- 等待任务通知
- 等待队列数据
- 等待事件组
- 等待信号量
- 等待互斥量
这类事件通常由其他任务或中断服务程序触发。
3 什么是调度算法
所谓调度算法,就是:
调度器根据一定规则,决定哪个就绪态任务可以进入运行态。
调度算法主要影响两个问题:
- 高优先级任务能不能优先执行
- 同优先级任务之间如何分配 CPU
4 配置调度算法的主要宏
调度算法主要通过 FreeRTOSConfig.h 中的几个配置项来决定:
configUSE_PREEMPTIONconfigUSE_TIME_SLICINGconfigIDLE_SHOULD_YIELD
另外还有一个高级选项:
configUSE_TICKLESS_IDLE
它主要用于低功耗省电,这里先不展开。
5 是否允许抢占
5.1 可抢占调度
由下面这个宏控制:
#define configUSE_PREEMPTION 1表示系统采用 可抢占调度。
它的特点是:
一旦更高优先级任务进入就绪态,就可以立即抢占当前正在运行的低优先级任务。
这也是 FreeRTOS 最常用的调度方式。
5.2 合作调度
如果设置为:
#define configUSE_PREEMPTION 0表示系统采用 合作调度模式。
它的特点是:
- 高优先级任务即使就绪,也不能马上执行
- 必须等待当前任务主动让出 CPU
- 如果当前任务一直不让出 CPU,其他任务就很难运行
因此,在合作调度下,优先级的作用会大大减弱。
6 是否启用时间片轮转
6.1 时间片轮转
由下面这个宏控制:
#define configUSE_TIME_SLICING 1它表示:
同优先级的多个就绪态任务,可以按时间片轮流执行。
也就是说:
- 这个任务执行一个时间片
- 下一个同优先级任务再执行一个时间片
这样可以让同优先级任务轮流获得 CPU。
6.2 不使用时间片轮转
如果设置为:
#define configUSE_TIME_SLICING 0表示同优先级任务之间 不轮流执行。
此时:
- 当前任务会一直运行
- 直到它主动放弃 CPU
- 或者被更高优先级任务抢占
所以,没有时间片轮转时,同优先级任务不一定能公平运行。
7 空闲任务是否让步
由下面这个宏控制:
#define configIDLE_SHOULD_YIELD 1它表示:
当空闲任务和用户任务处于同优先级时,空闲任务是否主动让出 CPU 给用户任务。
7.1 让步
如果设置为 1:
- 空闲任务每次循环时都会主动检查
- 如果有同优先级用户任务,就让用户任务先运行
7.2 不让步
如果设置为 0:
- 空闲任务和同优先级用户任务按普通规则竞争 CPU
- 不会特殊照顾用户任务
一般来说,空闲任务让步更常见。
8 常见调度组合
FreeRTOS 中常见的几种调度组合如下:
| 配置项 | A | B | C | D | E |
|---|---|---|---|---|---|
configUSE_PREEMPTION | 1 | 1 | 1 | 1 | 0 |
configUSE_TIME_SLICING | 1 | 1 | 0 | 0 | x |
configIDLE_SHOULD_YIELD | 1 | 0 | 1 | 0 | x |
| 说明 | 常用 | 很少用 | 很少用 | 很少用 | 几乎不用 |
其中:
- A:可抢占 + 时间片轮转 + 空闲任务让步
- B:可抢占 + 时间片轮转 + 空闲任务不让步
- C:可抢占 + 非时间片轮转 + 空闲任务让步
- D:可抢占 + 非时间片轮转 + 空闲任务不让步
- E:合作调度
最常用的是 A。
9 抢占与否的效果
如果配置为:
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1表示允许抢占。
效果是:
高优先级任务一旦就绪,就能马上执行。
如果配置为:
#define configUSE_PREEMPTION 0
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1表示不允许抢占。
效果是:
即使高优先级任务已经就绪,也必须等当前任务主动让出 CPU 后才能运行。
因此,不抢占时,如果某个任务一直不主动让出 CPU,其他任务就会长期无法执行。
10 时间片轮转与否的效果
如果配置为:
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1表示启用时间片轮转。
效果是:
同优先级任务会在 Tick 中断推动下轮流执行。
如果配置为:
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 0
#define configIDLE_SHOULD_YIELD 1表示不启用时间片轮转。
效果是:
同优先级任务不会因为 Tick 到来而自动轮换,当前任务会持续运行,直到主动放弃 CPU 或被高优先级任务抢占。
11 空闲任务让步与否的效果
如果配置为:
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1表示空闲任务让步。
效果是:
空闲任务每次循环都会主动让出 CPU 给同优先级用户任务。
如果配置为:
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 0表示空闲任务不让步。
效果是:
空闲任务和同优先级用户任务按普通时间片轮转方式运行。
12 小结
FreeRTOS 的调度算法,本质上就是决定 哪个就绪态任务进入运行态 的规则。
它主要由三个配置项决定:
configUSE_PREEMPTION:是否允许高优先级任务抢占低优先级任务configUSE_TIME_SLICING:同优先级任务是否按时间片轮流执行configIDLE_SHOULD_YIELD:空闲任务是否主动让步给同优先级用户任务
其中最常用的配置是:
- 可抢占
- 时间片轮转
- 空闲任务让步
也就是最典型的 FreeRTOS 调度方式。
