kernel/printk 内核日志(printk)机制与原理详解
源码路径:rk3588/kernel-6.1/kernel/printk/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Linux 内核 日志输出核心:printk() / pr_info() 等宏的底层存储、环形缓冲区、控制台驱动调度、用户态读取接口(/dev/kmsg、syslog),以及 NMI/递归安全路径。Linux 6.1 采用 无锁 printk ringbuffer(prb) 替代早期单一 log_buf 方案,支持多读者、多写者并发。
定位:内核诊断与调试的「消息总线」;串口/UART 控制台驱动在 drivers/tty/serial/,通过 register_console() 接入本框架。
目录
一、源码目录结构
1.1 编译依赖(Makefile)
1 2 3 4 5 6 7 8 9
| obj-y += printk.o
obj-$(CONFIG_PRINTK) += printk_safe.o obj-$(CONFIG_PRINTK) += printk_support.o printk_support-y := printk_ringbuffer.o printk_support-$(CONFIG_SYSCTL) += sysctl.o
obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) += braille.o obj-$(CONFIG_PRINTK_INDEX) += index.o
|
| 配置项 |
说明 |
CONFIG_PRINTK |
启用 printk(默认 y) |
CONFIG_LOG_BUF_SHIFT |
log buffer 大小 = 1 << shift(默认 17 = 128KB) |
CONFIG_PRINTK_TIME |
日志带时间戳 |
CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER |
ARM 架构定时器时间源(RK 非 GKI) |
CONFIG_PRINTK_CALLER |
显示 caller pid/cpu |
CONFIG_PRINTK_INDEX |
debugfs 导出 printk 格式索引 |
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT |
safe 模式备用 buffer |
CONFIG_CONSOLE_LOGLEVEL_DEFAULT |
控制台默认 loglevel |
CONFIG_A11Y_BRAILLE_CONSOLE |
盲文控制台 |
Kconfig 分散在 init/Kconfig、lib/Kconfig.debug。
1.2 源文件
| 文件 |
行数 |
功能 |
printk.c |
4053 |
核心:存储、console、syslog、devkmsg、kmsg_dump |
printk_ringbuffer.c |
2124 |
无锁 ringbuffer 实现 |
printk_ringbuffer.h |
384 |
prb 数据结构与 API |
index.c |
194 |
debugfs printk 格式索引 |
sysctl.c |
85 |
/proc/sys/kernel/printk* |
internal.h |
60 |
内部 API、printk_safe 宏 |
printk_safe.c |
47 |
NMI/递归上下文 vprintk 路由 |
braille.c |
58 |
盲文控制台 |
console_cmdline.h |
16 |
命令行 console 解析 |
相关头文件(目录外):
| 文件 |
功能 |
include/linux/printk.h |
printk() 宏、pr_* 系列 |
include/linux/console.h |
struct console、register_console |
include/linux/kmsg_dump.h |
panic/oops 时 kmsg 转储 |
drivers/tty/serial/ |
UART 控制台(如 8250、DesignWare) |
二、整体架构
2.1 数据流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| printk() / pr_info() / dev_printk() │ ▼ vprintk() ──► [NMI/recursion?] ──► vprintk_deferred() │ │ └─ vprintk_default() │ (仅写 ringbuffer) │ │ ▼ │ vprintk_emit() │ │ │ ├── vprintk_store() ────┘ → prb_reserve / prb_commit │ (写入 ringbuffer) │ ├── console_trylock_spinning() │ console_unlock() → 各 console->write() │ └── wake_up_klogd() → /dev/kmsg 读者
|
2.2 双阶段设计
| 阶段 |
说明 |
| 存储 |
所有消息写入 printk ringbuffer,带 seq、timestamp、level |
| 输出 |
持有 console_sem 的线程从 prb 读取并调用 console->write() |
存储与输出解耦:在 IRQ/NMI 中可安全写 buffer,控制台驱动稍后在进程上下文刷出。
2.3 启动阶段 buffer
1 2 3 4
| 早期: __log_buf[1 << CONFIG_LOG_BUF_SHIFT] + printk_rb_static setup_log_buf(early=1) — 启动极早阶段 后期: setup_log_buf(early=0) — 分配动态 prb,迁移 static 记录 prb = &printk_rb_dynamic
|
三、printk Ringbuffer(printk_ringbuffer.c)
3.1 结构
1 2 3 4 5
| struct printk_ringbuffer { struct prb_desc_ring desc_ring; struct prb_data_ring text_data_ring; atomic_long_t fail; };
|
三个逻辑环:
- desc_ring:
prb_desc(state_var + text_blk_lpos)+ printk_info(seq、ts、level、caller_id)
- text_data_ring:变长文本块,每块
[ID | padding | text]
3.2 描述符状态机
| 状态 |
含义 |
desc_reserved |
写者正在写入 |
desc_committed |
数据一致,写者可 reopen |
desc_finalized |
不可修改,读者可见 |
desc_reusable |
已回收,可复用 |
desc_miss |
读者查询 ID 不匹配(伪状态) |
state_var 将 ID 与 state 打包 到 atomic_long_t,实现无锁 CAS 更新。
3.3 写者 API
1 2 3 4 5
| prb_rec_init_wr(&r, reserve_size); prb_reserve(&e, prb, &r);
prb_commit(&e); prb_final_commit(&e);
|
LOG_CONT 续行:prb_reserve_in_last() 追加到上一条未 finalize 的记录。
3.4 读者 API
1 2 3
| prb_rec_init_rd(&r, &info, buf, size); prb_read_valid(prb, seq, &r); prb_first_seq() / prb_next_seq()
|
四、日志写入路径(printk.c)
4.1 vprintk_store()
核心存储函数:
1 2 3 4 5 6 7
| 1. printk_enter_irqsave() — 递归计数 ≤ PRINTK_MAX_RECURSION(3) 2. ts_nsec = get_local_clock() — ARM 可用 arch_timer 3. vsnprintf 预估长度 → prb_reserve 4. printk_parse_prefix() — 解析 "<6>" level 与 "c" continuation 5. printk_sprint() — 格式化正文(syslog 前缀、device 信息等) 6. prb_final_commit() 或 prb_commit() 7. printk_exit_irqrestore()
|
4.2 vprintk_emit()
1 2 3 4 5 6 7 8 9 10
| if (suppress_printk) return 0; printed_len = vprintk_store(...);
if (!in_sched) { preempt_disable(); if (console_trylock_spinning()) console_unlock(); preempt_enable(); wake_up_klogd(); }
|
LOGLEVEL_SCHED 路径 defer 控制台输出,避免调度器内死锁。
4.3 printk_parse_prefix()
解析内核日志前缀:
| 前缀 |
含义 |
<0> … <7> |
syslog level |
<c> / <N c> |
continuation(续行) |
KERN_* 宏 |
编译期嵌入 level |
4.4 console_printk[]
1 2 3 4 5 6
| int console_printk[4] = { CONSOLE_LOGLEVEL_DEFAULT, MESSAGE_LOGLEVEL_DEFAULT, CONSOLE_LOGLEVEL_MIN, CONSOLE_LOGLEVEL_DEFAULT, };
|
/proc/sys/kernel/printk 四个整数:console_loglevel、default_message_loglevel、minimum_console_loglevel、default_console_loglevel。
4.5 递归保护
1 2 3 4 5
| #define PRINTK_MAX_RECURSION 3 static DEFINE_PER_CPU(u8, printk_count); #ifdef CONFIG_HAVE_NMI static DEFINE_PER_CPU(u8, printk_count_nmi); #endif
|
NMI 与普通上下文 分开计数,防止 printk 触发 WARN 再次 printk 无限递归。
五、控制台输出(console)
5.1 struct console
1 2 3 4 5 6 7 8 9 10
| struct console { char name[16]; void (*write)(struct console *, const char *, unsigned); int (*read)(struct console *, char *, unsigned); struct device *dev; u32 seq; unsigned long dropped; short flags; };
|
5.2 register_console()
1 2 3 4 5 6
| 1. 匹配 cmdline / DT / SPCR preferred console 2. 插入 console_drivers 链表(CON_CONSDEV 在头) 3. 若 CON_PRINTBUFFER:从 syslog_seq 重放历史 4. 否则:seq = prb_next_seq(prb)(仅新消息) 5. console_unlock() 刷出 6. 真实 console 注册后 unregister bootconsole
|
RK3588 典型路径:earlycon=uart8250,... → boot console → ttyS* 驱动 register_console()。
5.3 console_lock 与 spinning
传统 console_sem 串行化控制台访问。优化路径:
1 2 3 4 5
| console_trylock_spinning() → 若当前持有者 same CPU 且可 schedule → console_waiter = 1,自旋等待 handover console_lock_spinning_enable/disable() → 减少 printk 持锁期间阻塞其他 CPU
|
5.4 console_emit_next_record()
1 2 3 4 5
| prb_read_valid(prb, con->seq, &r) suppress_message_printing(level) → 高于 console_loglevel 则 skip record_print_text() / info_print_ext_header() call_console_driver(con, text, len) con->seq++
|
5.5 特殊场景
| 场景 |
行为 |
oops_in_progress |
放宽 console 锁检查 |
suppress_panic_printk |
panic 时非 panic CPU 抑制 printk |
suspend_console() |
睡眠前停止 console 输出 |
console_flush_on_panic() |
panic 时强制刷所有 console |
六、printk-safe 与递归保护
6.1 printk_safe.c
1 2 3 4 5 6 7
| asmlinkage int vprintk(const char *fmt, va_list args) { if (this_cpu_read(printk_context) || in_nmi()) return vprintk_deferred(fmt, args);
return vprintk_default(fmt, args); }
|
printk_safe_enter_irqsave() / __printk_safe_enter() 递增 per-CPU printk_context,标记「安全模式」—— 控制台驱动可能持锁,不可再调 console。
6.2 vprintk_deferred
仅调用 vprintk_store(),不触发 console_unlock();由 defer_console_output() 或后续 printk 刷出。
七、用户态接口
7.1 /dev/kmsg
字符设备(devkmsg_read/write):
- 读:按 seq 读 ringbuffer 记录(含 facility、level、timestamp)
- 写:用户态注入 kernel 日志(需
SYSLOG capability)
- poll:新消息唤醒
devkmsg_log:on / off / ratelimit(printk.devkmsg= 内核参数可永久锁定)
7.2 syslog 系统调用
1
| int do_syslog(int type, char __user *buf, int len, int source)
|
| type |
功能 |
SYSLOG_ACTION_READ |
读 ringbuffer |
SYSLOG_ACTION_READ_ALL |
读全部(含已读) |
SYSLOG_ACTION_READ_CLEAR |
读并清除 |
SYSLOG_ACTION_CLEAR |
清除 |
SYSLOG_ACTION_SIZE_UNREAD |
未读字节数 |
dmesg_restrict:限制非特权用户读 kernel 日志。
7.3 kmsg_dump
1 2
| kmsg_dump_register(struct kmsg_dumper *dumper); kmsg_dump(KMSG_DUMP_OOPS / PANIC / ...);
|
panic/oops 时 pstore、mtdoops、ramoops 等通过 kmsg_dump_get_line() 导出 ringbuffer。
八、Sysctl 与调试(sysctl.c / index.c)
8.1 /proc/sys/kernel/
| 节点 |
说明 |
printk |
四个 loglevel 整数 |
printk_ratelimit |
速率限制间隔 |
printk_ratelimit_burst |
burst 计数 |
printk_delay |
每条 printk 延迟 ms(调试) |
printk_devkmsg |
on/off/ratelimit |
dmesg_restrict |
限制 dmesg 访问 |
kptr_restrict |
%pK 指针隐藏级别 |
8.2 PRINTK_INDEX(index.c)
debugfs:/sys/kernel/debug/printk/index/vmlinux(及模块名)
导出编译期 .printk_index 段:
1 2
| # <level/flags> filename:line function "format" <6> sched/core.c:1234 schedule "scheduling..."
|
便于日志分析与静态格式检索。
8.3 其他调试
| 机制 |
说明 |
ignore_loglevel |
忽略 console_loglevel(cmdline) |
pm_test |
分阶段 suspend 测试(power 子系统) |
suspend_stats debugfs |
与 power 共享统计思路 |
printk_time |
控制台/syslog 显示时间戳 |
九、RK3588/ARM64 平台说明
9.1 时间戳源
1 2 3 4 5 6 7 8 9
| #ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER static u64 get_local_clock(void) { return ns_from_counter(arch_timer_read_counter(), cntfrq); } #else static inline u64 get_local_clock(void) { return local_clock(); } #endif
|
CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER 依赖 ARM_ARCH_TIMER && NO_GKI,RK 非 GKI 内核可选,使 printk 时间与 arch timer 一致(boot 阶段更稳定)。
9.2 控制台与 earlycon
RK3588 常用配置:
1 2 3
| console=ttyFIQ0,1500000 earlycon=uart8250,mmio32,0xfeb50000 loglevel=8
|
| 组件 |
路径 |
| FIQ debugger / early console |
drivers/tty/serial/rockchip_fiq_debugger.c 等 |
| 8250/DesignWare UART |
drivers/tty/serial/8250/ |
register_console() |
驱动 probe 后注册 |
9.3 log buffer 大小
默认 CONFIG_LOG_BUF_SHIFT=17(128KB)。RK3588 嵌入式若日志量大,可增大至 18–20(256KB–1MB),通过 log_buf_len= 或 menuconfig。
9.4 多核与 big.LITTLE
- caller_id:task 为 pid;非 task 上下文为
0x80000000 + cpu_id
- per-CPU printk_count:各 CPU 独立递归计数
- console spinning handover:减少 big.LITTLE 上高并发 printk 时的锁竞争
- panic 抑制:
suppress_panic_printk 防止多核同时 printk 拖慢 panic 处理
9.5 常用调试命令
1 2 3 4 5 6 7 8 9 10 11
| dmesg -w dmesg -T
cat /dev/kmsg
echo 8 > /proc/sys/kernel/printk
|
十、完整 printk 时序
NMI 路径:vprintk → vprintk_deferred → 仅 vprintk_store,不进入 Console 分支;稍后由普通 printk 或 defer_console_output() 刷出。
十一、总结
| 组件 |
职责 |
| printk_ringbuffer.c |
无锁 prb:reserve/commit/read,支持 continuation |
| printk.c |
格式化、存储、console 调度、syslog/devkmsg、kmsg_dump |
| printk_safe.c |
NMI/递归上下文路由到 deferred 路径 |
| sysctl.c |
loglevel、ratelimit、dmesg_restrict 等 sysctl |
| index.c |
debugfs 导出编译期 printk 格式 |
Linux 6.1 printk 以 ringbuffer 为中心,将「快速存储」与「慢速 console 输出」分离,在 SMP、NMI、panic 场景下保持可预测行为。RK3588 平台通过 UART/earlycon 接入 register_console(),可选 ARM arch timer 作为时间戳源。
附录:源文件清单
| 文件 |
行数 |
kernel/printk/printk.c |
4053 |
kernel/printk/printk_ringbuffer.c |
2124 |
kernel/printk/printk_ringbuffer.h |
384 |
kernel/printk/index.c |
194 |
kernel/printk/sysctl.c |
85 |
kernel/printk/internal.h |
60 |
kernel/printk/braille.c |
58 |
kernel/printk/braille.h |
56 |
kernel/printk/printk_safe.c |
47 |
kernel/printk/console_cmdline.h |
16 |
kernel/printk/Makefile |
10 |
| 合计 |
~7077 |
关联源码(目录外):
| 文件 |
说明 |
include/linux/printk.h |
公共 printk 宏 |
include/linux/console.h |
console 驱动接口 |
lib/Kconfig.debug |
PRINTK_TIME 等选项 |
init/Kconfig |
LOG_BUF_SHIFT、PRINTK |
drivers/tty/serial/*.c |
UART 控制台驱动 |
正在加载留言…