nbcon 专题Linux v7.3 · 6.12.113

07 · E2 所有权模型(6.7):给每个控制台一个状态字

结论:6.7 合入的 8 个 nbcon 提交(7 个署名 Thomas Gleixner、与 John Ogness 共同开发,1 个出自 John Ogness)只做了一件事:定义控制台所有权的语义。每个 struct console 增加一个 32 位原子字 nbcon_state,记录当前所有者的优先级、所在 CPU、是否处于不可打断区、有无等待交接者。在此之上定义三级优先级和 direct / handover / hostile 三种获取方式,加上供驱动标记 unsafe 区的 API 和 write_atomic() 回调。6.7 里 nbcon_emit_next_record() 没有任何调用者,也没有任何驱动置 CON_NBCON。它是纯粹的骨架,但这套语义此后再也没改过,后续版本只是在上面接线程、接 emergency、接驱动。

要解决的问题

# 问题 出处(提交说明原文)
P1 console_lock 无状态,不知道谁在打印、打到哪里、能否接管 6b93bb41f6ea:"The current console/printk subsystem is protected by a Big Kernel Lock, (aka console_lock) which has ill defined semantics and is more or less stateless."
P2 panic / 紧急输出只能硬闯 同上:"makes forced takeover and output in emergency and panic situations a fragile endeavour that is based on try and pray."
P3 全局锁让一个慢控制台拖累所有控制台 同上,目标第一条:"Per console locking instead of global locking"
P4 驱动没法表达「这几行寄存器操作不能被打断」 9757acd0a700:"the console driver may have unsafe regions that need to be appropriately marked"

6.6 时,panic 与正在打印的 CPU 抢同一个 pl011 的真实代码(v6.6 amba-pl011.c:2326-2345):

    local_irq_save(flags);
    if (uap->port.sysrq)
        locked = 0;
    else if (oops_in_progress)
        locked = spin_trylock(&uap->port.lock);   /* 拿不到也照写 */
    else
        spin_lock(&uap->port.lock);
    ...
    uart_console_write(&uap->port, s, count, pl011_console_putchar);

方案与提交(全部在 6.7)

提交 标题 引入的东西
6b93bb41f6ea printk: Add non-BKL (nbcon) console basic infrastructure CON_NBCON、nbcon_state 位域、注册 / 注销接线。"It was decided to use a bitfield because using a plain u32 with mask/shift operations resulted in uncomprehensible code."
3a5bb25162b8 printk: nbcon: Add acquire/release logic direct / handover / hostile 三种获取,req_prio 握手,unsafe_takeover
5634c90fd855 Add buffer management 每控制台 pbufs,panic CPU 用独立缓冲
d818b56f7752 Make static printk buffers available to nbcon boot console 共享全局缓冲
4b08d9e24f50 Add ownership state functions nbcon_can_proceed() 等
ad56ebd1d79b Add sequence handling 每控制台 nbcon_seq,cmpxchg 推进
06653d57ff28 Add emit function and callback function for atomic printing nbcon_emit_next_record()、write_atomic()、nbcon_write_context
9757acd0a700 Allow drivers to mark unsafe regions and check state nbcon_enter_unsafe() / nbcon_exit_unsafe()

架构图:之前 / 之后

之前(6.6):panic 时只能 try and pray
之前(6.6):panic 时只能 try and pray
之后(6.7):每控制台一个所有权状态字
之后(6.7):每控制台一个所有权状态字

调用栈

之前 · 6.6 panic 与打印者冲突(行号为 v6.6):

── CPU1:正在打印 ─────────────────────────────────────────────
console_unlock()                                                printk.c:3012
  console_flush_all()                                           :2945
    console_emit_next_record()                                  :2864
      con->write() = pl011_console_write()                      :2910 → amba-pl011.c:2326
        spin_lock(&uap->port.lock)                              :2341
        uart_console_write() 逐字符 …… ← 被 panic 的停核 IPI 打断

── CPU0:panic ───────────────────────────────────────────────
panic() → smp_send_stop()
console_flush_on_panic()                                        6.6 起不拿 console_lock(eacb04ff3c5b)
  console_flush_all() → pl011_console_write()
    oops_in_progress → spin_trylock(&port->lock) 失败 → locked = 0,照写

之后 · 6.7 定义好但无人调用的 nbcon 路径(行号为 v6.7 nbcon.c):

(6.7 中没有调用者——以下是函数间的调用关系)
nbcon_context_try_acquire(ctxt)                                 :575
  nbcon_context_try_acquire_direct()                            :259
  nbcon_context_try_acquire_handover()                          :422
    nbcon_context_try_acquire_requested()                       :339
  nbcon_context_try_acquire_hostile()                           :528
nbcon_emit_next_record(wctxt)                                   :877
  nbcon_context_enter_unsafe() → printk_get_next_message()
  console_prepend_dropped() → nbcon_context_exit_unsafe()
  done = con->write_atomic(con, wctxt)                          6.7 返回 bool;6.12 改为 void(b7049d88c176)
  nbcon_context_enter_unsafe() → nbcon_seq_try_update() → exit_unsafe
nbcon_context_release(ctxt)                                     :637

register_console()                                              v6.7 printk.c
  if (newcon->flags & CON_NBCON) nbcon_alloc(newcon)            :3450-3455
  …… nbcon_init(newcon)                                          :3506-3507

驱动侧 API(6.7 定稿):

bool nbcon_can_proceed(wctxt)                                   v6.7 nbcon.c:758
bool nbcon_enter_unsafe(wctxt)                                  :832
bool nbcon_exit_unsafe(wctxt)                                   :852
    false = 所有权已丢失:立即退出,缓冲不再可信

设计要点:为什么是这三种获取方式

4+1 增量

视图 E2 带来的变化
场景 只是为 S2 / S3 准备了语义;6.7 没有任何场景真正走 nbcon
逻辑 引入 nbcon_state、三级优先级、三种获取、unsafe 区:今天的逻辑视图在 6.7 就已成形
进程 尚无新执行体
开发 新文件 kernel/printk/nbcon.c(1029 行);console.h 新增 nbcon 类型与 write_atomic
物理 依赖 NMI 安全的 cmpxchg(arm64 已具备)

留下的问题:printk 仍走 console_lock;没有 write_thread、没有线程、没有 emergency;驱动不知道如何与自己的 port->lock 协同。这些都要到 E3。