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 与打印者冲突(行号为 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 = 所有权已丢失:立即退出,缓冲不再可信
设计要点:为什么是这三种获取方式
- direct 覆盖绝大多数情况:控制台空闲,或者对方优先级低且正好在安全点。成本是一次 cmpxchg。
- handover 处理「对方在 unsafe 区」的情况:不能硬抢(寄存器可能写了一半),也不能无限等(自己是紧急路径)。于是写
req_prio告诉对方「出 unsafe 区就让给我」,然后自旋等待,有超时。nbcon_waiter_matches()的长注释(v7.3nbcon.c:293-325)证明了:由于只有三级优先级、只有一个 CPU 能拿 PANIC,等待者身份由req_prio唯一确定。 - hostile 只留给 panic 的最后一搏:寄存器状态可能已被破坏,所以设
unsafe_takeover永久标记,驱动以后可从wctxt->unsafe_takeover得知。 - 被抢后从头重印:
06653d57ff28定义的发射流程把「取记录、格式化、推进 seq」都放在 unsafe 区内,write_atomic()外的状态更新也在 unsafe 区内,「推进到一半被抢」因此不会出现。代价是同一条记录可能被打两遍。
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。