扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

FreeRTOS调度器:抢占与轮转机制

03/02 09:20
160
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大家好,我是杂烩君。本次我们来了解 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:同优先级任务操作共享资源时间片轮转是"轮流",任意时刻只有一个任务在跑,共享资源照样需要加互斥锁,别因为"优先级一样"就省掉保护。

 

你在项目里遇到过调度相关的问题吗?评论区聊聊。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!