调度算法基础:调度器的职责、调度时机与性能指标

各位同学,今天我们聊聊调度算法的基础。说实话,调度器这东西,在实时系统里就是“大脑”一样的存在。我刚开始做嵌入式时,总觉得调度嘛,不就是轮着来?后来被项目坑过几次,才明白这里面的门道有多深。

一、调度器的职责——它到底在忙什么?

调度器的核心任务,说白了就三件事:选谁跑、跑多久、跑完怎么办

  • 任务选择:从就绪队列里挑一个最合适的任务。嗯,这里要注意,不是随便挑,得看优先级、截止时间、资源占用等等。
  • 上下文切换:把当前任务的状态保存好,再把新任务的状态恢复出来。我见过不少新手在这里栽跟头——寄存器没保存全,回来就崩了。
  • 资源管理:协调共享资源,防止死锁。我曾经在一个多任务项目中,因为忘了释放互斥锁,导致三个任务互相等待,系统直接“死机”。

我个人习惯:在设计调度器时,先把任务分类。周期性的、突发性的、紧急的,分清楚再定策略。别一上来就写代码,否则后面改起来想哭。

二、调度时机——什么时候触发调度?

调度不是随时都能做的。触发调度的时机,主要有两种模式:

1. 事件驱动调度

事件来了,才调度。比如外部中断、定时器溢出、消息到达。这种方式的优点是响应快,缺点是如果事件太多,调度器会被频繁打断。

举个例子:你在做一个按键响应系统。用户按一下键,触发中断,调度器立刻把按键处理任务拉进来跑。跑完继续干别的。这就是事件驱动。

避坑指南:我曾经在一个工业控制器项目里,事件驱动调度做得太“积极”,结果中断嵌套了五层,栈空间直接爆了。后来我加了个中断优先级屏蔽,才稳住。

2. 时间片轮转调度

每个任务分一个固定时间片,轮流跑。时间片到了,强制切换。这种方式公平,但实时性差——你想想看,如果紧急任务刚好轮到最后一个时间片,那得等多久?

时间片大小的选择是个学问。太短,上下文切换开销大;太长,响应延迟高。我一般建议:时间片 = 系统时钟滴答周期的 10~100 倍。具体多少,得看你任务的执行时间分布。

// 一个简单的时间片轮转调度伪代码
void scheduler() {
    while (1) {
        task = get_next_task();  // 轮转取任务
        set_timer(TIME_SLICE);   // 设置时间片
        task->run();             // 执行任务
        // 时间片到,自动触发调度
    }
}

三、调度性能指标——怎么评价调度器好不好?

调度器好不好,不能光靠感觉。我一般用三个指标来衡量:

指标 定义 我的经验
吞吐量 单位时间内完成的任务数 吞吐量高不代表实时性好。我曾经优化过一个批处理系统,吞吐量翻倍了,但紧急任务反而被堵死了。
响应时间 从任务就绪到开始执行的时间 这是实时系统的命门。我一般要求响应时间不超过任务周期的 10%。
截止时间 任务必须在规定时间内完成 硬实时系统里,错过截止时间就是事故。软实时系统可以偶尔超时,但不能频繁。

注意:这三个指标往往是矛盾的。想提高吞吐量,就得让任务跑久一点,但响应时间就长了。想保证截止时间,就得预留资源,但吞吐量就下来了。没有完美的调度器,只有适合场景的调度器。

四、知识体系结构图

下面这张图,是我自己总结的调度算法基础结构。你看一眼,就能明白今天讲的内容是怎么串起来的。

调度算法基础 调度器职责 任务选择 上下文切换 资源管理 调度时机 事件驱动 时间片轮转 性能指标 吞吐量 响应时间 截止时间 三者相互影响,需根据场景权衡 没有最好的调度器,只有最合适的

五、一点个人感悟

调度算法这东西,理论看着简单,实际用起来全是坑。我做了十几年嵌入式,换过七八种调度策略,最后还是觉得:理解你的任务特性,比背调度算法更重要

你想想看,如果你的任务都是周期性的、执行时间稳定的,那时间片轮转就挺好。但如果任务有突发性、有紧急程度差异,那事件驱动加优先级调度更靠谱。别死磕一种策略,灵活点。

最后说一句:调度器是实时系统的“心脏”。心脏跳得好,系统才稳。多花点时间在设计上,后面调试能省一半的功夫。

专注资料整理