3. Linux实时性核心机制
Linux 的实时性不是单一开关,而是由调度、中断、锁与内核抢占模型共同决定的一条“延迟链”。在 Sitara AM335x/AM437x 这类 Cortex-A8 平台上,从外部事件到达,到实时任务真正开始执行,中间要经过硬件中断、中断服务程序、软中断/工作队列、唤醒调度器、上下文切换等多个环节。任何一个环节不可抢占或长时间关中断,都会把最坏情况延迟(Worst-Case Latency)推高。理解这条链,是后续做 PREEMPT_RT 裁剪与参数调优的基础。
一、进程调度:CFS 与实时调度类
Linux 内核调度器按调度类优先级组织:stop_sched_class > dl_sched_class(SCHED_DEADLINE)> rt_sched_class(SCHED_FIFO/SCHED_RR)> fair_sched_class(CFS,SCHED_OTHER)> idle_sched_class。普通进程走 CFS,按虚拟运行时间 vruntime 红黑树选最左节点;实时任务走 RT 调度类,优先级 1~99,数值越大越高,且只要可运行就抢占 CFS 任务。
对实时任务,通常使用 SCHED_FIFO 并配合 sched_setscheduler() 设置优先级。例如把采集线程设为优先级 80:
struct sched_param sp = { .sched_priority = 80 };
pthread_setschedparam(tid, SCHED_FIFO, &sp);
// 绑定 CPU 并锁定内存,避免迁移与缺页抖动
cpu_set_t set; CPU_ZERO(&set); CPU_SET(0, &set);
pthread_setaffinity_np(tid, sizeof(set), &set);
mlockall(MCL_CURRENT | MCL_FUTURE);
需要注意:RT 任务若长期占用 CPU 且不主动让出,会饿死 CFS 任务甚至看门狗线程。因此工程上常配合 sched_rr_get_interval() 使用 SCHED_RR,或设置 /proc/sys/kernel/sched_rt_runtime_us(默认 950000,即 95%)限制 RT 带宽。
二、中断处理:上半部与下半部
中断处理分为硬中断(上半部)和软中断/任务let/工作队列(下半部)。上半部在关中断上下文运行,必须极短;耗时逻辑应下推到下半部。关键参数是 /proc/irq/<n>/smp_affinity 与线程化中断。
在 PREEMPT_RT 中,绝大多数中断处理程序被强制线程化(IRQF_NO_THREAD 除外),中断线程拥有 RT 优先级,可被更高优先级任务抢占,从而把“关中断时间”转化为“可抢占的调度延迟”。Sitara 上可通过 request_threaded_irq() 显式指定 handler 与 thread_fn,把耗时处理放进线程上下文。
三、自旋锁与优先级反转
自旋锁(spinlock)在持有期间禁止抢占(非 RT)或允许抢占但禁止迁移(RT)。若低优先级任务持有锁,高优先级任务自旋等待,而中优先级任务抢占低优先级任务,就会出现优先级反转。经典解法是优先级继承(Priority Inheritance):rt_mutex 在 RT 内核中替代了大部分 spinlock,使持锁者临时继承等待者的优先级。
下面这张图给出从外部中断到实时任务执行的完整延迟链,并标注了各环节的阻塞点与 PREEMPT_RT 的改造位置:
四、PREEMPT_RT 补丁原理
PREEMPT_RT 的核心思想是“把不可抢占的临界区尽量变小、变可抢占”。主要手段包括:
- 中断线程化:除少数必须原子完成的 IRQ 外,handler 被包装成内核线程,拥有 RT 优先级,可被更高优先级任务抢占。
- spinlock 转为 rt_mutex:大部分自旋锁替换为可睡眠的互斥量,并实现优先级继承,消除优先级反转。
- 可抢占内核:在更多位置插入抢占点,使内核态执行也能被调度器打断。
- hrtimer 与高精度调度:用高精度定时器驱动调度节拍,减少周期性 tick 带来的抖动。
在 Sitara 上,AM335x 为单核 Cortex-A8,没有多核缓存一致性与跨核 IPI 的复杂性,但也没有多核并行卸载能力。其 INTC 中断控制器与 OCP 互连总线会引入额外延迟,因此中断线程化与 PRU 卸载往往比单纯调 RT 优先级更有效。
五、Sitara 适配现状与工程建议
TI 的 Processor SDK Linux 提供基于 linux-rt 分支的内核,但默认配置通常不是完整 PREEMPT_RT。实际工程中需要:
- 确认内核配置:
CONFIG_PREEMPT_RT=y、CONFIG_HIGH_RES_TIMERS=y、CONFIG_PREEMPT=y。 - 用
cyclictest测量基线延迟:cyclictest -m -p 80 -n -i 1000 -l 100000,观察 Max 值。 - 将关键中断线程优先级设为高于应用 RT 任务,避免中断线程被应用任务阻塞。
- 对微秒级确定性要求,优先使用 PRU-ICSS 承担硬实时 I/O,Linux 侧只做管理与数据搬运。