04 · 开发视图:源码分层、模块边界与驱动契约
结论:nbcon 没有新建子系统,而是把
kernel/printk/切成五层:调用者(panic / lockdep / RCU 等标记 emergency)→ printk 前端(printk.c:注册、vprintk_emit()、legacy 循环与线程、panic 刷出)→ 选路策略(internal.h:printk_get_console_flush_type())→ nbcon 核心(nbcon.c:所有权、发射、线程、原子刷出)→ 存储(printk_ringbuffer.c)。驱动经两份契约接入:console.h(四个回调 + unsafe API)和serial_core.h(uart_port_lock()自动获取 nbcon 上下文)。转换一个驱动,要实现write_thread/write_atomic/device_lock/device_unlock、置CON_NBCON,并把每一次寄存器访问包进 unsafe 区。从 6.6 到 7.3,nbcon.c从无到 2084 行,主要作者是 John Ogness(26 个非合并提交)、Thomas Gleixner(10 个)和 Petr Mladek(7 个)。
分层与文件
| 层 | 文件 | 行数 v6.7 → 6.12.113 → v7.3 | 主要入口(v7.3 行号) |
|---|---|---|---|
| 调用者 | kernel/panic.c、kernel/locking/lockdep.c、kernel/rcu/tree_stall.h、tree_exp.h、kernel/debug/kdb/ |
— | vpanic() 577 · oops_enter() 1040 · __warn() 1074 |
| printk 前端 | kernel/printk/printk.c |
4379 → 5057 → 5188 | vprintk_emit() 2423 · console_flush_one_record() 3234 · console_flush_on_panic() 3534 · legacy_kthread_func() 3704 · printk_kthreads_shutdown() 3759 · printk_kthreads_check_locked() 3795 · register_console() 4056 · defer_console_output() 4681 |
| 选路策略 | kernel/printk/internal.h、printk_safe.c |
138 → 343 → 312 | force_legacy_kthread() 31 · struct console_flush_type 181 · printk_get_console_flush_type() 194 · is_printk_legacy_deferred() printk_safe.c:59 |
| nbcon 核心 | kernel/printk/nbcon.c |
1029 → 1835 → 2084 | 见下表 |
| 存储 | kernel/printk/printk_ringbuffer.c/.h |
2124 → 2356 → 2418 | prb_reserve() · prb_commit() · prb_read_valid() · prb_next_reserve_seq() |
| 契约 ① | include/linux/console.h |
535 → 677 → 757 | CON_NBCON 193 · nbcon_state 215 · write_atomic 388 · write_thread 419 · device_lock 446 |
| 契约 ② | include/linux/serial_core.h |
1121 → 1292 → 1350 | __uart_port_using_nbcon() 644 · __uart_port_nbcon_acquire() 678 · uart_port_lock_irqsave() 721 |
| 可观测 | fs/proc/consoles.c、drivers/tty/tty_io.c |
— | /proc/consoles 的 N 标志;/sys/class/tty/console/active 支持 nbcon(6.12 def84b446777) |
nbcon.c 按职责分块(v7.3):
| 职责 | 函数(行号) | 引入版本 |
|---|---|---|
| 状态字读写 | nbcon_state_set/read/try_cmpxchg 133-156 · nbcon_seq_read/force/try_update 168-207 |
6.7 |
| 获取 / 释放 | try_acquire_direct 243 · waiter_matches 293 · try_acquire_requested 351 · try_acquire_handover 434 · try_acquire_hostile 550 · try_acquire 598 · owner_matches 635 · release 677 · can_proceed 730 |
6.7(is_reacquire 6.17) |
| 驱动 unsafe API | nbcon_can_proceed 798 · nbcon_enter_unsafe 885 · nbcon_exit_unsafe 909 · nbcon_reacquire_nobuf 938 |
6.7 / 6.12 |
| 发射 | nbcon_emit_next_record 980 · nbcon_emit_one 1129 |
6.7 / 6.12 |
| 打印线程 | nbcon_kthread_should_wakeup 1180 · nbcon_kthread_func 1220 · nbcon_irq_work 1286 · nbcon_kthreads_wake 1313 · nbcon_kthread_stop/create 1348/1375 |
6.12 |
| 优先级 / emergency | nbcon_get_default_prio 1442 · nbcon_allow_unsafe_takeover 1471 · nbcon_cpu_emergency_enter/exit 1717/1734 |
6.12 / 6.19 |
| legacy 循环里的 nbcon | nbcon_legacy_emit_next_record 1506 |
6.12 |
| 原子刷出 | __nbcon_atomic_flush_pending_con 1564 · nbcon_atomic_flush_pending_con 1620 · nbcon_atomic_flush_pending 1689 · nbcon_atomic_flush_unsafe 1701 |
6.12(逐条 6.19) |
| 分配 | nbcon_alloc 1770 · nbcon_free 1833 |
6.7 |
| 非打印路径 | nbcon_device_try_acquire/release 1882/1907 |
6.12 |
| KDB / braille | nbcon_kdb_try_acquire/release 1965/1989 · nbcon_braille_* 2019-2079 |
6.19 / 7.3 |
Kconfig 与编译期开关:nbcon 没有独立的 Kconfig,随 CONFIG_PRINTK 编入。唯一与 RT 相关的开关是 force_legacy_kthread():#ifdef CONFIG_PREEMPT_RT 时为 true(internal.h:31),它决定 legacy 控制台在 RT 上一律交给 pr/legacy。CONFIG_PRINTK_EXECUTION_CTX(7.0)给 nbcon_write_context 增加 cpu/pid/comm,供 netconsole 等使用。
驱动契约
以 v7.3 的 pl011 为例(drivers/tty/serial/amba-pl011.c),转换后的控制台描述符:
static struct console amba_console = { /* amba-pl011.c:2725 */
.name = "ttyAMA",
.setup = pl011_console_setup, /* 7.3:clk_prepare_enable() 常开 */
.exit = pl011_console_exit, /* 7.3:clk_disable_unprepare() */
.write_atomic = pl011_console_write_atomic,
.write_thread = pl011_console_write_thread,
.device_lock = pl011_console_device_lock, /* __uart_port_lock_irqsave() */
.device_unlock = pl011_console_device_unlock,
.flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON,
};
两个写回调的结构差异,就是 nbcon 的核心取舍:
/* write_atomic:整串在一个 unsafe 区里写完——高优先级上下文,宁可让别人等 */
pl011_console_write_atomic() /* :2645 */
if (!nbcon_enter_unsafe(wctxt)) return;
保存并改写 CR → [补换行] → uart_console_write(全部) → 等 TX 空 → 恢复 CR
nbcon_exit_unsafe(wctxt);
/* write_thread:每个字节一个 unsafe 区——低优先级,随时给 emergency 让路 */
pl011_console_write_thread() /* :2673 */
if (!nbcon_enter_unsafe(wctxt)) return;
保存并改写 CR
if (nbcon_exit_unsafe(wctxt))
for (i = 0; i < len; i++) {
if (!nbcon_enter_unsafe(wctxt)) break;
uart_console_write(1 字节);
if (!nbcon_exit_unsafe(wctxt)) break;
}
while (!nbcon_enter_unsafe(wctxt)) /* 被抢了也必须复原硬件 */
nbcon_reacquire_nobuf(wctxt);
等 TX 空 → 恢复 CR → nbcon_exit_unsafe(wctxt);
驱动转换检查单(来自 6.13–7.3 的驱动提交与修复):
device_lock()用驱动自己的锁,并且必须禁迁移。uart 用__uart_port_lock_irqsave()(双下划线版本,不再嵌套获取 nbcon)。- 所有寄存器访问都放进 unsafe 区。
write_thread逐字节开安全窗口,write_atomic整串一个区。 - 被抢后用
nbcon_reacquire_nobuf()复原硬件。复原后wctxt->outbuf == NULL,核心据此判定「本条没打完」。 - 从行中间接管时先补
\n(pl011 / 8250 / imx 的console_line_ended)。 write_atomic()里不能睡眠、不能唤醒任务、不能拿 RT 下会睡眠的锁:8250 把 modem control 延后到 irq_work(7.3 再改 LAZY,37aba16e7943);pl011 原来在write_atomic()里clk_enable(),7.3 改为控制台存续期间时钟常开(c0e8cfef7546)。- 写不出安全原子路径(例如依赖网络栈)时,置
CON_NBCON_ATOMIC_UNSAFE,让write_atomic()只在 panic 末尾被尝试(netconsole,7.0)。 - 非打印路径(改波特率、tty 发送)只要用了
uart_port_lock*()就自动与原子输出互斥;非 uart 驱动要自己调用nbcon_device_try_acquire()/release()。 - 7.0 起,
nbcon_alloc()除检查write_thread外,还要求device_lock/device_unlock必须提供,否则注册失败(bdfcca65e7a6)。
各版本的开发视图增量
| 版本 | 非合并提交数(kernel/printk + console.h) |
新增或改变的模块边界 |
|---|---|---|
| 6.7 | 14 | 新文件 nbcon.c;console.h 增加 nbcon_state / nbcon_context / nbcon_write_context / write_atomic |
| 6.9 | 37(多为 vt 清理) | panic 下 ringbuffer 跳过未完成记录;32 位 seq 宏移入 nbcon |
| 6.12 | 45 | internal.h 新增 console_flush_type;console.h 新增 write_thread / device_lock;serial_core.h 包装函数获取 nbcon;printk.c 新增 legacy 线程 |
| 6.13–6.18 | 3 / 4 / 8 / 0 / 3 / 11 | 驱动目录开始出现 CON_NBCON(imx、pl011、tegra-utc、drm_log、sifive) |
| 6.19 | 22 | CON_NBCON_ATOMIC_UNSAFE;KDB API;console_flush_one_record();console_is_usable() 导出到 console.h |
| 7.0–7.3 | 8 / 4 / 2 / 9 | 执行上下文字段;braille API;线程推迟到 subsys_initcall;8250、qcom-geni 转换 |
自己核对开发视图
# 主线各版本 nbcon.c 的函数清单
git -C linux show v6.7:kernel/printk/nbcon.c | grep -n '^[a-z].*('
git -C linux show master:kernel/printk/nbcon.c | grep -n '^[a-z].*('
# 哪些驱动已是 nbcon(会按需拉取 blob)
git -C linux grep -l CON_NBCON master -- drivers
# 某提交进了哪个版本
git -C linux describe --contains --match 'v[67].*' 2eb2608618ce