4、实时优先级与时间片:优先级范围(0-99)、优先级继承协议、时间片分配机制
好,咱们今天聊点硬核的——实时优先级和时间片。这部分内容,说白了你要是搞不清楚,写出来的实时任务就是“假实时”。我见过太多人把优先级设成99就觉得万事大吉,结果系统一跑,该抖的还是抖,该丢的还是丢。
嗯,咱们一步步来拆解。
4.1 实时优先级范围:0-99 到底怎么用?
Linux内核里,实时优先级范围是0到99。数字越大,优先级越高。99是最高,0是最低。这个和普通进程的nice值(-20到19)完全是两套体系,别搞混了。
我个人习惯把实时优先级分成三个梯队:
- 80-99:留给最核心的中断处理、高精度定时器、关键控制回路。比如电机驱动、飞行控制。
- 50-79:给中等重要的周期性任务。比如数据采集、状态监控。
- 0-49:给一些低优先级的后台实时任务。比如日志记录、非关键通信。
核心原则:优先级不是越高越好。你想想看,一个99优先级的任务如果写了个死循环,整个系统就挂了。因为没有任何东西能抢占它。
我在项目中遇到过一件事:有个同事把所有实时线程都设成99,结果系统一启动,键盘鼠标全卡死。为什么?因为99优先级把调度器本身都堵住了。后来我让他把非关键线程降到70,系统立马稳了。
4.2 优先级继承协议:别让你的任务“饿死”
这里有个经典问题——优先级反转。说白了就是:一个低优先级任务拿着锁,高优先级任务等着锁,结果中优先级任务跑得欢,高优先级任务干瞪眼。
Linux内核怎么解决?靠的是优先级继承协议(Priority Inheritance Protocol)。
机制很简单:当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务会临时“继承”高优先级任务的优先级。这样它就能尽快跑完、释放锁,高优先级任务才能继续。
我的经验:在编写实时任务时,尽量使用 pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) 来启用优先级继承。默认情况下,Linux的mutex是不开这个的,你得手动设置。
我曾经在一个机器人项目中踩过这个坑。三个任务:A(优先级80)、B(优先级60)、C(优先级40)。A和C共享一把锁,B是个纯计算任务。结果A等锁的时候,B一直跑,A就是拿不到锁。系统响应时间从1ms飙到200ms。后来加上优先级继承,问题立刻解决。
注意:优先级继承不是万能的。它只能解决“直接阻塞”的情况。如果有多个锁嵌套,或者有中断上下文参与,情况会更复杂。这时候你可能需要考虑优先级天花板协议(Priority Ceiling Protocol)。
4.3 时间片分配机制:SCHED_FIFO vs SCHED_RR
实时调度器里,时间片怎么分?这取决于你用的是哪种调度策略。
| 策略 | 时间片 | 适用场景 |
|---|---|---|
| SCHED_FIFO | 无时间片,跑完才让 | 短任务、高确定性需求 |
| SCHED_RR | 固定时间片(默认100ms) | 同优先级轮转、长任务 |
SCHED_FIFO:先进先出。同一个优先级的任务,谁先就绪谁先跑,直到它主动让出(比如sleep、等待IO、或者被更高优先级抢占)。没有时间片限制。说白了,它不跟你讲公平,只讲顺序。
SCHED_RR:轮转调度。同优先级的任务,每个跑一个时间片,然后换下一个。默认时间片是100ms,但你可以通过 sched_rr_get_interval() 查看,或者通过 /proc/sys/kernel/sched_rr_timeslice_ms 调整。
我的建议:如果你不确定用哪个,先选SCHED_FIFO。因为大多数实时任务都是“跑完就等事件”,不需要轮转。SCHED_RR反而可能引入不必要的上下文切换开销。
嗯,这里要注意:时间片不是越大越好。100ms对于很多实时应用来说太长了。我一般会把时间片调到10ms甚至更小,尤其是控制周期在1ms-10ms的场景。
4.4 知识体系结构图
下面这张图,是我自己梳理的实时优先级与时间片的核心逻辑。你看一眼,基本就全明白了。
4.5 实战中的几个坑
最后,我把自己踩过的几个坑列出来,你遇到了能少走弯路:
- 别把所有任务都设成99——系统调度器本身也需要CPU,你堵死了它,系统就死了。
- 优先级继承不是默认开启的——你得在创建mutex时显式设置属性,否则默认是PTHREAD_PRIO_NONE。
- SCHED_FIFO任务一定要有让出CPU的机制——比如sleep、等待条件变量、或者主动调用sched_yield()。否则同优先级的其他任务永远跑不了。
- 时间片别设太大——100ms对于1ms控制周期来说,简直是灾难。我一般调到10ms以内。
- 注意中断上下文——中断处理函数的优先级比任何用户态实时任务都高。如果你的实时任务被中断频繁打断,响应时间会变差。
一个小技巧:调试实时任务时,可以用 chrt 命令查看和设置优先级。比如 chrt -p -f 80 1234 把PID 1234设为SCHED_FIFO优先级80。非常方便。
好了,这部分内容就到这里。优先级和时间片,说白了就是“谁先跑”和“跑多久”的问题。搞清楚了这两个,你的实时系统就稳了一半。