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 知识体系结构图

下面这张图,是我自己梳理的实时优先级与时间片的核心逻辑。你看一眼,基本就全明白了。

实时优先级与时间片核心逻辑 实时优先级 0-99 数字越大优先级越高 优先级继承协议 解决优先级反转 时间片分配机制 SCHED_FIFO / SCHED_RR 低优先级临时继承高优先级 尽快释放锁资源 SCHED_FIFO:无时间片 跑完才让出CPU SCHED_RR:固定时间片 默认100ms,可调整 核心原则:优先级决定谁先跑,时间片决定跑多久 优先级继承防止饿死,时间片策略决定公平性

4.5 实战中的几个坑

最后,我把自己踩过的几个坑列出来,你遇到了能少走弯路:

  1. 别把所有任务都设成99——系统调度器本身也需要CPU,你堵死了它,系统就死了。
  2. 优先级继承不是默认开启的——你得在创建mutex时显式设置属性,否则默认是PTHREAD_PRIO_NONE。
  3. SCHED_FIFO任务一定要有让出CPU的机制——比如sleep、等待条件变量、或者主动调用sched_yield()。否则同优先级的其他任务永远跑不了。
  4. 时间片别设太大——100ms对于1ms控制周期来说,简直是灾难。我一般调到10ms以内。
  5. 注意中断上下文——中断处理函数的优先级比任何用户态实时任务都高。如果你的实时任务被中断频繁打断,响应时间会变差。

一个小技巧:调试实时任务时,可以用 chrt 命令查看和设置优先级。比如 chrt -p -f 80 1234 把PID 1234设为SCHED_FIFO优先级80。非常方便。

好了,这部分内容就到这里。优先级和时间片,说白了就是“谁先跑”和“跑多久”的问题。搞清楚了这两个,你的实时系统就稳了一半。