3. 日志级别宏详解:pr_emerg 到 pr_cont
在 Linux 内核开发与调试中,printk 是最基础也是最常用的日志输出接口。为了对日志进行分级管理和动态过滤,内核定义了一套完整的日志级别宏。本章将逐一剖析从 pr_emerg 到 pr_cont 的 9 个核心宏,涵盖其语义、使用场景、数值对应关系以及实际调试中的选择策略。
3.1 日志级别与数值映射
每个 printk 日志级别都对应一个整数,数值越小表示优先级越高(紧急程度越高)。系统根据当前控制台的日志级别(loglevel)决定哪些消息能够被显示。下表列出了所有标准日志级别及其宏定义:
| 宏名称 | 数值 | 含义 | 典型使用场景 |
|---|---|---|---|
pr_emerg |
0 | 系统不可用 | 内核崩溃、致命硬件错误 |
pr_alert |
1 | 必须立即采取行动 | 严重数据损坏、系统即将挂起 |
pr_crit |
2 | 临界条件 | 严重驱动故障、内存耗尽临界 |
pr_err |
3 | 错误条件 | 设备 I/O 错误、系统调用失败 |
pr_warning |
4 | 警告条件 | 资源不足、配置异常、降级运行 |
pr_notice |
5 | 正常但重要的条件 | 驱动加载成功、热插拔事件 |
pr_info |
6 | 信息性消息 | 版本号、参数初始化、状态变更 |
pr_debug |
7 | 调试消息 | 函数入口/出口、变量值跟踪 |
pr_cont |
— | 续行消息 | 多行日志拼接、进度指示 |
KERN_WARNING),这意味着 pr_info 和 pr_debug 默认不会输出到控制台,但会写入内核日志缓冲区(可通过 dmesg 查看)。调试阶段建议通过 loglevel=8 内核参数临时提升级别。
3.2 核心宏详解
3.2.1 pr_emerg — 系统不可用
这是最高优先级的日志级别,仅在系统完全无法继续运行时使用。例如内核 panic、检测到不可恢复的硬件故障。使用 pr_emerg 输出的消息会立即显示在所有控制台上,且通常伴随系统挂起或重启。
// 示例:检测到致命 CPU 缓存错误
if (cache_error_fatal) {
pr_emerg("CPU%d: L2 cache parity error, system halted\n", smp_processor_id());
panic("Fatal cache error");
}
3.2.2 pr_alert — 必须立即行动
表示系统处于危险状态,需要管理员立即介入。例如文件系统检测到严重元数据损坏、关键硬件 watchdog 超时。与 pr_emerg 的区别在于系统可能仍能运行有限时间。
// 示例:RAID 阵列丢失多个磁盘
pr_alert("RAID array /dev/md0: %d disks failed, data at risk\n", failed_disks);
3.2.3 pr_crit — 临界条件
用于临界状态,例如内存分配连续失败、中断处理超时、驱动进入未知状态。这些错误如果不处理,很快就会升级为系统不可用。
// 示例:DMA 分配失败
if (!dma_buffer) {
pr_crit("Unable to allocate DMA buffer for NIC, network may stall\n");
return -ENOMEM;
}
3.2.4 pr_err — 错误条件
最常用的错误报告宏,表示某个操作失败但系统整体仍可运行。例如设备 probe 失败、系统调用返回错误、文件读写异常。调试时 pr_err 是定位问题的第一道防线。
// 示例:I2C 设备通信失败
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
pr_err("i2c: failed to write to device %s, err=%d\n", client->name, ret);
return ret;
}
3.2.5 pr_warning — 警告条件
表示系统遇到非预期情况,但能够自动恢复或降级运行。例如内存碎片化、时钟漂移、驱动使用过时 API。警告消息应附带可操作的修复建议。
// 示例:中断处理耗时过长
pr_warning("IRQ%d: handler took %lu us, consider using threaded IRQ\n",
irq, duration_us);
3.2.6 pr_notice — 正常但重要
用于记录系统正常运行时的重要事件,例如模块加载/卸载、设备热插拔、网络链路状态变化。这些消息对系统管理员有参考价值,但不会指示错误。
// 示例:USB 设备插入
pr_notice("usb %s: new high-speed USB device number %d using %s\n",
dev_name(dev), number, hcd->driver->description);
3.2.7 pr_info — 信息性消息
最通用的信息输出宏,用于打印驱动版本、硬件配置参数、初始化状态等。在调试阶段,pr_info 常用于确认代码执行路径。
// 示例:驱动初始化
pr_info("e1000e: %s NIC detected, MAC %pM\n", pci_name(pdev), mac_addr);
3.2.8 pr_debug — 调试消息
仅在调试配置(DEBUG 定义或 dynamic_debug 启用)下才编译或输出的消息。用于函数入口/出口、变量值跟踪、循环状态等细粒度调试。生产内核中默认不包含 pr_debug 输出。
// 示例:跟踪函数调用
pr_debug("%s: entering, arg=%d\n", __func__, arg);
// ... 业务逻辑 ...
pr_debug("%s: exiting, ret=%d\n", __func__, ret);
pr_debug 在未定义 DEBUG 且未启用动态调试时,会被预处理器完全移除,不会产生任何运行时开销。这是与 pr_info 的关键区别——pr_info 始终存在,而 pr_debug 可以做到零开销。
3.2.9 pr_cont — 续行消息
这是一个特殊宏,用于将日志消息续接到上一行末尾,而不添加换行符。常用于打印进度条、多行拼接或避免日志行被其他 CPU 的输出打断。
// 示例:打印内存检测进度
pr_info("Memory test: ");
for (i = 0; i < num_pages; i++) {
if (i % 100 == 0)
pr_cont(".");
}
pr_cont(" done (%d pages)\n", num_pages);
3.3 日志级别选择策略
在实际开发中,选择合适的日志级别直接影响调试效率和系统性能。下图展示了从开发到生产的不同阶段,各级别宏的推荐使用策略:
3.4 日志级别与动态调试的协同
在 Linux 内核中,pr_debug 与动态调试(dynamic_debug)机制深度集成。当内核配置了 CONFIG_DYNAMIC_DEBUG 时,pr_debug 不再是简单的条件编译,而是变为一个可以在运行时通过 debugfs 控制开关的调用点。这意味着:
- 零开销生产部署:默认所有
pr_debug调用点被禁用,不产生任何日志输出和性能开销。 - 运行时按需开启:通过
echo 'module mydriver +p' > /sys/kernel/debug/dynamic_debug/control即可开启指定模块的所有调试消息。 - 精细过滤:支持按文件名、函数名、行号、模块名进行精确控制,避免日志洪流。
// 动态调试控制示例
// 开启 drivers/net/ 目录下所有 pr_debug 输出
# echo 'file drivers/net/* +p' > /sys/kernel/debug/dynamic_debug/control
// 仅开启 e1000e 驱动中 e1000_open 函数的调试消息
# echo 'module e1000e function e1000_open +p' > /sys/kernel/debug/dynamic_debug/control
// 关闭所有调试输出
# echo 'module e1000e -p' > /sys/kernel/debug/dynamic_debug/control
pr_debug 输出,而非 pr_info。这样在开发阶段可以通过 dynamic_debug 灵活控制输出范围,进入生产阶段后无需修改代码即可完全关闭调试日志,实现“一次编写,两阶段使用”。
3.5 常见误区与避坑指南
| 误区 | 错误做法 | 正确做法 |
|---|---|---|
| 级别滥用 | 所有调试信息都用 pr_err 输出 |
根据严重程度选择:错误用 pr_err,跟踪用 pr_debug |
忽略 pr_cont 竞争 |
多 CPU 同时使用 pr_cont 拼接日志 |
使用 pr_cont 前加锁,或使用 seq_file 替代 |
生产环境保留 pr_debug |
生产内核中 pr_debug 默认开启 |
确保 CONFIG_DYNAMIC_DEBUG 启用,默认关闭所有调试点 |
| 日志行不换行 | 连续使用 pr_info 打印多行,每行末尾不加 \n |
每条完整日志末尾加 \n,续行使用 pr_cont |
| 忽略日志缓冲区溢出 | 高频循环中大量使用 pr_info |
使用 pr_debug 或 rate_limit 变体(printk_ratelimited) |
3.6 小结
本章详细解析了 Linux 内核中 9 个日志级别宏的定义、数值、使用场景和选择策略。核心要点包括:
- 日志级别从 0(
pr_emerg)到 7(pr_debug),数值越小优先级越高。 pr_debug与动态调试机制结合,可实现运行时零开销的调试日志控制。pr_cont用于续行拼接,但需注意多核竞争问题。- 开发阶段全量输出,测试阶段按需过滤,生产阶段最小化输出——这是日志级别选择的核心策略。