08 · E3 落地与 RT 进主线(6.11 被拒 → 6.12)
结论:6.12 一次性合入了 nbcon 的「活」的部分:选路分派(
printk_get_console_flush_type())、打印线程(每个 nbcon 控制台一个pr/<con>,加write_thread()与device_lock())、emergency 段(WARN / oops / lockdep / RCU stall 在调用者上下文原子输出)、panic 次序(先 nbcon、再 legacy、最后 hostile),以及 RT 专用的pr/legacy:legacy 控制台在 RT 上一律交给线程,驱动不用改。同一个合并窗口里,sched-rt-2024-09-17合入,arm64 / x86 / riscv 可以选 PREEMPT_RT。在这之前,6.11 的 pull 被 Linus 拒绝,原因是 emergency 段到结束时才统一刷出,他回复 "The 'flush at end' is not even remotely sane"。6.12 改成 emergency 段里每次 printk 就地刷出。代价是:6.12 时 ARM64 上没有任何串口驱动是 nbcon,RT 的收益只体现在「调用者不再被拖住」,panic 时的可靠性要等 E4。
要解决的问题
| # | 问题 | 出处 |
|---|---|---|
| P1 | printk 调用者亲自刷串口,RT 延迟无上界 | 6.11 vprintk_emit() → console_trylock_spinning() → console_unlock() |
| P2 | RT 上 legacy 驱动的 write() 拿 spinlock_t,在原子上下文会睡眠 |
5f53ca3ff83b:"The write() callback of legacy consoles usually makes use of spinlocks. This is not permitted with PREEMPT_RT in atomic contexts." |
| P3 | 紧急信息(回溯)必须马上出去,不能等线程 | ecb5e1aa82c8:"This information should be pushed out to the consoles ASAP." |
| P4 | 打印线程与驱动自身的 tty 访问如何同步 | 7a16a7718907:增加 device_lock() / device_unlock() |
| P5 | write_atomic() 与 tty 侧的寄存器访问如何互斥 |
4126f149c48b:uart_port_lock() 包装里获取 nbcon |
| P6 | arm64 等架构不能选 PREEMPT_RT | d8fccd9ca5f9:"With the recent printk changes, the last known road block has been addressed." |
6.11 为什么被拒
6.11 版的 emergency 段让段内 printk 只入缓冲、在段结束时统一刷出,目的是让一整段回溯连续输出。John Ogness 在线程里解释:对 RCU stall、lockdep 这类大段输出,刷出要等到大部分警告都存进缓冲之后。Linus 的回复(2024-07-23):"I really think the flushing needs to be way more aggressive for any oops","The 'flush at end' is not even remotely sane"。也就是说,如果机器在 oops 打印途中彻底死掉,「先存后刷」会让最关键的那几行永远出不来。
6.12 的 ecb5e1aa82c8 改为:"Perform direct console flushing at the emergency priority if the current CPU is in an emergency state and it is safe to do so." 每次 printk 都在 vprintk_emit() 里就地原子刷出。
方案与提交(6.12,按功能分组)
| 功能 | 关键提交 | 说明 |
|---|---|---|
| 选路 | 6690d6b52726 Add helper for flush type logic |
struct console_flush_type:nbcon_atomic / nbcon_offload / legacy_direct / legacy_offload |
| 追踪控制台种类 | bebd87ae27e0、97ea9bccfcbe、60013065fdc6 |
have_nbcon_console / have_boot_console / have_legacy_console;没有 legacy 时跳过 console_lock |
| 原子刷出 | d3a9f82ec5c0 Provide function to flush using write_atomic() |
只刷到调用时最新的记录,防止一个 CPU 被别人的新消息拖住 |
| emergency | ecb5e1aa82c8 Implement emergency sections |
per-CPU 嵌套计数;__warn()、oops_enter()、lockdep、RCU stall 等接入 |
| panic | d2e85ca7a736 Flush nbcon consoles first;e35a8884270b Coordinate direct printing;5dde3b735413 Add unsafe flushing on panic |
nbcon 先、legacy 后,最后 hostile |
| 驱动同步 | 7a16a7718907 device_lock;adf6f37d142e non-printing API;4126f149c48b port->lock wrapper |
nbcon_device_try_acquire/release()、uart_port_lock*() 接入 |
| 线程 | 76f258bf3f2a Introduce printer kthreads;13189fa73afa Rely on kthreads for normal operation;1529bbb6e261 nice -20 |
write_thread() 成为必选回调 |
| RT | 5f53ca3ff83b Implement legacy printer kthread for PREEMPT_RT |
force_legacy_kthread();pr/legacy |
| 杂项 | bd07d864522e reacquire;5102981d5e2a replay 提示;b7049d88c176 write_atomic 改 void;def84b446777 sysfs active |
— |
| 架构 | d8fccd9ca5f9 arm64;d2d6422f8bd1 x86;2638e4e6b182 riscv(合并 baeb9a7d8b60:"After twenty years of development we finally reached the point to enable PREEMPT_RT support in the mainline kernel.") |
select ARCH_SUPPORTS_RT |
架构图:之前 / 之后
调用栈
之前 · 6.11 主线(与 6.6 相同,行号见 E1):
vprintk_emit()
vprintk_store()
preempt_disable(); console_trylock_spinning() → console_unlock()
console_flush_all() → console_emit_next_record() → con->write() ← 调用者上下文、关中断
之后 · 6.12(行号为本站基准 6.12.113):
vprintk_emit() printk.c:2342
printk_get_console_flush_type(&ft) :2363 → internal.h:233
vprintk_store() :2374
ft.nbcon_atomic → nbcon_atomic_flush_pending() :2377 → nbcon.c:1626
ft.nbcon_offload → nbcon_kthreads_wake() :2380 → nbcon.c:1271
ft.legacy_direct → console_trylock_spinning() → console_unlock() :2397(非 RT)
ft.legacy_offload → defer_console_output() :2403 → :4571(RT 上恒走这里)
── RT 上的 legacy 控制台 ──
wake_up_klogd_work_func()(irq_work) printk.c:4498
force_legacy_kthread() → wake_up_interruptible(&legacy_wait)
legacy_kthread_func() :3619(pr/legacy,legacy_kthread_create() :3634)
wait_event_interruptible(legacy_wait, legacy_kthread_should_wakeup()) :3571
console_lock()
__console_flush_and_unlock() :3252
console_flush_all() :3230 ← 6.12.113 仍一次刷完全部积压
console_emit_next_record() → con->write() :3024 ← 在线程里,spinlock_t 睡眠也无妨
── nbcon 控制台 ──
nbcon_kthread_func() nbcon.c:1186
nbcon_emit_one(&wctxt, false) :1106
con->device_lock(); cant_migrate()
nbcon_context_try_acquire() → nbcon_emit_next_record() → con->write_thread()
nbcon_context_release(); con->device_unlock()
── emergency ──
__warn() panic.c:773
nbcon_cpu_emergency_enter() nbcon.c:1652
printk → vprintk_emit → ft.nbcon_atomic → nbcon_atomic_flush_pending()
nbcon_atomic_flush_pending_con() nbcon.c:1544
local_irq_save();__nbcon_atomic_flush_pending_con() :1496 ← 6.12:一次持有到刷完
nbcon_cpu_emergency_exit() nbcon.c:1667
── panic ──
panic() panic.c:340
printk_legacy_allow_panic_sync() :430 → printk.c:2313
console_flush_on_panic(CONSOLE_FLUSH_PENDING) :465 → printk.c:3449
…
console_flush_on_panic(); nbcon_atomic_flush_unsafe() :520-521 → nbcon.c:1638
线程何时启动(6.12.113):printk_set_kthreads_ready() 是 early_initcall(printk.c:3765),调用 printk_kthreads_check_locked()(:3696):有 legacy 或 boot console 且是 RT,就建 pr/legacy;没有 boot console 且有 nbcon,就为每个 nbcon 建 pr/* 并置 printk_kthreads_running。7.3 把它推迟到 subsys_initcall,见 E5。
RT 与 printk 的关系,到这里理清了
| 问题 | 6.12 之前(树外 RT 补丁) | 6.12 主线 |
|---|---|---|
| 调用者被串口拖住 | RT 补丁自带打印线程 | NORMAL 一律 offload:nbcon 走 pr/<con>,legacy 走 pr/legacy |
legacy 驱动 write() 在 RT 上睡眠 |
补丁里线程化 | force_legacy_kthread() 让 legacy 只在线程里被调用,驱动不用改 |
| 紧急 / panic 输出 | 补丁里给 8250 加了 write_atomic |
框架齐备,但主线驱动要等 6.13+ 才转换 |
| Kconfig | 树外打开 | select ARCH_SUPPORTS_RT(arm64 d8fccd9ca5f9) |
4+1 增量
| 视图 | E3 带来的变化 |
|---|---|
| 场景 | S1、S2、S3、S7 第一次有了 nbcon 路径;S5 有了框架(但 ARM64 无驱动) |
| 逻辑 | console_flush_type;write_thread / device_lock;device context |
| 进程 | 新执行体 pr/<con>、pr/legacy;emergency CPU;panic 次序 |
| 开发 | internal.h 选路;serial_core.h 包装;printk.c legacy 线程;panic.c / lockdep / RCU 接入 |
| 物理 | arm64 可选 PREEMPT_RT;但 pl011 / 8250 仍是 legacy(见 6.12.113 serial_core.h:632 恒为 false) |
留下的问题:没有驱动就没有 nbcon 的可靠性收益;所有 legacy 控制台挤在一个 pr/legacy 里;原子刷出一次持有到刷完。见 E4、E5。