Printk 索引¶
有很多方法可以监控系统状态。一个重要的信息来源是系统日志。它提供了大量信息,包括或多或少重要的警告和错误消息。
有一些监控工具可以根据记录的消息进行过滤并采取行动。
内核消息随着代码一起发展。因此,特定的内核消息不是 KABI,而且永远不会是!
维护系统日志监控工具是一项巨大的挑战。它需要知道在特定内核版本中更新了哪些消息以及原因。在源代码中找到这些更改需要非平凡的解析器。此外,它还需要将源代码与二进制内核进行匹配,这并不总是那么容易。各种更改可能会被反向移植。不同的监控系统可能会使用不同的内核版本。
这就是 printk 索引功能可能变得有用的地方。它提供了在运行系统上的内核和模块使用的所有源代码中使用的 printk 格式的转储。它在运行时通过 debugfs 访问。
printk 索引有助于查找消息格式的更改。它还有助于将字符串追溯到内核源代码和相关的提交。
用户界面¶
printk 格式的索引被拆分为单独的文件。这些文件根据构建 printk 格式的二进制文件命名。始终存在 “vmlinux”,并且可以选择存在模块,例如
/sys/kernel/debug/printk/index/vmlinux
/sys/kernel/debug/printk/index/ext4
/sys/kernel/debug/printk/index/scsi_mod
请注意,仅显示已加载的模块。此外,当模块是内置的时,模块中的 printk 格式可能会出现在 “vmlinux” 中。
该内容受动态调试接口的启发,如下所示
$> head -1 /sys/kernel/debug/printk/index/vmlinux; shuf -n 5 vmlinux
# <level[,flags]> filename:line function "format"
<5> block/blk-settings.c:661 disk_stack_limits "%s: Warning: Device %s is misaligned\n"
<4> kernel/trace/trace.c:8296 trace_create_file "Could not create tracefs '%s' entry\n"
<6> arch/x86/kernel/hpet.c:144 _hpet_print_config "hpet: %s(%d):\n"
<6> init/do_mounts.c:605 prepare_namespace "Waiting for root device %s...\n"
<6> drivers/acpi/osl.c:1410 acpi_no_auto_serialize_setup "ACPI: auto-serialization disabled\n"
, 其中含义是
额外的信息使得查找各种内核之间的差异变得更加困难。特别是行号可能会经常更改。另一方面,它有助于确认它是否是相同的字符串或找到导致最终更改的提交。
printk() 不是稳定的 KABI¶
一些开发人员担心将所有这些实现细节导出到用户空间会将特定的 printk()
调用转换为 KABI。
但事实恰恰相反。 printk()
调用_不应该_是 KABI。并且 printk 索引帮助用户空间工具处理这个问题。
子系统特定的 printk 包装器¶
printk 索引是使用存储在专用 .elf 节 “.printk_index” 中的额外元数据生成的。这是通过使用宏包装器执行 __printk_index_emit() 以及真正的 printk()
调用来实现的。动态调试功能使用的元数据也使用相同的技术。
仅当使用这些特殊包装器打印特定消息时,才会存储该消息的元数据。它已为常用的 printk()
调用实现,包括例如 pr_warn()
或 pr_once()。
对于通过通用辅助函数调用原始 printk()
的各种子系统特定的包装器,需要进行额外的更改。这些需要它们自己的包装器来添加 __printk_index_emit()。
到目前为止,只有少数子系统特定的包装器已更新,例如 dev_printk()。因此,某些子系统的 printk 格式可能在 printk 索引中丢失。
子系统特定的前缀¶
宏 pr_fmt()
允许定义一个前缀,该前缀在相关的 printk()
调用生成的字符串之前打印。
子系统特定的包装器通常会添加更复杂的前缀。
这些前缀可以通过 __printk_index_emit() 的可选参数存储到 printk 索引元数据中。然后,debugfs 接口可能会显示包含这些前缀的 printk 格式。例如,drivers/acpi/osl.c 包含
#define pr_fmt(fmt) "ACPI: OSL: " fmt
static int __init acpi_no_auto_serialize_setup(char *str)
{
acpi_gbl_auto_serialize_methods = FALSE;
pr_info("Auto-serialization disabled\n");
return 1;
}
这会产生以下 printk 索引条目
<6> drivers/acpi/osl.c:1410 acpi_no_auto_serialize_setup "ACPI: auto-serialization disabled\n"
它有助于将实际日志中的消息与 printk 索引进行匹配。然后,可以使用源文件名、行号和函数名来将字符串与源代码进行匹配。