嵌入式|RTOS教学——FreeRTOS基础2:任务调度
在实时操作系统(RTOS,如 FreeRTOS、RT-Thread 等)中,任务调度(Task Scheduling) 是内核的核心功能,负责按照一定规则在多个任务之间分配 CPU 执行时间,确保系统高效、有序地运行,同时满足实时性要求(即关键任务能在规定时间内完成)。
简单来说,任务调度就像一个 “CPU 管理员”:系统中可能有多个任务(如传感器采集、数据处理、显示刷新等)等待执行,但 CPU 同一时间只能运行一个任务,调度器的作用就是决定 “下一个该让哪个任务占用 CPU”,以及 “什么时候切换任务”。
一、任务调度的核心目标
任务调度的设计围绕以下核心目标展开,尤其在实时系统中至关重要:
- 实时性:确保高优先级的任务(如紧急事件处理)能优先获得 CPU 资源,在规定的 “截止时间” 内完成(例如:火灾报警任务必须在 10ms 内响应)。
- 公平性:在保证实时性的前提下,让低优先级任务也能获得执行机会(避免 “饿死”)。
- 高效性:任务切换的时间开销要尽可能小(通常是微秒级),避免占用过多 CPU 资源。
- 可预测性:调度行为必须是确定的(相同条件下,调度结果一致),便于开发者评估系统性能。
二、任务调度的基本概念
理解任务调度前,需先明确几个关键概念:
1. 任务状态
任务在生命周期中会处于不同状态,调度器通过状态管理决定任务是否有资格获得 CPU:
- 运行态(Running):当前正在占用 CPU 执行的任务(系统中同一时间只能有一个运行态任务)。
- 就绪态(Ready):任务已准备好执行,只需获得 CPU 即可运行(因优先级低于当前运行任务,暂时等待)。
- 阻塞态(Blocked):任务因等待某个事件(如延时结束、信号量释放、队列有数据)而暂停,此时不会参与 CPU 竞争(即使优先级高也不会运行)。
- 挂起态(Suspended):任务被强制暂停(需通过特定接口唤醒),与阻塞态的区别是:挂起态不会因事件触发而自动恢复,必须显式解除挂起。
调度器的核心工作之一,就是维护这些状态的转换(如:延时结束后,任务从阻塞态转为就绪态)。
2. 任务优先级
每个任务都有一个优先级(通常是 0~31 的整数,数值越大优先级越高),是调度器决定执行顺序的核心依据。
例如:FreeRTOS 中,优先级 5 的任务会优先于优先级 3 的任务获得 CPU;若两个任务优先级相同,则按 “时间片轮转” 方式交替执行。
三、常见的调度算法(调度规则)
不同 RTOS 采用的调度算法不同,核心是 “如何从就绪态任务中选择下一个运行的任务”。以下是最常用的 3 种算法:
1. 抢占式调度(Preemptive Scheduling)(主要!)
核心规则:高优先级任务会抢占低优先级任务的 CPU 使用权。只有当高优先级任务阻塞(如调用vTaskDelay()
、等待信号量等)或结束时,低优先级任务才能获得运行机会。
- 例:任务 A(优先级 2,运行中)正在处理数据,此时任务 B(优先级 3)从阻塞态转为就绪态,调度器会立即暂停任务 A,切换到任务 B 执行。
- 优势:保证高优先级任务的实时性(响应快),是实时系统的主流选择(FreeRTOS、RT-Thread 默认支持)。
2. 协作式调度(Cooperative Scheduling)(很少用)
核心规则:任务必须主动 “让出” CPU(如调用延时函数、等待事件),低优先级任务才有可能执行;高优先级任务即使就绪,也不能打断低优先级任务。
- 例:任务 A(优先级 2,运行中)若不主动让出 CPU,任务 B(优先级 3,就绪态)会一直等待,直到任务 A 调用
vTaskDelay()
后才获得执行机会。 - 劣势:实时性差(高优先级任务可能因低优先级任务 “霸占” CPU 而延迟),仅用于简单场景(如早期嵌入式系统)。
3. 时间片轮转调度(Round-Robin Scheduling)(主要!)
核心规则:FreeRTOS 会采用时间片轮询策略。每个任务会被分配一个固定的时间片(由configTICK_RATE_HZ
配置,通常为 1ms~10ms),任务运行完一个时间片后会主动让出 CPU,切换到下一个同优先级任务。
- 例:任务 A 和任务 B 优先级均为 1,时间片设为 1ms:任务 A 执行 1ms 后,调度器切换到任务 B 执行 1ms,如此循环,交替执行。
- 适用场景:处理优先级相同的任务,保证公平性(避免某一任务长期占用 CPU)。
FreeRTOS的调度策略就是优先级抢占 + 时间片轮转结合的方式,当任务优先级相同时使用时间片轮转,任务优先级不同时使用抢占式调度。
四、任务切换的底层原理
当调度器决定切换任务时(如高优先级任务就绪、当前任务进入阻塞态),会触发 “任务切换” 操作,底层流程可简化为 3 步:
保存上下文(Context Save):
将当前运行任务的 CPU 寄存器(如程序计数器 PC、栈指针 SP、通用寄存器 R0-R15 等)的值保存到该任务的栈空间中(每个任务有独立的栈),记录任务当前的执行位置和状态。切换任务控制块(TCB):
调度器从 “就绪列表” 中选出下一个要运行的任务(按优先级或时间片规则),将内核的 “当前任务指针” 指向新任务的任务控制块(TCB)——TCB 是存储任务信息的结构体(包含栈指针、优先级、状态等)。恢复上下文(Context Restore):
从新任务的栈空间中读取之前保存的寄存器值,恢复到 CPU 中,新任务从上次暂停的位置继续执行。
关键:任务切换依赖 CPU 的异常机制(如 ARM Cortex-M 内核的 PendSV 异常),这是一种低优先级异常,确保任务切换不会打断高优先级中断(如定时器、外部中断),保证系统稳定性。
五、FreeRTOS 中的调度器配置与启动
以 FreeRTOS 为例,任务调度的核心操作如下:
1. 配置调度器:在 FreeRTOSConfig.h
中通过宏定义配置调度算法:
#define configUSE_PREEMPTION 1 // 1=启用抢占式调度,0=协作式
#define configUSE_TIME_SLICING 1 // 1=启用相同优先级任务的时间片轮转
#define configTICK_RATE_HZ 1000 // 系统时钟节拍(1ms 一次,用于计时和调度)
2. 启动调度器:所有任务创建完成后,调用 vTaskStartScheduler()
启动调度器,此后 CPU 由调度器管理:
int main(void)
{// 初始化硬件(GPIO、串口等)MX_GPIO_Init();MX_USART1_UART_Init();// 创建任务xTaskCreate(Task1, "Task1", 128, NULL, 1, NULL);xTaskCreate(Task2, "Task2", 128, NULL, 2, NULL);// 启动调度器(从此处开始,任务开始运行,main 函数不再执行)vTaskStartScheduler();// 若调度器启动失败(如内存不足),才会执行到这里while(1);
}
六、总结
任务调度是 RTOS 的 “心脏”,其核心价值在于:在单 CPU 环境下,通过合理的调度算法,让多个任务 “看似同时运行”,并保证关键任务的实时性。
理解任务调度的关键是:
- 任务状态如何转换(运行→就绪→阻塞);
- 调度算法如何选择下一个任务(抢占式、时间片轮转);
- 任务切换的底层机制(上下文保存与恢复)。