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 索引進行匹配。然後,可以使用原始檔名、行號和函式名將字串與原始碼匹配。