4、任务调度算法(下):优先级调度、多级反馈队列、Linux CFS调度器在机顶盒中的应用
好,咱们接着聊调度算法。上一节我们把先来先服务、时间片轮转这些基础算法讲透了。这一节要聊的,才是机顶盒开发里真正天天打交道的家伙——优先级调度、多级反馈队列,还有Linux的CFS调度器。
说实话,我刚入行那会儿,觉得调度算法就是教科书上的理论。直到有一次,机顶盒在播放4K视频时按遥控器换台,画面卡了整整两秒。嗯,从那以后我才真正明白——调度算法选不对,用户就要砸电视。
4.1 优先级调度:谁急谁先上
优先级调度,说白了就是给每个任务贴个标签。标签数字越小,优先级越高,CPU就先伺候谁。
在机顶盒里,这个逻辑特别直观:
- 中断处理:遥控器按键、红外接收——优先级最高,必须秒响应
- 音视频解码:解码线程优先级次高,掉帧就是事故
- UI渲染:中等优先级,别让用户觉得卡就行
- 后台下载:最低优先级,有空再干
核心要点:优先级调度分两种——抢占式和非抢占式。机顶盒里几乎全是抢占式。高优先级任务一来,低优先级立马让位。
我遇到过一个问题:有个同事把UI线程优先级设得比解码线程还高。结果呢?视频一卡一卡的,但菜单动画流畅得飞起。用户投诉说「这机顶盒看视频像幻灯片」。后来我们把优先级调回来,问题就解决了。
警告:优先级反转是经典坑。低优先级任务拿着锁,高优先级任务等着锁,中间还有个中优先级任务抢CPU。结果高优先级任务反而被低优先级任务「反转」了。我曾经在调试一个直播卡顿问题时,查了三天才发现是优先级反转在作怪。
4.2 多级反馈队列:兼顾公平与效率
优先级调度有个毛病——低优先级任务可能永远得不到CPU。这就是所谓的「饥饿」问题。
多级反馈队列(MLFQ)就是来解决这个的。它的思路很巧妙:
- 设置多个队列,每个队列优先级不同
- 高优先级队列时间片短,低优先级队列时间片长
- 任务用完时间片还没完成,就降到下一级队列
- 定期把低队列的任务提升回来,防止饥饿
你想想看,这像什么?像公司里的晋升机制。新人(新任务)先在高优先级队列里试,如果干得快(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流畅体验?到时候见。