FreeRTOS调度器:抢占与轮转机制
空闲任务负责回收被删除任务的 TCB 和栈内存,所有用户任务全部阻塞时 CPU 也有任务可跑,避免调度器无任务可选。调度器持续监控所有任务状态(就绪 / 运行 / 阻塞),从就绪链表中选优先级最高的任务占用 CPU,状态变化时触发切换。假设系统中有3个任务,优先级:TaskA(高,优先级3)、TaskB(中,优先级2)、TaskC(低,优先级1)A:抢占式针对不同优先级,高优先级随时抢;时间片轮转
关注+星标公众号,不错过精彩内容
来源 | 嵌入式大杂烩
很多工作多年的嵌入式攻城狮都没明白FreeRTOS 调度器的原理。你要明白:调度器是整个 RTOS 的核心,决定"谁在跑、什么时候换人"。今天就来展开说说!
1. FreeRTOS调度器
调度器持续监控所有任务状态(就绪 / 运行 / 阻塞),从就绪链表中选优先级最高的任务占用 CPU,状态变化时触发切换。
FreeRTOS 默认两种策略并行工作:
-
抢占式调度:不同优先级之间,高优先级随时抢占,不管低优先级跑没跑完
-
时间片轮转:同优先级之间,每人轮流跑一个时间片(默认 1ms)
核心原则:优先级高于时间片。
这两种行为由 FreeRTOSConfig.h 中的两个宏独立控制:
/* FreeRTOSConfig.h */
#define configUSE_PREEMPTION 1 /* 1=抢占式,0=合作式(任务必须主动让步) */
#define configUSE_TIME_SLICING 1 /* 1=同优先级时间片轮转,0=关闭轮转 */
两个宏都开启才是默认的"抢占 + 时间片"模式;关掉 configUSE_PREEMPTION 则退化为合作式调度,任务必须调用 taskYIELD() 才切换。
2. 时钟节拍(SysTick)
SysTick 是调度器的心跳,默认 1000Hz(每 1ms 中断一次)。每次中断主要做两件事:tick 计数 +1 并检查阻塞任务,然后决定是否需要上下文切换切换:
xTaskIncrementTick() 内部除了唤醒延时到期任务,还包含时间片轮转判断逻辑——如果当前优先级就绪链表里还有其他任务,则返回 pdTRUE,触发切换:
这段代码说明:时间片轮转不是一个独立机制,它内嵌在 xTaskIncrementTick() 里,每次 SysTick 中断都会顺带判断。
3. 抢占式调度
3.1 运行流程
假设系统中有3个任务,优先级:TaskA(高,优先级3)、TaskB(中,优先级2)、TaskC(低,优先级1)
3.2 调度触发时机
|
调度触发点/调度点 |
说明 |
|---|---|
|
SysTick 中断 |
最常见,每 1ms 检查一次 |
|
任务主动阻塞 |
vTaskDelay()
、 |
|
任务创建 / 删除 / 改优先级 |
状态变化,重新调度 |
|
ISR 中唤醒高优先级任务 |
中断退出后触发 |
在 ISR 里唤醒任务后,需用 portYIELD_FROM_ISR() 通知调度器,不然即便唤醒了高优先级任务,也要等下一个 SysTick 才切换:
3.3 核心源码
vTaskSwitchContext() 的核心是 taskSELECT_HIGHEST_PRIORITY_TASK() 宏,展开后等效逻辑如下:
vTaskStartScheduler() 启动时做四件事:创建空闲任务 → 关中断 → 置 xSchedulerRunning = pdTRUE → 调用 xPortStartScheduler()(内部启动 SysTick 并切换到第一个任务)。
空闲任务不可缺:所有用户任务阻塞时 CPU 有任务可跑,同时负责回收被删除任务的 TCB 和栈资源。
4. 时间片轮转
4.1 运行流程
同优先级任务按就绪链表顺序轮流跑,每人一个时间片,用完切下一个:
4.2 配置与注意事项
#define configTICK_RATE_HZ 1000UL /* 时间片 = 1ms;改成 500 则为 2ms */
/* 延时必须用宏换算,不能写死 tick 数 */
vTaskDelay(pdMS_TO_TICKS(100));
时间片大小的取舍:太小 → 切换频繁,上下文保存/恢复的 CPU 开销上升;太大 → 同优先级任务响应延迟增大,按实际场景平衡。
5. 上下文切换
无论哪种调度触发,最终都走上下文切换,三步完成:
PendSV 优先级设为最低,目的是让所有业务中断先处理完,再做任务切换,不影响中断实时性。
PendSV_Handler 汇编实现的核心逻辑(ARM Cortex-M3 精简版):
6. 常见QA
Q:抢占式调度和时间片轮转的区别?A:抢占式针对不同优先级,高优先级随时抢;时间片轮转针对同优先级,按时间片顺序切换。两者同时工作,优先级优先。
Q:为什么上下文切换用 PendSV 而不直接在 SysTick 里切?A:SysTick 优先级比外部中断高,若直接切换会打断正在处理的中断。PendSV 优先级最低,所有中断处理完后才执行切换,保证中断实时性。
Q:空闲任务有什么用?能删掉吗?A:不能删。空闲任务负责回收被删除任务的 TCB 和栈内存,所有用户任务全部阻塞时 CPU 也有任务可跑,避免调度器无任务可选。
7. 常见问题
坑1:改了 configTICK_RATE_HZ,延时写死 tick 数频率从 1000Hz 改成 500Hz,vTaskDelay(1000) 实际延时变 2 秒。统一换成 pdMS_TO_TICKS() 即可避免。
坑2:高优先级任务死循环不让步高优先级任务循环体没有任何阻塞调用,低优先级任务永远抢不到 CPU,系统假死。高优先级任务循环里需要加阻塞操作,如vTaskDelay,主动释放,低优先级任务有机会运行
坑3:同优先级任务操作共享资源时间片轮转是"轮流",任意时刻只有一个任务在跑,共享资源照样需要加互斥锁,别因为"优先级一样"就省掉保护。
你在项目里遇到过调度相关的问题吗?评论区聊聊。
------------ END ------------
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐






所有评论(0)