nbcon 专题Linux v7.3 · 6.12.113

02 · 逻辑视图:对象、优先级与所有权状态机

结论:nbcon 的全部同步语义都压在每个控制台的一个 32 位原子字 nbcon_state 里,字段为 prio:2 | req_prio:2 | unsafe:1 | unsafe_takeover:1 | cpu:24。获取所有权只有三种方式:direct(无主,或对方优先级更低且处于安全点)、handover(对方在 unsafe 区:写 req_prio 后自旋等它让出,最长 spinwait_max_us,原子刷出时为 2000 µs)、hostile(仅 panic 末尾,并永久标记 unsafe_takeover)。优先级只有三级:NORMAL < EMERGENCY < PANIC,由「当前 CPU 是否在 panic / emergency 段」决定,而不是由调用者传入。丢失所有权的一方必须立即退出,接管者从头重印被打断的那条记录。这套语义从 6.7 引入至今没有改过,后续演进都是在它上面调整策略。

对象模型

逻辑视图 ①:struct console 的 legacy / nbcon 两套字段、nbcon_state 位域、上下文结构与 ringbuffer
逻辑视图 ①:struct console 的 legacy / nbcon 两套字段、nbcon_state 位域、上下文结构与 ringbuffer
对象 定义(v7.3) 生命周期 作用
struct console 的 nbcon 部分 include/linux/console.h:464-472 register_console() → nbcon_alloc()(nbcon.c:1770)到 nbcon_free()(:1833) 每控制台的状态字、打印进度、私有缓冲、线程、唤醒对象
struct nbcon_state console.h:215(static_assert ≤ int) 只通过 nbcon_state_try_cmpxchg()(nbcon.c:156)修改 所有权的唯一事实来源
enum nbcon_prio console.h:248 — NONE / NORMAL / EMERGENCY / PANIC
struct nbcon_context console.h:272 调用方栈上,或 con->nbcon_device_ctxt(给 tty 侧用) 一次获取 / 打印尝试的参数与结果
struct nbcon_write_context console.h:297 包着 nbcon_context,交给驱动回调 outbuf/len 是本次要写的文本;7.0 起带 cpu/pid/comm(CONFIG_PRINTK_EXECUTION_CTX)
struct console_flush_type kernel/printk/internal.h:181 每次 printk 现算 本 CPU 此刻该走哪几条刷出路径
printk_buffers internal.h:271 每控制台一份;panic CPU 用 panic_nbcon_pbufs 格式化缓冲:outbuf 2048 + scratchbuf 1024

进度与 ringbuffer 的关系:每个 nbcon 控制台有自己的 nbcon_seq(下一条要打印的记录号,nbcon_seq_read() nbcon.c:168),在 32 位机上只存低 32 位,再由 prb 补全。nbcon_seq_try_update()(:207)用 cmpxchg 推进,失败说明期间被别人接管或被 nbcon_seq_force() 重置。nbcon_alloc() 把初值设为 ULSEQ_MAX(:1794,6.12 的 fb9fabf3d862),注册流程再显式设定起点,避免「什么也没设就开始重放」。

被接管后的重印:nbcon_prev_seq 记住上一任所有者被分配到的记录号。新所有者在 nbcon_emit_next_record()(nbcon.c:980)里发现同一条记录被分配过两次,就调用 console_prepend_replay() 加上 ** replaying previous printk message ** 前缀(6.12 的 5102981d5e2a)。

优先级怎么来

nbcon_get_default_prio()                                          nbcon.c:1442
  panic_on_this_cpu()          → NBCON_PRIO_PANIC
  *cpu_emergency_nesting > 0   → NBCON_PRIO_EMERGENCY             (per-CPU 计数,nbcon.c:1402)
  否则                          → NBCON_PRIO_NORMAL

所有权状态机

逻辑视图 ②:direct / handover / hostile 三种获取与 unsafe 区
逻辑视图 ②:direct / handover / hostile 三种获取与 unsafe 区
nbcon_context_try_acquire(ctxt, is_reacquire)                     nbcon.c:598
  nbcon_state_read(con, &cur)
try_again:
  err = nbcon_context_try_acquire_direct(ctxt, &cur, is_reacquire)   :243
        panic_on_other_cpu() 且非 reacquire          → -EPERM
        ctxt->prio <= cur.prio 或 <= cur.req_prio    → -EPERM
        cur.unsafe                                   → -EBUSY(转 handover)
        cmpxchg {prio=ctxt->prio, req_prio=0, cpu=this}
  if (err == -EBUSY)
    err = nbcon_context_try_acquire_handover(ctxt, &cur)            :434
        同 CPU / 已 unsafe_takeover / spinwait_max_us == 0 → -EBUSY
        cmpxchg {req_prio = ctxt->prio}(失败 → -EAGAIN → try_again)
        for (timeout = spinwait_max_us; timeout >= 0; timeout--)
            nbcon_context_try_acquire_requested()                   :351
            udelay(1)
        超时:小心撤销 req_prio
  if (err == -EBUSY)
    err = nbcon_context_try_acquire_hostile(ctxt, &cur)             :550
        仅 allow_unsafe_takeover 且 prio == PANIC
        cmpxchg {prio, cpu, unsafe |= ..., unsafe_takeover |= cur.unsafe}
  成功:ctxt->pbufs = panic CPU ? &panic_nbcon_pbufs : con->pbufs
        ctxt->seq = nbcon_seq_read(con)

所有者这一侧的义务:

两条不变式:

  1. nbcon_owner_matches()(:635)只比较 prio 与 cpu。所以 NORMAL 所有者如果允许被抢占或迁移,就会有 ABA 风险,注释(:653-661)给了完整反例。这就是 device_lock() 必须禁迁移、线程路径必须 cant_migrate() 的原因。
  2. 发生过 hostile 之后,unsafe_takeover 会一直保持置位,控制台被视为永久不安全,直到重新初始化;之后的 direct 获取会触发 WARN_ON_ONCE(:280)。

选路:console_flush_type

printk_get_console_flush_type(&ft)                                internal.h:194
  NORMAL:     nbcon 且无 boot console → printk_kthreads_running && !console_irqwork_blocked
                                         ? nbcon_offload : nbcon_atomic
              legacy 或 boot console  → is_printk_legacy_deferred() ? legacy_offload : legacy_direct
  EMERGENCY:  nbcon 且无 boot console → nbcon_atomic
              legacy                  → 同上(direct / offload)
  PANIC:      nbcon 且无 boot console → nbcon_atomic
              legacy                  → 未 legacy_allow_panic_sync 前保持沉默;panic 时不 offload

is_printk_legacy_deferred()(printk_safe.c:59)在 force_legacy_kthread()(RT 上为真,internal.h:31)、printk_deferred 上下文、NMI 或持有 printk_cpu_sync 时返回真。所以 RT 上 legacy 控制台永远走 offload,由 pr/legacy 打印,这是 E3 的核心设计。

与 6.12.113 的差异

项目 6.12.113 7.3
CON_NBCON_ATOMIC_UNSAFE 无 有(6.19 187de7c212e5)
全局 emergency 计数 / 阻塞线程 无 有(6.19 c41c0ebfa1e0)
原子刷出粒度 整段持所有权、整段关中断(6.12.113 nbcon.c:1496-1586) 逐条 acquire / release、逐条恢复中断
panic 中 reacquire 有(已回合,is_reacquire 参数) 有
irq_work IRQ_WORK_INIT_LAZY(已回合,6.12.113 nbcon.c:1698) 同
console_irqwork_blocked(suspend) 无 有(6.19)
nbcon_write_context 执行上下文字段 无 有(7.0)