printk 是内核最古老的调试工具——从 0.01 版本就存在。但它的实现远比想象的复杂。
一、先看图
flowchart LR
KERN[内核代码] -->|printk| RINGBUF[环形缓冲区<br/>__log_buf]
RINGBUF --> CONSOLE[控制台<br/>串口/VGA]
RINGBUF --> KMSG[/dev/kmsg]
KMSG --> JOURNALD[systemd-journald]
RINGBUF --> NETCON[netconsole<br/>UDP 远程]
classDef kern fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef out fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class KERN,RINGBUF kern
class CONSOLE,KMSG,JOURNALD,NETCON out
二、printk 基础
printk(KERN_ERR "Error: %s failed with %d\n", name, ret);
pr_err("Error: %s failed with %d\n", name, ret); // 推荐
dev_err(dev, "failed with %d\n", ret); // 设备相关2.1 日志级别
| 级别 | 宏 | 含义 |
|---|---|---|
| 0 | KERN_EMERG | 紧急 |
| 1 | KERN_ALERT | 警报 |
| 2 | KERN_CRIT | 严重 |
| 3 | KERN_ERR | 错误 |
| 4 | KERN_WARNING | 警告 |
| 5 | KERN_NOTICE | 通知 |
| 6 | KERN_INFO | 信息 |
| 7 | KERN_DEBUG | 调试 |
三、环形缓冲区
# 缓冲区大小(默认 1MB)
dmesg | wc -c
# 或内核参数
log_buf_len=4M固定大小环形缓冲区 → 满了就覆盖旧的。
// kernel/printk/printk.c
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);四、dev_printk 系列
dev_info(dev, "device initialized\n");
dev_warn(dev, "firmware version %d is old\n", ver);
dev_err(dev, "probe failed: %d\n", ret);自动包含设备信息(bus、device name)→ 方便过滤。
五、Rate Limiting
printk_ratelimited(KERN_WARNING "too many retries\n");
// 或
if (net_ratelimit())
pr_warn("packet dropped\n");防止日志洪水 → 默认 10 条/5 秒。
六、netconsole
# 内核参数
netconsole=@192.168.1.1/eth0,@192.168.1.2/aa:bb:cc:dd:ee:ff
# 或模块
modprobe netconsole netconsole=@192.168.1.1/eth0,6666@192.168.1.2/通过 UDP 发送内核日志到远程 → panic 时也能看到最后的消息(如果网络还活着)。
七、printk lockless 改造
传统 printk 使用 logbuf_lock → 高频打印可能造成延迟。
Linux 5.10+ 改为 lockless ring buffer:
// kernel/printk/printk_ringbuffer.c
struct printk_ringbuffer结构化日志记录 → 每条消息包含时间戳、级别、设备信息。
八、/dev/kmsg 与 journald
cat /dev/kmsg # 结构化格式
dmesg # 传统格式
journalctl -k # 通过 journald 读取systemd-journald 从 /dev/kmsg 读取 →
合并内核和用户态日志 → 统一查询。
九、观察
dmesg -T # 人类可读时间
dmesg --level=err # 只看错误
dmesg -w # 实时跟踪
# 控制台级别
cat /proc/sys/kernel/printk
# current default minimum boot-time-default
# 动态调试
echo 'file drivers/nvme/* +p' > /sys/kernel/debug/dynamic_debug/control十、小结
- printk 写入内核环形缓冲区 → 输出到控制台/kmsg/netconsole
- dev_printk 系列自动包含设备信息
- rate limiting 防止日志洪水
- lockless ring buffer 解决高频打印延迟
- netconsole 用于远程调试
参考文献
kernel/printk/Documentation/admin-guide/dynamic-debug-howto.rstDocumentation/networking/netconsole.rst- John Ogness, “printk rework.” LPC 2019
工具
dmesgjournalctl -k/dev/kmsgnetconsole
上一篇:lockdep 下一篇:/proc 与 /sys
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。