关注+星标公众,不错过精彩内容

来源 | 嵌入式大杂烩

很多工作多年的嵌入式攻城狮都没明白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()

xQueueReceive() 等

任务创建 / 删除 / 改优先级

状态变化,重新调度

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 ------------

基于MCU的冰箱压缩机控制方案

免费测评并赠送Cortex-M23开发板,解锁嵌入式开发新技能!

部分元器件涨价近10倍,小公司最担心什么?

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐