printk()-2-官方文档翻译

官网:
Message logging with printk: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html
How to get printk format specifiers right:https://www.kernel.org/doc/html/latest/core-api/printk-formats.html
Printk Index: https://www.kernel.org/doc/html/latest/core-api/printk-index.html


一、Message logging with printk

printk() 是 Linux 内核中最广为人知的函数之一。它是用于打印消息的标准工具,通常也是最基本的跟踪和调试方法。如果您熟悉 printf(3),就会发现 printk() 是基于它编写的,尽管两者在功能上存在一些差异:

(1) printk() 消息可以指定日志级别。

(2) 格式字符串虽然与 C99 基本兼容,但并不完全遵循 C99 规范。它有一些扩展和一些限制(例如,不支持 %n 或浮点转换说明符)。请参阅“如何正确设置 printk 格式说明符”。

所有 printk() 消息都会打印到内核日志缓冲区,这是一个通过 /dev/kmsg 导出到用户空间的环形缓冲区。通常使用 dmesg 来读取它。

printk() 的典型用法如下:

printk(KERN_INFO "Message: %s\n", arg);

其中 KERN_INFO 为日志级别(请注意,它与格式字符串相连,日志级别不是单独的参数)。可用的日志级别有:

---------------------------------------------------------------------
Name            String Alias function
---------------------------------------------------------------------
KERN_EMERG      “0”    pr_emerg()
KERN_ALERT      “1”    pr_alert()
KERN_CRIT       “2”    pr_crit()
KERN_ERR        “3”    pr_err()
KERN_WARNING    “4”    pr_warn()
KERN_NOTICE     “5”    pr_notice()
KERN_INFO       “6”    pr_info()
KERN_DEBUG      “7”    pr_debug() and pr_devel() if DEBUG is defined
KERN_DEFAULT    “”
KERN_CONT       “c”    pr_cont()
---------------------------------------------------------------------

日志级别指定消息的重要性。内核会根据其日志级别和当前的 `console_loglevel`(一个内核变量)来决定是否立即显示消息(将其打印到当前控制台)。如果消息优先级高于(日志级别值低于)`console_loglevel`,则消息将被打印到控制台。

如果省略日志级别,则消息将以 `KERN_DEFAULT` 级别打印。

您可以使用以下命令检查当前的 `console_loglevel`:

$ cat /proc/sys/kernel/printk
4        4        1        7

结果显示了当前、默认、最低和启动时默认的日志级别。

要更改当前的 console_loglevel,只需将所需的级别写入 /proc/sys/kernel/printk 文件即可。例如,要将所有消息打印到控制台:

# echo 8 > /proc/sys/kernel/printk

//另一种方法是使用 dmesg:

# dmesg -n 5

设置 console_loglevel 为在控制台打印 KERN_WARNING (4) 或更严重的警告信息。有关详细信息,请参阅 dmesg(1)。

除了 printk() 之外,您还可以使用 pr_*() 别名进行日志记录。此类宏会将日志级别嵌入到宏名称中。例如:

pr_info("Info message no. %d\n", msg_num);

打印一条 KERN_INFO 消息。

除了比等效的 printk() 调用更简洁之外,它们还可以通过 pr_fmt() 宏使用统一的格式字符串定义。例如,在源文件顶部(任何 #include 指令之前)定义此宏:

#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__

该文件中所有 pr_*() 消息都会加上生成该消息的模块和函数名称作为前缀。

为了便于调试,还有两个条件编译的宏:pr_debug() 和 pr_devel()。除非定义了 DEBUG(或者对于 pr_debug(),还定义了 CONFIG_DYNAMIC_DEBUG),否则这两个宏会被编译掉。


1. Function reference

(1) pr_fmt

pr_fmt (fmt)

由 pr_*() 宏用于生成 printk 格式字符串

参数:

fmt

从 pr_*() 宏传递的格式字符串

描述:

此宏可用于为 pr_*() 宏生成统一的格式字符串。一个常见的用法是为文件中所有 pr_*() 消息添加一个通用字符串作为前缀。例如,在源文件顶部定义以下宏:

#define pr_fmt(fmt) KBUILD_MODNAME “: “ fmt 

会将文件中所有 pr_info、pr_emerg 等消息加上模块名称作为前缀。


(2) printk

printk(fmt, ...)

打印内核消息

参数:

fmt

格式字符串

...

可变参数

描述:

这是 printk() 函数。它可以从任何上下文中调用。我们希望它能够正常工作。

如果启用了 printk 索引,则会从 printk_index_wrap 调用 _printk()。否则,printk 会被简单地 #defined 给 _printk。

我们尝试获取 console_lock。如果成功,操作很简单——我们记录输出并调用控制台驱动程序。如果获取信号量失败,我们将输出放入日志缓冲区并返回。console_sem 的当前持有者会在 console_unlock() 中注意到新的输出,并在释放锁之前将其发送到控制台。

这种延迟打印的一个影响是,调用 printk() 然后更改 console_loglevel 的代码可能会出错。这是因为 console_loglevel 是在实际打印时检查的。

另请参阅:printf(3)

有关 C99 格式字符串扩展,请参阅 vsnprintf() 文档。


(3) pr_emerg

pr_emerg (fmt, ...)

打印紧急级别消息

参数:

fmt

格式字符串

...

格式字符串的参数

描述:

此宏展开为使用 KERN_EMERG 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(4) pr_alert

pr_alert(fmt, ...)

打印警报级别消息

参数:

fmt

格式字符串:

...

格式字符串的参数

描述:

此宏展开为使用 KERN_ALERT 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(5) pr_crit

pr_crit (fmt, ...)

打印严重级别消息

参数:

fmt

格式字符串

...

格式字符串的参数

描述:

此宏展开为使用 KERN_CRIT 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(6) pr_err

pr_err (fmt, ...)

打印错误级别的消息.

参数:

fmt

格式字符串

...

格式字符串的参数

描述:

此宏展开为一个使用 KERN_ERR 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(7) pr_warn

pr_warn(fmt, ...)

打印警告级别的消息

参数

fmt

格式字符串

...

格式字符串的参数

描述

此宏展开为一个日志级别为 KERN_WARNING 的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(8) pr_notice

pr_notice(fmt, ...)

打印通知级别消息

参数

fmt

格式字符串

...

格式字符串的参数

描述

此宏展开为使用 KERN_NOTICE 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(9) pr_info

pr_info(fmt, ...)

打印信息级别消息

参数

fmt

格式字符串

...

格式字符串的参数

描述

此宏展开为使用 KERN_INFO 日志级别的 printk 函数。它使用 pr_fmt() 函数生成格式字符串。


(10) pr_cont

pr_cont (fmt, ...)

在同一行中继续显示上一条日志消息。

参数

fmt

格式字符串

...

格式字符串的参数

说明

此宏会展开为使用 KERN_CONT 日志级别的 printk 函数。它仅应在继续显示不包含换行符 ('n') 的日志消息时使用。否则,它将默认使用 KERN_DEFAULT 日志级别。


(11) pr_devel

pr_devel (fmt, ...)

有条件地打印调试级别消息

参数

fmt

格式字符串

...

格式字符串的参数

描述

如果 #define 了 DEBUG,则此宏会展开为使用 KERN_DEBUG 日志级别的 printk 函数。否则,它不执行任何操作。

它使用 pr_fmt() 函数生成格式字符串。


(12) pr_debug

pr_debug (fmt, ...)

有条件地打印调试级别的消息。

参数

fmt

格式字符串

...

格式字符串的参数

描述:

如果设置了 CONFIG_DYNAMIC_DEBUG,则此宏展开为 dynamic_pr_debug()。否则,如果定义了 DEBUG,则等效于使用 KERN_DEBUG 日志级别的 printk 函数。如果未定义 DEBUG,则不执行任何操作。

它使用 pr_fmt() 生成格式字符串(dynamic_pr_debug() 内部也使用 pr_fmt())。

#if defined(CONFIG_DYNAMIC_DEBUG)
#include <linux/dynamic_debug.h>

/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
    dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
    no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif


二、How to get printk format specifiers right

Author: Randy Dunlap <rdunlap@infradead.org>
Author: Andrew Murray <amurray@mpc-data.co.uk>

1. Integer types

If variable is of Type,         use printk format specifier:
------------------------------------------------------------
        signed char             %d or %hhx
        unsigned char           %u or %x
        char                    %u or %x
        short int               %d or %hx
        unsigned short int      %u or %x
        int                     %d or %x
        unsigned int            %u or %x
        long                    %ld or %lx
        unsigned long           %lu or %lx
        long long               %lld or %llx
        unsigned long long      %llu or %llx
        size_t                  %zu or %zx
        ssize_t                 %zd or %zx
        s8                      %d or %hhx
        u8                      %u or %x
        s16                     %d or %hx
        u16                     %u or %x
        s32                     %d or %x
        u32                     %u or %x
        s64                     %lld or %llx
        u64                     %llu or %llx

如果 `<type>` 的大小取决于架构(例如 `cycles_t`、`tcflag_t`)或取决于配置选项(例如 `blk_status_t`),则使用其最大可能类型的格式说明符,并显式转换为该类型。

示例:

printk("test: latency: %llu cycles\n", (unsigned long long)time);

提醒:sizeof() 返回类型为 size_t。

内核的 printf 函数不支持 %n。出于显而易见的原因,浮点格式(%e、%f、%g、%a)也无法识别。使用任何不支持的格式说明符或长度限定符都会导致 vsnprintf() 发出警告并提前返回。


2. Pointer types

可以使用 `%p` 来打印原始指针值,它会在打印前对地址进行哈希处理。内核还支持用于打印不同类型指针的扩展说明符。

某些扩展说明符会打印给定地址上的数据,而不是地址本身。在这种情况下,可能会打印以下错误消息,而不是地址不可达的信息:

(null) data on plain NULL address
(efault) data on invalid address
(einval) invalid data on a valid address


3. Plain Pointers

%p abcdef12 or 00000000abcdef12

打印时未带扩展名的指针(即未加修饰的 %p)会被哈希处理,以防止泄露内核内存布局信息。这样做还有一个额外的好处,就是可以提供一个唯一的标识符。在 64 位机器上,指针的前 32 位会被置零。内核会持续打印 (ptrval),直到收集到足够的熵为止。

如果可能,请使用诸如 %pS 或 %pB(如下所述)之类的专用修饰符,以避免提供需要事后解释的未哈希地址。如果无法使用专用修饰符,并且打印地址的目的是为了提供更多调试信息,请使用 %p,并在调试期间使用 no_hash_pointers 参数启动内核,这样所有 %p 地址都会被原封不动地打印出来。如果您确实需要始终使用未修改的地址,请参阅下文的 %px

如果(且仅当)您将地址作为虚拟文件的内容打印出来,例如在……用户空间进程读取 procfs 或 sysfs(例如使用 seq_printf(),而不是 printk())时,请使用下面描述的 %pK 修饰符,而不是 %p 或 %px。


4. Error Pointers

%pe -ENOSPC

用于将错误指针(即 IS_ERR() 为真的指针)打印为符号错误名称。对于没有符号名称的错误值,将以十进制形式打印;而作为参数传递给 %pe 的非 ERR_PTR 将被视为普通的 %p。


5. Symbols/Function Pointers

%pS     versatile_init+0x0/0x110
%ps     versatile_init
%pSR    versatile_init+0x9/0x110
        (with __builtin_extract_return_addr() translation)
%pB     prev_fn_of_versatile_init+0x88/0x88

S 和 s 说明符用于以符号格式打印指针。它们会输出带偏移量 (S) 或不带偏移量 (s) 的符号名称。如果 KALLSYMS 被禁用,则会输出符号地址。

B 说明符会输出带偏移量的符号名称,应在打印堆栈回溯时使用。该说明符会考虑编译器优化带来的影响,例如尾调用(使用 noreturn GCC 属性标记)时可能出现的情况。

如果指针位于模块内,则会在符号名称后打印模块名称(可选)和构建 ID,并在说明符末尾附加一个额外的 b。

%pS     versatile_init+0x0/0x110 [module_name]
%pSb    versatile_init+0x0/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
%pSRb   versatile_init+0x9/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
        (with __builtin_extract_return_addr() translation)
%pBb    prev_fn_of_versatile_init+0x88/0x88 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]


6. Probed Pointers from BPF / tracing

%pks kernel string
%pus user string

k 和 u 说明符用于打印先前探测到的内存,分别来自内核内存 (k) 或用户内存 (u)。后续的 s 说明符用于打印字符串。在常规的 vsnprintf() 函数中直接使用时,(k) 和 (u) 注解会被忽略;但是,例如,当在 BPF 的 bpf_trace_printk() 函数之外使用时,它会读取其指向的内存而不会报错。


7. Kernel Pointers

%pK 01234567 or 0123456789abcdef

用于打印内核指针,这些指针应该对非特权用户隐藏。%pK 的行为取决于 kptr_restrict sysctl 参数——更多详情请参阅 /proc/sys/kernel/ 的文档。

此修饰符仅用于打印用户空间从 procfs 或 sysfs 等文件系统读取的文件内容,不适用于 dmesg。有关如何在 printk() 中管理哈希指针的讨论,请参阅上文关于 %p 的部分。


8. Unmodified Addresse

%px 01234567 or 0123456789abcdef

当您确实需要打印指针地址时,可以使用 `%px`。在使用 `%px` 打印指针之前,请考虑是否会泄露内核内存布局的敏感信息。`%px` 的功能与 `%lx`(或 `%lu`)等效。之所以推荐使用 `%px`,是因为它更容易被 grep 找到。如果将来需要修改内核处理指针打印的方式,我们将能够更轻松地找到调用点。

在使用 `%px` 之前,请考虑在调试期间启用内核参数 `no_hash_pointers` 并配合使用 `%p` 是否足够(参见上文关于 `%px` 的描述)。`%px` 的一个合理应用场景是在内核崩溃之前立即打印信息,这样可以防止任何敏感信息被利用,并且使用 `%px` 也无需通过启用 `no_hash_pointers` 来重现内核崩溃。


9. Pointer Differences

%td 2560
%tx a00

要打印指针差值,请使用 ptrdiff_t 函数的 %t 修饰符。

示例:

printk("test: difference between pointers: %td\n", ptr2 - ptr1);


10. Struct Resources

%pr     [mem 0x60000000-0x6fffffff flags 0x2200] or
        [mem 0x60000000 flags 0x2200] or
        [mem 0x0000000060000000-0x000000006fffffff flags 0x2200]
        [mem 0x0000000060000000 flags 0x2200]
%pR     [mem 0x60000000-0x6fffffff pref] or
        [mem 0x60000000 pref] or
        [mem 0x0000000060000000-0x000000006fffffff pref]
        [mem 0x0000000060000000 pref]

用于打印结构体资源。R 和 r 说明符分别表示打印的资源是否包含已解码的标志成员(R 表示包含,r 表示不包含)。如果 start 等于 end,则仅打印 start 值。

按引用传递。


11. Physical address types phys_addr_t

%pa[p] 0x01234567 or 0x0123456789abcdef

用于打印 phys_addr_t 类型(及其衍生类型,例如 resource_size_t),该类型会根据构建选项而变化,与 CPU 数据路径的宽度无关。

按引用传递。


12. Struct Range

%pra [range 0x0000000060000000-0x000000006fffffff] or
[range 0x0000000060000000]

用于打印结构体 range。结构体 range 存储任意范围的 u64 值。如果 start 等于 end,则只打印 start 值。

按引用传递。


13. DMA address types dma_addr_t

%pad 0x01234567 or 0x0123456789abcdef

用于打印 dma_addr_t 类型的值,该值会根据构建选项而变化,与 CPU 数据路径的宽度无关。

按引用传递。


14. Raw buffer as an escaped string

%*pE[achnops]

用于将原始缓冲区内容打印为转义字符串。对于以下缓冲区:

1b 62 20 5c 43 07 22 90 0d 5d

以下示例展示了如何进行转换(不包括周围的引号):

%*pE            "\eb \C\a"\220\r]"
%*pEhp          "\x1bb \C\x07"\x90\x0d]"
%*pEa           "\e\142\040\\\103\a\042\220\r\135"

转换规则根据可选的标志组合应用(详情请参阅 string_escape_mem() 内核文档)

a - ESCAPE_ANY
c - ESCAPE_SPECIAL
h - ESCAPE_HEX
n - ESCAPE_NULL
o - ESCAPE_OCTAL
p - ESCAPE_NP
s - ESCAPE_SPACE

默认情况下使用 ESCAPE_ANY_NP。

在许多情况下,ESCAPE_ANY_NP 都是合理的选择,尤其是在打印 SSID 时。

如果省略字段宽度,则只会转义 1 个字节。


15. Raw buffer as a hex string

%*ph    00 01 02  ...  3f
%*phC   00:01:02: ... :3f
%*phD   00-01-02- ... -3f
%*phN   000102 ... 3f

用于将小型缓冲区(长度不超过 64 字节)以特定分隔符分隔的十六进制字符串形式打印出来。对于更大的缓冲区,请考虑使用 print_hex_dump() 函数。


16. MAC/FDDI addresses

%pM     00:01:02:03:04:05
%pMR    05:04:03:02:01:00
%pMF    00-01-02-03-04-05
%pm     000102030405
%pmR    050403020100

用于以十六进制表示法打印 6 字节 MAC/FDDI 地址。M 和 m 指示符分别用于打印带 (M) 或不带 (m) 字节分隔符的地址。默认字节分隔符为冒号 (:)。

对于 FDDI 地址,可在 M 指示符后使用 F 指示符,以使用短横线 (-) 分隔符代替默认分隔符。

对于蓝牙地址,应在 M 指示符后使用 R 指示符,以使用反向字节序,便于对小端序的蓝牙地址进行视觉解读。

通过引用传递。


17. IPv4 addresses

%pI4    1.2.3.4
%pi4    001.002.003.004
%p[Ii]4[hnbl]

用于打印 IPv4 点分隔的十进制地址。`I4` 和 `i4` 指示符分别用于打印带前导零 (i4) 或不带前导零 (I4) 的地址。

`h`、`n`、`b` 和 `l` 指示符分别用于指定主机、网络、大端或小端字节序的地址。如果未提供指示符,则使用默认的网络/大端字节序。

通过引用传递。


18. IPv6 addresses

%pI6    0001:0002:0003:0004:0005:0006:0007:0008
%pi6    00010002000300040005000600070008
%pI6c   1:2:3:4:5:6:7:8

用于打印 IPv6 网络级 16 位十六进制地址。I6 和 i6 说明符分别用于打印带冒号 (I6) 或不带冒号 (i6) 分隔符的地址。地址始终使用前导零。

如 https://tools.ietf.org/html/rfc5952 所述,可以将附加的 c 说明符与 I 说明符一起使用,以打印压缩的 IPv6 地址。

通过引用传递。


19. IPv4/IPv6 addresses (generic, with port, flowinfo, scope)

%pIS    1.2.3.4         or 0001:0002:0003:0004:0005:0006:0007:0008
%piS    001.002.003.004 or 00010002000300040005000600070008
%pISc   1.2.3.4         or 1:2:3:4:5:6:7:8
%pISpc  1.2.3.4:12345   or [1:2:3:4:5:6:7:8]:12345
%p[Ii]S[pfschnbl]

用于打印 IP 地址,无需区分其类型是 AF_INET 还是 AF_INET6。可以通过 IS 或 iS 指定指向有效 struct sockaddr 的指针,并将其传递给此格式说明符。

附加的 p、f 和 s 说明符用于指定端口(IPv4、IPv6)、流信息(IPv6)和作用域(IPv6)。端口以冒号 (:) 为前缀,流信息以斜杠 (/) 为前缀,作用域以百分比 (%) 为前缀,后跟实际值。

如果指定了附加说明符 c,则对于 IPv6 地址,将使用 https://tools.ietf.org/html/rfc5952 中描述的压缩 IPv6 地址。 IPv6 地址在附加说明符 p、f 或 s 的情况下,用方括号 [, ] 括起来,如 https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 所述。

对于 IPv4 地址,也可以使用附加说明符 h、n、b 和 l,但这些说明符在 IPv6 地址中会被忽略。

按引用传递。

更多示例:

%pISfc          1.2.3.4         or [1:2:3:4:5:6:7:8]/123456789
%pISsc          1.2.3.4         or [1:2:3:4:5:6:7:8]%1234567890
%pISpfc         1.2.3.4:12345   or [1:2:3:4:5:6:7:8]:12345/123456789

//UUID/GUID 地址

%pUb    00010203-0405-0607-0809-0a0b0c0d0e0f
%pUB    00010203-0405-0607-0809-0A0B0C0D0E0F
%pUl    03020100-0504-0706-0809-0a0b0c0e0e0f
%pUL    03020100-0504-0706-0809-0A0B0C0E0E0F

用于打印 16 字节 UUID/GUID 地址。附加的 l、L、b 和 B 说明符用于指定小端序(小写 (l) 或大写 (L) 十六进制表示)和大端序(小写 (b) 或大写 (B) 十六进制表示)。

如果未使用任何附加说明符,则默认以大端序和小写十六进制表示打印。

按引用传递。


20. dentry names

%pd{,2,3,4}
%pD{,2,3,4}

打印目录项名称时,如果与 `d_move()` 发生竞争,名称可能会混合新旧名称,但这不会出错。`%pd dentry` 是之前使用的 `%s dentry->d_name.name` 的更安全等效方案,`%pd<n>` 会打印最后 n 个组件。`%pD` 对结构体文件执行相同的操作。

按引用传递。


21. block_device names

%pg sda, sda1 or loop0p1

用于打印块设备指针的名称。


22. struct va_format

%pV

用于打印 struct va_format 结构体。这些结构体包含格式字符串和 va_list,如下所示:

struct va_format {
    const char *fmt;
    va_list *va;
};

实现了“recursive vsnprintf”函数。

请勿在未采取任何机制验证格式字符串和 va_list 参数正确性的情况下使用此功能。

按引用传递。


23. Device tree nodes

%pOF[fnpPcCF]

用于打印设备树节点结构。默认行为等同于 %pOFF。

f - device node full_name
n - device node name
p - device node phandle
P - device node path spec (name + @unit)
F - device node flags
c - major compatible string
C - full compatible string

使用多个参数时,分隔符为“:”。

示例:

%pOF    /foo/bar@0                      - Node full name
%pOFf   /foo/bar@0                      - Same as above
%pOFfp  /foo/bar@0:10                   - Node full name + phandle
%pOFfcF /foo/bar@0:foo,device:--P-      - Node full name +
                                          major compatible string +
                                          node flags
                                                D - dynamic
                                                d - detached
                                                P - Populated
                                                B - Populated bus

按引用传递。


24. Fwnode handles

%pfw[fP]

用于打印 fwnode_handle 的信息。默认情况下,会打印完整的节点名称,包括路径。修饰符的功能与上面的 %pOF 等效。

f - 节点的完整名称,包括路径
P - 节点名称,包括地址(如果有)

示例(ACPI):

%pfwf   \_SB.PCI0.CIO2.port@1.endpoint@0        - Full node name
%pfwP   endpoint@0                              - Node name

//示例(OF):

%pfwf   /ocp@68000000/i2c@48072000/camera@10/port/endpoint - Full name
%pfwP   endpoint                                - Node name


25. Time and date

%pt[RT]                 YYYY-mm-ddTHH:MM:SS
%pt[RT]s                YYYY-mm-dd HH:MM:SS
%pt[RT]d                YYYY-mm-dd
%pt[RT]t                HH:MM:SS
%ptSp                   <seconds>.<nanoseconds>
%pt[RST][dt][r][s]

打印日期和时间,格式如下:

R  content of struct rtc_time
S  content of struct timespec64
T  time64_t type

以人类可读格式显示。

默认情况下,年份将递增 1900,月份将递增 1。使用 %pt[RT]r(原始格式)可禁用此行为。

%pt[RT]s (space)将覆盖 ISO 8601 分隔符,在日期和时间之间使用空格代替大写字母 T。如果省略日期或时间,则此设置无效。

对于 struct timespec64 的内容,%ptSp 等效于 %lld.%09ld。当提供其他说明符时,它将变为相应的 %ptT[dt][r][s].%09ld。换句话说,秒数将以人类可读格式打印,后跟一个点和纳秒。

按引用传递。


26. struct clk

%pC pll1

用于打印 struct clk 结构体。%pC 会打印时钟名称(通用时钟框架)或唯一的 32 位 ID(传统时钟框架)。

按引用传递。

位图及其衍生函数,例如 cpumasknodemask

%*pb 0779
%*pbl 0,3-6,8-10

对于打印位图及其衍生数据(例如 cpumask 和 nodemask),`%*pb` 输出位图,其字段宽度为位数;`%*pbl` 输出位图,其字段宽度为位数。

字段宽度按值传递,位图按引用传递。可以使用辅助宏 `cpumask_pr_args()` 和 `nodemask_pr_args()` 来简化 cpumask 和 nodemask 的打印。


27. Flags bitfields such as page flags and gfp_flags

%pGp    0x17ffffc0002036(referenced|uptodate|lru|active|private|node=0|zone=2|lastcpupid=0x1fffff)
%pGg    GFP_USER|GFP_DMA32|GFP_NOWARN
%pGv    read|exec|mayread|maywrite|mayexec|denywrite

用于打印标志位域,该位域由一组符号常量构成,这些常量用于构造标志值。标志的类型由第三个字符指定。目前支持的标志类型有:

p - [p]age 标志,期望值为 (unsigned long *) 类型
v - [v]ma_flags,期望值为 (unsigned long *) 类型
g - [g]fp_flags,期望值为 (gfp_t *) 类型

标志名称和打印顺序取决于标志类型。

请注意,此格式不应直接用于跟踪点的 TP_printk() 函数中。而应使用 <trace/events/mmflags.h> 中的 show_*_flags() 函数。

按引用传递。


28. Network device features

%pNF 0x000000000000c000

用于打印 netdev_features_t 类型的数据。

通过引用传递。


29. V4L2 and DRM FourCC code (pixel format)

%p4cc

打印 V4L2 或 DRM 使用的 FourCC 代码,包括格式字节序及其十六进制数值。

通过引用传递。

示例:

%p4cc   BG12 little-endian (0x32314742)
%p4cc   Y10  little-endian (0x20303159)
%p4cc   NV12 big-endian (0xb231564e)


30. Generic FourCC code

%p4c[h[R]lb] gP00 (0x67503030)

打印一个通用的 FourCC 代码,以 ASCII 字符和十六进制数值两种形式显示。

通用的 FourCC 代码始终以大端字节序打印,即最高有效字节在前。这与 V4L/DRM FourCC 代码相反。

附加的 h、hR、l 和 b 说明符定义了加载存储字节时使用的字节序。数据可以按主机字节序、反向主机字节序、小端字节序或大端字节序进行解释。

按引用传递。

例如,对于小端字节序机器,给定 &(u32)0x67503030:

%p4ch   gP00 (0x67503030)
%p4chR  00Pg (0x30305067)
%p4cl   gP00 (0x67503030)
%p4cb   00Pg (0x30305067)

对于大端字节序机器,给定 &(u32)0x67503030,示例如下:

%p4ch   gP00 (0x67503030)
%p4chR  00Pg (0x30305067)
%p4cl   00Pg (0x30305067)
%p4cb   gP00 (0x67503030)


31. Rust

%pA

仅供 Rust 代码中用于格式化 core::fmt::Arguments。请勿在 C 代码中使用。


32. Thanks

如果您添加了其他 %p 扩展,请尽可能在 <lib/tests/printf_kunit.c> 文件中添加一个或多个测试用例。

感谢您的合作与关注。


三、Printk Index

监控系统状态的方法有很多。系统日志是重要的信息来源之一。它提供了大量信息,包括各种重要或不太重要的警告和错误消息。

有一些监控工具可以过滤日志消息并根据这些消息采取相应的操作。

内核消息会随着代码的演进而不断变化。因此,某些特定的内核消息并非绝对可靠,而且永远也不会是!

维护系统日志监控器是一项巨大的挑战。它需要了解特定内核版本中更新了哪些消息以及更新的原因。在源代码中查找这些更改需要复杂的解析器。此外,还需要将源代码与二进制内核进行匹配,而这并非总是易事。各种更改可能会被反向移植。不同的受监控系统可能会使用不同的内核版本。

这时,printk 索引功能就派上用场了。它提供了运行系统中内核和模块所使用的所有源代码中 printk 格式的转储。运行时可以通过 debugfs 访问它。

printk 索引有助于查找消息格式的更改。此外,追踪字符串到内核源代码和相关提交也很有帮助。


1. User Interface

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"

其中含义如下:

level:日志级别值:0-7 表示特定严重程度,-1 为默认值,'c' 表示未显式指定日志级别的连续行

flags:可选标志:目前仅 'c' 表示 KERN_CONT

filename:line:printk() 调用时的源文件名和行号。请注意,存在许多包装函数,例如 pr_warn()、pr_warn_once() 和 dev_warn()。

function:使用 printk() 调用的函数名称。

format:格式字符串

这些额外信息使得查找不同内核之间的差异变得更加困难。尤其是行号可能会频繁变化。另一方面,它对于确认是否是同一字符串或查找导致更改的提交记录非常有帮助。


2. printk() Is Not a Stable KABI

一些开发者担心,将所有这些实现细节导出到用户空间会导致某些 printk() 调用变成 KABI 调用。

但事实恰恰相反。printk() 调用绝不能是 KABI 调用。而 printk 索引正是帮助用户空间工具处理这个问题的。


3. Subsystem specific printk wrappers

printk 索引是使用存储在专用 .elf 段“.printk_index”中的额外元数据生成的。它是通过宏包装器实现的,这些包装器会调用 __printk_index_emit() 函数,并同时调用实际的 printk() 函数。动态调试功能使用的元数据也采用了相同的技术。

只有当使用这些特殊包装器打印特定消息时,才会存储该消息的元数据。它已针对常用的 printk() 调用实现,例如 pr_warn() 或 pr_once()。

对于通过公共辅助函数调用原始 printk() 的各种子系统特定包装器,还需要进行额外的更改。这些包装器需要添加 __printk_index_emit() 函数。

目前只有少数子系统特定包装器进行了更新,例如 dev_printk()。因此,某些子系统的 printk 格式可能在 printk 索引中缺失。


4. Subsystem specific prefix

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

 

posted on 2026-03-13 16:46  Hello-World3  阅读(1)  评论(0)    收藏  举报

导航