nbcon 专题Linux v7.3 · 6.12.113

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 等使用。

驱动契约

开发视图 ②:驱动实现四件事,调用核心四类 API;转换中的典型坑
开发视图 ②:驱动实现四件事,调用核心四类 API;转换中的典型坑

以 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 的驱动提交与修复):

  1. device_lock() 用驱动自己的锁,并且必须禁迁移。uart 用 __uart_port_lock_irqsave()(双下划线版本,不再嵌套获取 nbcon)。
  2. 所有寄存器访问都放进 unsafe 区。write_thread 逐字节开安全窗口,write_atomic 整串一个区。
  3. 被抢后用 nbcon_reacquire_nobuf() 复原硬件。复原后 wctxt->outbuf == NULL,核心据此判定「本条没打完」。
  4. 从行中间接管时先补 \n(pl011 / 8250 / imx 的 console_line_ended)。
  5. write_atomic() 里不能睡眠、不能唤醒任务、不能拿 RT 下会睡眠的锁:8250 把 modem control 延后到 irq_work(7.3 再改 LAZY,37aba16e7943);pl011 原来在 write_atomic() 里 clk_enable(),7.3 改为控制台存续期间时钟常开(c0e8cfef7546)。
  6. 写不出安全原子路径(例如依赖网络栈)时,置 CON_NBCON_ATOMIC_UNSAFE,让 write_atomic() 只在 panic 末尾被尝试(netconsole,7.0)。
  7. 非打印路径(改波特率、tty 发送)只要用了 uart_port_lock*() 就自动与原子输出互斥;非 uart 驱动要自己调用 nbcon_device_try_acquire() / release()。
  8. 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