4、任务调度算法(下):优先级调度、多级反馈队列、Linux CFS调度器在机顶盒中的应用

好,咱们接着聊调度算法。上一节我们把先来先服务、时间片轮转这些基础算法讲透了。这一节要聊的,才是机顶盒开发里真正天天打交道的家伙——优先级调度、多级反馈队列,还有Linux的CFS调度器。

说实话,我刚入行那会儿,觉得调度算法就是教科书上的理论。直到有一次,机顶盒在播放4K视频时按遥控器换台,画面卡了整整两秒。嗯,从那以后我才真正明白——调度算法选不对,用户就要砸电视。

4.1 优先级调度:谁急谁先上

优先级调度,说白了就是给每个任务贴个标签。标签数字越小,优先级越高,CPU就先伺候谁。

在机顶盒里,这个逻辑特别直观:

  • 中断处理:遥控器按键、红外接收——优先级最高,必须秒响应
  • 音视频解码:解码线程优先级次高,掉帧就是事故
  • UI渲染:中等优先级,别让用户觉得卡就行
  • 后台下载:最低优先级,有空再干

核心要点:优先级调度分两种——抢占式和非抢占式。机顶盒里几乎全是抢占式。高优先级任务一来,低优先级立马让位。

我遇到过一个问题:有个同事把UI线程优先级设得比解码线程还高。结果呢?视频一卡一卡的,但菜单动画流畅得飞起。用户投诉说「这机顶盒看视频像幻灯片」。后来我们把优先级调回来,问题就解决了。

警告:优先级反转是经典坑。低优先级任务拿着锁,高优先级任务等着锁,中间还有个中优先级任务抢CPU。结果高优先级任务反而被低优先级任务「反转」了。我曾经在调试一个直播卡顿问题时,查了三天才发现是优先级反转在作怪。

4.2 多级反馈队列:兼顾公平与效率

优先级调度有个毛病——低优先级任务可能永远得不到CPU。这就是所谓的「饥饿」问题。

多级反馈队列(MLFQ)就是来解决这个的。它的思路很巧妙:

  1. 设置多个队列,每个队列优先级不同
  2. 高优先级队列时间片短,低优先级队列时间片长
  3. 任务用完时间片还没完成,就降到下一级队列
  4. 定期把低队列的任务提升回来,防止饥饿

你想想看,这像什么?像公司里的晋升机制。新人(新任务)先在高优先级队列里试,如果干得快(I/O密集型),就留在高位。如果一直占着CPU不放(CPU密集型),就降级到低优先级队列,但时间片给得更长。

队列级别 优先级 时间片 典型任务
Q0 最高 8ms 遥控器响应、触摸事件
Q1 16ms 音视频解码、渲染
Q2 32ms UI动画、网络请求
Q3 64ms 后台下载、日志写入

我个人习惯在机顶盒里用三级反馈队列。为什么是三级?因为机顶盒的任务类型就三种:交互型(快响应)、流媒体型(持续吞吐)、后台型(不着急)。三级刚刚好,多了浪费,少了不够用。

小技巧:MLFQ的参数调优是个手艺活。时间片设太短,上下文切换开销大;设太长,交互响应慢。我一般从8ms起步,根据实际场景微调。记得用perf工具看上下文切换次数,超过每秒10万次就要警惕了。

4.3 Linux CFS调度器:公平不是平均

Linux从2.6.23开始用CFS(完全公平调度器)。它不讲优先级,讲「虚拟运行时间」。

CFS的核心思想很简单:每个任务都应该获得CPU时间的公平份额。但它不是平均主义——权重高的任务,虚拟时间走得慢,实际获得的时间就多。

看个例子:

// 假设两个任务
// 任务A:权重1024(普通优先级)
// 任务B:权重2048(高优先级)

// CFS计算虚拟运行时间
vruntime = 实际运行时间 * (1024 / 权重)

// 任务A运行10ms,vruntime增加10ms
// 任务B运行10ms,vruntime只增加5ms

// CFS每次选vruntime最小的任务运行
// 所以任务B会获得更多CPU时间

为什么CFS适合机顶盒?我总结了三点:

  • 公平性好:不会出现某个任务饿死的情况
  • 交互响应快:休眠唤醒的任务vruntime小,能快速获得CPU
  • 可调性好:通过nice值调整权重,精细控制

关键点:CFS的「完全公平」不是每个任务获得相同时间,而是每个任务获得与其权重成比例的时间。权重高的任务,实际运行时间更多。

我记得有一次调试机顶盒的EPG(电子节目指南)加载慢的问题。用top命令一看,EPG线程的vruntime特别大,说明它很久没被调度了。后来发现是另一个后台下载任务占用了太多CPU。我们用renice把下载任务的nice值调高(优先级降低),EPG加载就恢复正常了。

4.4 机顶盒中的调度策略选择

说了这么多,到底在机顶盒里怎么选?我给出一个实战建议:

场景 推荐策略 原因
直播播放 CFS + 实时优先级 解码线程设SCHED_FIFO,优先级99
点播播放 CFS + nice值调整 解码线程nice=-5,UI线程nice=0
后台下载 CFS默认 nice=10,让出CPU给前台任务
遥控器响应 中断线程化 + 高优先级 使用SCHED_FIFO,优先级80以上

避坑指南:我曾经把解码线程设成SCHED_FIFO优先级99,结果系统响应变得极其迟钝。为什么?因为解码线程一直占着CPU不放,连中断处理都抢不到时间。后来我把解码线程改成SCHED_RR,时间片设20ms,问题就解决了。实时优先级不是越高越好,要留点余量给系统关键线程。

最后说一句:调度算法没有银弹。你在实验室里跑得再好的参数,到了用户家里可能就翻车。因为用户家的机顶盒可能同时跑着直播、录像、后台升级三个任务。所以我的习惯是:先做压力测试,模拟最坏情况,再调调度参数。

嗯,这一节就到这里。下一节我们聊内存管理——机顶盒只有256MB内存,怎么跑出4K流畅体验?到时候见。