Linux RT 分析Linux 6.12.107

09 · printk:调用者不再亲自刷串口

结论:非 RT 内核里 printk() 的调用者只要拿到 console_lock 就会在自己的上下文(可能是 hardirq、可能持着 raw 锁)把所有积压日志同步写到串口——115200 波特率下一行 100 字符约 9 ms,一次内核日志风暴能把系统最坏延迟拉到几十 ms,这就是 printk 被称为「RT 最后一块拼图」的原因。6.12 的 RT 内核把 force_legacy_kthread() 固定为 true:所有 legacy 控制台在 pr/legacy 内核线程里输出;已转换为 nbcon 的控制台各有一个 pr/<name><idx> 线程;只有 emergency/panic 走 write_atomic() 直接输出。调用者的成本降到「写 ring buffer」,代价是日志到达串口的时刻不再确定。

第一层

先看结论:printk 调用者不再亲自刷串口,legacy 与 nbcon 控制台各有专用线程输出,日志风暴不再制造几十 ms 的抖动。

非 RTprintk 调用者可能亲自刷串口
非 RT:printk 调用者可能亲自刷串口
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RT写 ring buffer 后交给 pr/legacy 与 nbcon 线程
RT:写 ring buffer 后交给 pr/legacy 与 nbcon 线程

第二层

先看结论:RT 侧只有两处 ifdef——force_legacy_kthread() 恒真、spinlock 允许函数为空;重活在 nbcon 的所有权状态机里。

非 RTconsole_lock 打印循环与打印者交接
非 RT:console_lock 打印循环与打印者交接
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RTlegacy 线程 + nbcon 所有权模型
RT:legacy 线程 + nbcon 所有权模型

RT 的两处开关

kernel/printk/CONFIG_PREEMPT_RT 只出现在两处:

  1. kernel/printk/internal.h:24-35
/*
 * Identify if legacy printing is forced in a dedicated kthread. If true,
 * all printing via console lock occurs within a dedicated legacy printer
 * thread. The only exception is on panic, ...
 */
#ifdef CONFIG_PREEMPT_RT
# define force_legacy_kthread() (true)
#else
# define force_legacy_kthread() (false)
#endif
  1. kernel/printk/printk.c:2984-3009:非 RT 下调用者上下文打印会「不尊重」raw_spinlock/spinlock 嵌套规则,需要 printk_legacy_allow_spinlock_enter() 压掉 lockdep 误报;RT 下调用者不打印,这两个函数是空的。

legacy 线程

printk.c:3596-3617 legacy_kthread_create()kthread_run(legacy_kthread_func, NULL, "pr/legacy")sched_set_normal(…, -20)——SCHED_NORMAL nice -20,不是 FIFOprintk_kthreads_check_locked():3658-3687)在 RT 上必须创建成功,否则注销所有 legacy 控制台:3677-3682)。

nbcon(6.12.107 已有线程框架)

printk_deferred

printk.c:4547-4550vprintk_deferred()LOGLEVEL_SCHED 写入,通过 irq_work 触发输出(:4528-4540)。它一直存在,但绝大多数调用点用的是普通 printk(),所以在非 RT 上不能指望它兜底。

性能影响

净影响:printk 成本降到 µs 以下;日志到达变晚、crash 前几行可能丢。

指标 非 RT RT 说明
printk() 调用成本 写 ring buffer + 可能刷完全部积压(ms – 数百 ms) 写 ring buffer + 唤醒(< 1 µs 典型) 核心收益
日志风暴对最坏延迟的影响 直接计入(若发生在 hardirq/关抢占区) 无(线程是普通优先级)
日志到达串口的延迟 同步 取决于线程何时被调度:高 RT 负载下可达秒级 调试体验变差
crash 前最后几行 通常能刷出 依赖 panic 的 atomic flush(nbcon);legacy 控制台可能丢
console_lock 争用 打印者交接自旋 只在线程内

源码定位

主题 位置
force_legacy_kthread() kernel/printk/internal.h:24-35
RT 下空的 spinlock 允许函数 kernel/printk/printk.c:2984-3009
legacy 线程 kernel/printk/printk.c:3531, 3596-3617, 3658-3687
nbcon 线程 kernel/printk/nbcon.c:1157, 1181-1186, 1271, 1299, 1326-1350
atomic 输出 kernel/printk/nbcon.c:1045, 1496include/linux/console.h:355-393
arm64 串口驱动状态 drivers/tty/serial/amba-pl011.c:2504drivers/tty/serial/8250/8250_core.c:502include/linux/serial_core.h:596-713
printk_deferred include/linux/printk.h:174-175, 497-498kernel/printk/printk.c:4455-4456, 4528-4550

调优要点

小结