5. printk的格式字符
常见的数据类型对应的printk的格式字符如下:
int %d or %x unsigned int %u, %x long %ld, %lx unsigned long %lu, %lx long long %lld, %llx unsigned long long %llu, %llx size_t %zu, %zx ssize_t %zd, %zx
5.1 打印指针
使用 %p 来打印指针, 但是在printk里面对此进行了扩展。
- %pS, %pF 打印对应的符号名/函数名和偏移量
- %ps, %pf 打印对应的符号名/函数名
5.2 打印受限的内核地址
使用 %pK来打印受限的内核地址
- %pK 且 /proc/sys/kernel/kptr_restrict = 0 时可直接打印
- %pK 且 /proc/sys/kernel/kptr_restrict = 1 时所有的地址打印为0(除非配置了CAP_SYSLOG)。
- %pK 且 /proc/sys/kernel/kptr_restrict = 2 时所有的地址打印为0。
kptr_restrict = 0,所有用户都可以读取内核符号地址。
kptr_restrict = 1,普通用户无法读取内核符号地址, root用户可以查看。
kptr_restrict = 2,所有用户都无法读取内核符号地址。
5.3 打印结构资源
- %pr 打印结构体资源
- %pR 打印结构体资源,包含一个解码标记
打印物理地址
- %pa
5.4 打印raw buffer(64字节以下,较大的buffer应使用print_hex_dump)
- %*ph ,空格分割,如 00 01 02
- %*phC, 冒号分割,如 00:01:02
- %*phD ,横杠分割,如 00-01-02
- %*phN ,无分割符,如 000102
5.5 打印MAC/FDDI
- %pM,冒号分割, 如 00:01:02:03:04:05
- %pMR ,冒号分割,反序, 如 05:04;03;02:01:00
- %pMF, 横杠分割, 如 00-01-02-03-04-05
- %pm ,无分割, 如 000102030405
- %pmR ,无分割, 反序,如 0504030201
5.6 打印ipv4地址
- %pI4, 1.2.3.4的形式
- %pi4, 001.002.003.004的形式
- [hnbl] 附加的’h’ ‘n’ ‘b’ ‘l’ 用于指定参数的字节序是主机字节序还是网络字节序, 或者是大端对齐还是小端对齐。 printk 默认地址是网络字节序的, 并且自动转换为主机字节序再打印, 不需要做额外的字节序转换
5.7 打印ipv6地址
- %pI6 ,00010003;0004;0005;0006;0007:0008 的形式
- %pi6, 001002003004005006007008 的形式
- %pI6C, 1;2;3:4;5;6:7:8 的形式
5.8 打印 UUID/GUID
- %pUb 小写字母大端序
- %pUB 大写字母大端序
- %pUl 小写字母小端序
- %pUL 大写字母小端序
5.9 打印结构体
- %pV 打印结构的各个成员的名称和值
6. 基于 printk 的宏
每次使用printk都要指定log level太过于麻烦, 内核定义了一组宏。
#ifndef pr_fmt #define pr_fmt(fmt) fmt #endif #define pr_emerg(fmt, ...) \ printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__) #define pr_alert(fmt, ...) \ printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__) #define pr_crit(fmt, ...) \ printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__) #define pr_err(fmt, ...) \ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) #define pr_warning(fmt, ...) \ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) #define pr_warn pr_warning #define pr_notice(fmt, ...) \ printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__) #define pr_info(fmt, ...) \ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) #define pr_cont(fmt, ...) \ printk(KERN_CONT fmt, ##__VA_ARGS__)
7. pr_debug, pr_devel
内核还定义了一组打印宏pr_devel在build debug版kernel时才会有效, 但是也可用DEBUG宏来开启。
#ifdef DEBUG #define pr_devel(fmt, ...) \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #else #define pr_devel(fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif
还有一组宏pr_debug, 与pr_devel相比,它还能用于dynamic debug。
#if defined(DEBUG) #define pr_debug(fmt, ...) \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #elif defined(CONFIG_DYNAMIC_DEBUG) /@@* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */ #define pr_debug(fmt, ...) \ dynamic_pr_debug(fmt, ##__VA_ARGS__) #else #define pr_debug(fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif
8. 防止printk刷屏
在频繁被执行的地方, 插入 printk 会导致输出大量消息, 刷掉其它的消息, 为了控制printk输出的次数,可以使用printk_ratelimited(…)。
需要 “#include ”
printk_ratelimited(…)会保证每一条在5秒钟内输出次数不大于10次, 如果需要更精细的频率控制, 可以设置 DEFINE_RATELIMIT_STATE 宏并使用__ratelimit函数。
“/proc/sys/kernel/printk_ratelimit” 定义了消息之间的最小时间间隔, “/proc/sys/kernel/printk_ratelimit_burst”定义了消息的数量,即在 printk_ratelimit 秒内最多打印 printk_ratelimit_burst 条消息。
每一个level的printk还有 “pr_err_ratelimited”, “pr_debug_ratelimited”, “pr_info_ratelimited”这些对应的宏。
如果只需要打印一次的话, 可以使用printk_once(…) 。
9. dev_xxx()
在开发设备驱动的过程中, 在输出消息时, 往往希望能够附带device相关的信息, 例如 device name等,可以使用 dev_printk()。
int dev_printk(const char *level, const struct device *dev, const char *fmt, ...);
dev_printk() 能够额外输出设备的 driver name 或者 bus name, device name。如果对这些输出内容不满意的话, 可以基于__dev_printk()自己封装一个消息输出函数, dev_printk()正是基于该函数的封装。
如果希望在 DEBUG 版中才输出消息, 或者是dynamic debug, 则可以使用 dev_dbg()。
#if defined(CONFIG_DYNAMIC_DEBUG) #define dev_dbg(dev, format, ...) \ do { \ dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \ } while (0) #elif defined(DEBUG) #define dev_dbg(dev, format, arg...) \ dev_printk(KERN_DEBUG, dev, format, ##arg) #else #define dev_dbg(dev, format, arg...) \ ({ \ if (0) \ dev_printk(KERN_DEBUG, dev, format, ##arg); \ 0; \ }) #endif
同样, 有限制输出频率的不同消息等级的 dev_xxx() 宏。
#define dev_emerg_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_emerg, dev, fmt, ##__VA_ARGS__) #define dev_alert_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_alert, dev, fmt, ##__VA_ARGS__) #define dev_crit_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_crit, dev, fmt, ##__VA_ARGS__) #define dev_err_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_err, dev, fmt, ##__VA_ARGS__) #define dev_warn_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_warn, dev, fmt, ##__VA_ARGS__) #define dev_notice_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_notice, dev, fmt, ##__VA_ARGS__) #define dev_info_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_info, dev, fmt, ##__VA_ARGS__)
DEBUG版 和 dynamic debug 版本也可以限制输出频率, dev_dbg_ratelimited()
#if defined(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG) #define dev_dbg_ratelimited(dev, fmt, ...) \ do { \ static DEFINE_RATELIMIT_STATE(_rs, \ DEFAULT_RATELIMIT_INTERVAL, \ DEFAULT_RATELIMIT_BURST); \ DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt); \ if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT) && \ __ratelimit(&_rs)) \ __dynamic_pr_debug(&descriptor, pr_fmt(fmt), \ ##__VA_ARGS__); \ } while (0) #else #define dev_dbg_ratelimited(dev, fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif
10. 在用户空间打印内核消息
“/dev/kmsg“ 设备提供了在用户空间输出内核消息的途径,可以使用 mknod -m 600 /dev/kmsg c 1 11 命令来创建一个。
echo "Hello Kernel-World" > /dev/kmsg
或者还可以使用前置数字来指明消息等级。
# echo "2Writing critical printk messages from userspace" >/dev/kmsg
数字2等于 KERN_CRIT 的等级。
使用 dmesg -u 可以看到所有从用户空间打印的的内核消息。
11. 封装 printk 打造一个专属的打印函数
每次调试的时候都要加行号,加函数名调试,实在是太麻烦了,为什么不自己封装一个专属的 print log 的函数呢?
#define log_info(fmt, arg...) printk(KERN_INFO "[%s][%d] "fmt"", __func__, __LINE__, ##arg);
其中 _func_ _LINE_
分别表示当前函数名和行号。
12. 查看printk输出的消息
在用户空间, 可以使用如下几种方式查看 内核消息
- dmesg 命令
- cat /proc/kmsg (不会返回, 会一直等待并输出新的内核消息, 再次之前的消息不会输出)
- cat //var/log/syslog
dmesg是最常用的方式, 使用dmesg命令时可以加一些控制参数
- -C 清空存放内核消息的环形缓冲区
- -c 列出内核消息然后清空环形缓冲
- -k 仅仅打印从内核中输出的内核消息
- -u 仅仅打印从用户空间打印的内核消息
- -n 调整将被打印到控制台的消息的等级
- -s 设置环形缓冲区的大小