3. 实时Linux内核机制与PREEMPT_RT补丁

标准Linux内核的设计目标偏向吞吐量与公平性,而实时应用要求的是“最坏情况下的响应时间可界定”。PREEMPT_RT补丁集并不是一个独立内核,而是把Linux逐步改造成可抢占内核的一组机制集合。理解这些机制,比记住某个配置项更重要,因为Ryzen Embedded平台上的调优动作,几乎都对应到下面这几类内核行为。

实时调度类:决定谁先拿到CPU

Linux调度器把任务按调度类分层,优先级从高到低依次是:停机类(stop)、截止时间类(DL)、实时类(RT,含SCHED_FIFO与SCHED_RR)、普通类(CFS,含SCHED_OTHER/SCHED_BATCH/SCHED_IDLE)。实时任务使用1~99的静态优先级,数值越大越优先;普通任务使用nice值(-20~19),数值越小越优先。两类优先级不在同一量纲上,实时任务永远压过普通任务。

SCHED_FIFO与SCHED_RR的区别在于同优先级任务的处理:FIFO一旦运行就持续占用CPU,直到主动让出、被更高优先级抢占或阻塞;RR则在同一优先级内按时间片轮转。工程上,控制回路通常用SCHED_FIFO并赋予固定优先级,避免时间片带来的抖动。需要特别注意的是,实时任务若陷入死循环且不让出CPU,会直接饿死同核上的所有低优先级任务,包括内核线程,因此实时任务必须保证有阻塞点。

在Ryzen Embedded上,可通过以下方式设置并验证:

chrt -f 80 ./motor_ctrl          # 以SCHED_FIFO优先级80启动
chrt -p $(pidof motor_ctrl)      # 查看当前调度策略与优先级
ps -eLo pid,tid,cls,rtprio,comm  # 列出线程级调度类与实时优先级

输出中cls列显示FF表示SCHED_FIFO,TS表示SCHED_OTHER。若实时任务被普通任务抢占,通常说明优先级设置未生效或存在优先级反转。

优先级继承:解决优先级反转

优先级反转的经典场景是:低优先级任务L持有互斥锁,高优先级任务H等待该锁而被阻塞,此时中优先级任务M抢占L,导致H被M间接拖延。PREEMPT_RT通过优先级继承(PI)缓解该问题:当H阻塞在L持有的rtmutex上时,L的优先级被临时提升到H的水平,使其尽快执行完临界区并释放锁,随后恢复原优先级。

PI只对使用rtmutex的互斥量生效,因此实时任务之间共享资源时应使用pthread_mutex,并设置PTHREAD_PRIO_INHERIT属性,而不是使用普通信号量或自旋锁:

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&shared_lock, &attr);

需要提醒的是,PI不能消除反转,只能压缩其持续时间;若临界区过长,即使继承优先级,H仍要等待整个临界区执行完毕。因此实时代码中临界区应尽量短,且避免在持锁期间做I/O或内存分配。

中断线程化与自旋锁转换

标准内核中,硬中断处理程序在关中断上下文中运行,会直接打断实时任务,是抖动的重要来源。PREEMPT_RT把大部分中断处理程序转为内核线程(中断线程化),中断到来时只做最小必要的硬件应答,其余处理交给具有实时优先级的线程完成,从而可被调度、可被抢占、可设置优先级。

与之配套的是自旋锁的转换。标准内核的spinlock在持有期间禁止抢占,实时任务一旦碰到被占用的自旋锁只能忙等,破坏实时性。PREEMPT_RT将大部分spinlock替换为可睡眠的rtmutex,持锁期间允许抢占,等待者进入睡眠而非自旋。只有少数确实需要在原子上下文使用的锁保留为raw_spinlock。

这两项机制带来的直接后果是:中断处理延迟从“不可控”变为“可调度”,但代价是中断线程本身也参与调度竞争。因此在Ryzen Embedded上,通常把关键外设的中断线程优先级设为略高于对应实时任务,例如控制任务用FIFO 80,其触发中断的线程用FIFO 85,保证数据到达后能及时唤醒控制任务。

PREEMPT_RT 关键机制与 Ryzen Embedded 适配关系 标准内核的实时障碍 硬中断不可抢占 自旋锁忙等、禁抢占 优先级反转无约束 → 最坏延迟不可界定 PREEMPT_RT 机制 中断线程化(IRQ thread) spinlock → rtmutex 优先级继承(PI) → 延迟可调度、可界定 Ryzen Embedded 适配 中断线程优先级 85 控制任务 FIFO 80 隔离CPU + 关闭节能 → 抖动收敛到微秒级 优先级继承时序示意(H 等待 L 持有的 rtmutex) t H 运行 阻塞等待锁 获得锁,继续运行 L 持锁运行 优先级提升至H 释放锁,恢复原优先级 M 就绪 被L抢占,无法运行 L释放后恢复运行 PI 触发

Ryzen Embedded上的适配要点

Ryzen Embedded(如V1000、R1000系列)是x86_64多核SoC,PREEMPT_RT的适配重点不在架构移植,而在平台特性与实时性的冲突处理上。

第一,CPU隔离与频率管理。实时任务应绑定到隔离核,通过内核参数isolcpus=2,3或cgroup cpuset把非实时任务排除在外,再用taskset -c 2 ./motor_ctrl绑定。同时关闭该核的cpufreq调频与C-state深度睡眠,避免频率切换和唤醒延迟引入抖动,可在启动参数中加入processor.max_cstate=1 intel_idle.max_cstate=0(AMD平台对应acpi_idle相关限制)。

第二,中断亲和性。把网卡、串口、GPIO等关键外设的中断线程绑定到非隔离核,或按需绑定到隔离核并设置合适优先级。可通过/proc/irq/<n>/smp_affinity设置中断CPU亲和性,通过chrt -f 85 -p <pid>调整中断线程优先级。

第三,内核配置。需启用CONFIG_PREEMPT_RT、CONFIG_HIGH_RES_TIMERS、CONFIG_NO_HZ_FULL,并确认CONFIG_DEBUG_PREEMPT等调试选项在量产版本中关闭,避免调试开销影响延迟。

第四,验证方法。使用cyclictest在隔离核上测量最坏延迟:

cyclictest -m -p 80 -t 1 -a 2 -i 1000 -l 100000
# -m 锁定内存,-p 80 实时优先级,-a 2 绑定CPU2
# -i 1000 周期1ms,-l 100000 循环10万次

在Ryzen Embedded V1605B上,合理配置后典型最坏延迟可收敛到几十微秒量级;若出现数百微秒以上的尖峰,应优先排查SMI、电源管理、非隔离核上的高负载任务以及未线程化的中断。判断标准不是平均值,而是最大值与分布尾部——实时性看的是最坏情况。

优先级继承只对rtmutex生效。若实时任务之间用信号量或条件变量共享资源,PI不会介入,反转仍会发生。共享资源应统一使用设置了PTHREAD_PRIO_INHERIT的pthread_mutex。
中断线程化后,中断处理线程也参与调度。若其优先级低于实时任务,中断可能被延迟处理;若过高,又可能频繁抢占实时任务。建议中断线程优先级略高于对应实时任务,并通过cyclictest实测确认。