4. RTOS内存管理机制
调度器,说白了就是RTOS的大脑。它决定哪个任务该跑、跑多久、什么时候换人。我见过不少项目,功能逻辑写得挺好,结果调度策略选错了,系统直接崩在产线上。嗯,今天咱们就把这四种调度方式掰开揉碎了讲清楚。
4.1 基于优先级的抢占式调度
这是RTOS里最常用的调度方式。每个任务都有一个优先级,调度器永远让最高优先级的就绪任务先跑。一旦有更高优先级的任务就绪,当前任务立刻被挂起——这就是“抢占”。
核心规则:高优先级任务可以打断低优先级任务的执行。优先级数字越小,优先级越高(不同RTOS可能相反,以实际手册为准)。
我在一个工业控制项目里遇到过这种情况:一个数据采集任务优先级设低了,结果被几个通信任务轮番抢占,采集周期直接翻了三倍。后来把采集任务优先级提到最高,问题才解决。
| 特性 | 说明 |
|---|---|
| 响应时间 | 高优先级任务响应最快,适合实时性要求高的场景 |
| CPU利用率 | 高,因为CPU总在处理最重要的任务 |
| 风险 | 优先级反转、死锁、低优先级任务饿死 |
我曾经踩过的坑:三个任务用了相同的优先级,结果调度器不知道选谁,系统行为变得不可预测。记住,同优先级任务需要配合时间片轮转或协作式调度。
4.2 时间片轮转调度
同优先级的任务,每个分一个固定时间片,轮流执行。时间片到了就换下一个。说白了就是“大家轮流来,谁也别占着不放”。
我个人习惯把时间片设成10-50ms。太短了切换开销大,太长了实时性变差。你想想看,如果时间片设成500ms,一个低优先级任务可能把高优先级任务堵死——虽然它们优先级相同,但实际效果很糟糕。
// 伪代码:时间片轮转调度逻辑
void scheduler_tick(void) {
current_task->remaining_ticks--;
if (current_task->remaining_ticks == 0) {
// 时间片用完,切换到同优先级的下一个任务
current_task->remaining_ticks = TIME_SLICE;
task_yield();
}
}
建议:时间片轮转适合交互式任务或批处理任务。实时性要求高的任务,用抢占式调度更靠谱。
4.3 协作式调度
协作式调度,任务自己决定什么时候让出CPU。调度器不会主动打断任务。任务必须显式调用task_yield()或等待事件才能切换。
这种调度方式简单,没有抢占带来的竞态问题。但风险也很明显——如果一个任务死循环或者忘了让出CPU,整个系统就卡死了。我在一个物联网终端项目里用过协作式调度,因为任务都很短,而且每个任务都乖乖地在循环末尾调用task_delay(),跑得很稳。
| 对比项 | 抢占式 | 协作式 |
|---|---|---|
| 切换时机 | 任何时刻 | 任务主动让出 |
| 实时性 | 高 | 低 |
| 代码复杂度 | 高(需要处理临界区) | 低 |
| 适用场景 | 实时控制、通信 | 简单循环、状态机 |
注意:协作式调度下,一个任务的bug可能拖垮整个系统。我曾经见过一个新手把while(1)里忘了加延时,结果其他任务永远得不到执行。
4.4 调度器启动与挂起
调度器不是一开始就工作的。系统启动时,通常先初始化硬件、创建任务,最后才启动调度器。启动后,调度器接管CPU控制权,开始按策略分配任务。
// 典型启动流程
void main(void) {
hardware_init(); // 初始化硬件
os_kernel_init(); // 初始化内核
task_create(task1, ...);
task_create(task2, ...);
os_scheduler_start(); // 启动调度器,不再返回
}
挂起调度器,就是暂时停止任务切换。这在某些特殊场景下很有用,比如:
- 系统进入低功耗模式前
- 执行一段不可重入的代码
- 调试时想单步执行
我个人的经验:挂起调度器的时间一定要短。我曾经在中断里挂起调度器做复杂计算,结果高优先级任务响应延迟,被客户投诉了。挂起时间建议控制在100微秒以内。
4.5 四种调度方式的关系与选择
实际项目中,这四种方式经常混合使用。比如:高优先级任务用抢占式,同优先级任务用时间片轮转,低优先级任务用协作式。调度器启动后,根据任务状态动态切换策略。
总结一下:没有最好的调度方式,只有最合适的。抢占式保证实时性,时间片轮转保证公平性,协作式保证简单性。调度器启动后,系统就进入了多任务世界——嗯,这才是RTOS真正开始干活的时候。