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"

,其中含义是

  • 级别:

    日志级别值:0-7 表示特定严重性,-1 为默认值,‘c’ 表示没有明确日志级别的连续行

  • 标志:

    可选标志:目前只有‘c’表示 KERN_CONT

  • 文件名:行号:

    相关源代码文件和行号,以及 printk() 调用。请注意,有许多封装函数,例如 pr_warn()、pr_warn_once()、dev_warn()。

  • 函数:

    使用 printk() 调用的函数名。

  • 格式:

    格式字符串

这些额外信息使得查找不同内核之间的差异变得有些困难。特别是行号可能会经常变化。另一方面,它极大地有助于确认字符串是否相同,或者找到负责最终更改的提交。

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 索引进行匹配。然后,可以使用源文件名、行号和函数名将字符串与源代码匹配。