----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
一、printk介绍
我们在学习C语言的时候,经常使用printf函数将内容输出到控制台,printf 是格式化输出函数,主要功能是向标准输出设备按规定格式输出信息。printf是C语言标准库函数,定义于头文件 <stdio.h>。
而在linux内核中是无法使用printf 函数的,取而代之的是printk函数。printk在内核源码中用来记录日志信息的函数,只能在内核源码范围内使用,用法类似于printf函数。
一个较大的差别在于printk支持多种日志级别,从而允许printk根据消息的等级选择性进行打印。
printk 函数主要做两件事情:
- 将信息记录日志文件中(一般为/var/log/message);
- 调用控制台驱动来将信息输出到控制台;
1.1 日志缓冲区
printk将内核信息输出到内核信息缓冲区中,内核缓冲区在kernel/printk/printk.c中定义:
#define LOG_ALIGN __alignof__(struct printk_log) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) #define LOG_BUF_LEN_MAX (u32)(1 << 31) static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
内核日志缓冲区__log_buf的大小为$2^{CONFIG\_LOG\_BUF\_SHIFT}$ ,CONFIG_LOG_BUF_SHIFT默认为17,即128KB,可以通过make menuconfig配置,对应的配置项为LOG_BUF_SHIFT。
1.2 日志级别
日志级别用来控制printk打印的这条信息是否在控制台上显示,linux内核共提供了8种不同的日志级别,分为级别 0~7。数值越大,表示级别越低,对应的消息越不重要。
linux内核日志级别相应的宏定义在include/linux/kern_levels.h 文件中。
#define KERN_EMERG KERN_SOH "0" /* system is unusable */ #define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ #define KERN_CRIT KERN_SOH "2" /* critical conditions */ #define KERN_ERR KERN_SOH "3" /* error conditions */ #define KERN_WARNING KERN_SOH "4" /* warning conditions */ #define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ #define KERN_INFO KERN_SOH "6" /* informational */ #define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ #define KERN_DEFAULT "" /* the default kernel loglevel */
各个日志级别含义如下:
- KERN_EMERG 表示紧急事件,一般是系统崩溃之前提示的消息;
- KERN_ALERT 表示必须立即采取行动的消息;
- KERN_CRIT 表示临界状态,通常涉及严重的硬件或软件操作失败;
- KERN_ERR 用于报告错误状态,设备驱动程序会经常使用该级别来报告来自硬件的问题;
- KERN_WARNING 对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题;
- KERN_NOTICE 表示有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报;
- KERN_INFO 表示内核提示信息,很多驱动程序在启动的时候,用这个级别打印出它们找到的硬件信息;
- KERN_DEBUG 用于调试信息;
1.3 函数使用
在使用printk时,会将日志级别放到最开始的位置,使用方式如下:
printk(log_level "message...");
例如:
printk(KERN_EMERG "GetIot: KERN_EMERG\n"); printk(KERN_ALERT "GetIot: KERN_ALERT\n"); printk(KERN_CRIT "GetIot: KERN_CRIT\n"); printk(KERN_ERR "GetIot: KERN_ERR\n"); printk(KERN_WARNING "GetIot: KERN_WARNING\n"); printk(KERN_NOTICE "GetIot: KERN_NOTICE\n"); printk(KERN_INFO "GetIot: KERN_INFO\n"); printk(KERN_DEBUG "GetIot: KERN_DEBUG\n");
若未设置日志级别,printk默认使用内核定义的全局变量default_message_loglevel作为的默认打印的日志级别。
当printk中的消息日志级别小于当前控制台日志级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台。
但无论当前控制台日志级别是何值,即使没有在控制台打印出来,都可以通过下面两种方法查看日志:
- 第一种是使用dmesg命令查看日志;
- 第二种是通过cat /proc/kmsg来查看日志;
另外如果配置好并运行了syslogd 或klogd,没有在控制台上显示的printk的信息也会追加到/var/log/messages.log中。
1.3.1 dmesg
执行dmesg查看日志:
[root@zy:/]# dmesg Booting Linux on physical CPU 0x0 Linux version 5.2.8 (root@zhengyang) (gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29)) #23 Sun Mar 26 14:53:14 CST 2023 ...
1.3.2 /proc/kmsg
执行cat /proc/kmsg查看日志:
[root@zy:/]# cat /proc/kmsg <6>Booting Linux on physical CPU 0x0 <5>Linux version 5.2.8 (root@zhengyang) (gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29)) #23 Sun Mar 26 14:53:14 CST 2023 ...
1.4 调整日志级别
linux系统支持在运行时,通过proc文件系统查看和调整内核日志的输出等级。查看当前控制台的打印级别的命令如下:
[root@zy:/]# cat /proc/sys/kernel/printk 7 4 1 7
该文件有4个数字值,含义如下:
- 控制台日志级别:优先级高于该值的消息将被打印至控制台;
- 默认的消息日志级别:将用该优先级来打印没有优先级的消息(即 printk 没有指定消息级别);
- 最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);
- 默认的控制台日志级别:控制台日志级别的缺省值;
这四个值是在 kernel/printk/printk.c 中被定义的,如下:
int console_printk[4] = { CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */ MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */ CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */ CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */ }; EXPORT_SYMBOL_GPL(console_printk);
修改日志级别有两种方式,配置menuconfig和修改/proc/sys/kernel/printk文件。
1.4.1 配置menuconfig
修改CONFIG_MESSAGE_LOGLEVEL_DEFAULT的值,然后重新编译,更新内核。menuconfig配置路径如下:
Kernel hacking ---> printk and dmesg options ---> (4) Default message log level (1-7)
如下图所示:
1.4.2 在系统中修改
在系统运行期间,可以通过执行以下命令,修改当前控制态的日志级别:
echo "新的打印级别 4 1 7" > /proc/sys/kernel/printk
如屏蔽掉所有的内核printk打印,只需要把第一个数值调到最小值1或者0,此时可以敲如下命令:
echo "1 4 1 7" > /proc/sys/kernel/printk
二、其他常见用法
为了方便开发者使用,在linux内核中除了直接使用printk加消息级别的方式,内核还提供了pr_xx和dev_xx系列的打印接口,本质上,它们都是基于printk实现的。其中:
- pr_xx系列函数简化了日志级别的使用;
- dev_xx系列函数可以用于设备驱动程序中,便于打印设备相关的信息;
2.1 pr_xx系列函数
在 <linux/printk.h> 中定义了 pr_notice、pr_info、pr_warn、pr_err 等接口。使用这些 pr_xxx 接口,就可以省去指定消息级别的麻烦。
#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__) #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 #if defined(CONFIG_DYNAMIC_DEBUG) #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
如上各pr_xx定义,都是对printk+日志级别的封装,故都能和prink一样在linux内核中直接使用;
但是对于pr_devel需要在定义了DEBUG宏,pr_debug需要在定义了DEBUG或CONFIG_DYNAMIC_DEBUG宏才有实意。
2.2 dev_xx系列函数
在 <linux/dev_printk.h> 里也提供了一些驱动模型诊断宏,例如dev_err、dev_warn、dev_info等等。使用它们,不仅可以按标记的消息级别打印,还会打印对应的设备和驱动信息,这对于驱动调试来说相当重要。
#define dev_emerg(dev, fmt, ...) \ dev_printk_index_wrap(_dev_emerg, KERN_EMERG, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_crit(dev, fmt, ...) \ dev_printk_index_wrap(_dev_crit, KERN_CRIT, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_alert(dev, fmt, ...) \ dev_printk_index_wrap(_dev_alert, KERN_ALERT, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_err(dev, fmt, ...) \ dev_printk_index_wrap(_dev_err, KERN_ERR, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_warn(dev, fmt, ...) \ dev_printk_index_wrap(_dev_warn, KERN_WARNING, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_notice(dev, fmt, ...) \ dev_printk_index_wrap(_dev_notice, KERN_NOTICE, dev, dev_fmt(fmt), ##__VA_ARGS__) #define dev_info(dev, fmt, ...) \ dev_printk_index_wrap(_dev_info, KERN_INFO, dev, dev_fmt(fmt), ##__VA_ARGS__) #if defined(CONFIG_DYNAMIC_DEBUG) || \ (defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE)) #define dev_dbg(dev, fmt, ...) \ dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) #elif defined(DEBUG) #define dev_dbg(dev, fmt, ...) \ dev_printk(KERN_DEBUG, dev, dev_fmt(fmt), ##__VA_ARGS__) #else #define dev_dbg(dev, fmt, ...) \ ({ \ if (0) \ dev_printk(KERN_DEBUG, dev, dev_fmt(fmt), ##__VA_ARGS__); \ }) #endif
如上dev_xx定义,其和pr_xx类似,都可以直接理解为printk+日志级别的封装,一般在linux驱动程序中使用;
同样需要注意,dev_dbg也不是无条件打印的,除非定义了DEBUG或设定了CONFIG_DYNAMIC_DEBUG。一个相关约定是在已经开启了 DEBUG 时,使用VERBOSE_DEBUG来添加dev_vdbg。
2.3 测试
目前在内核驱动代码中,都不再建议直接使用printk直接添加打印信息,而是使用dev_info、dev_dbg、dev_err之类的函数代替,虽然这些 dev_xxx 函数的本质还是使用printk打印的,但是相比起printk,dev_xxx 的好处是:
- 支持打印模块信息、dev信息;
- 支持动态调试(dynamic debug)方式;
比如,如下代码:
dev_warn(&rtc->dev, "invalid alarm value: %ptR\n", &alarm->time);
输出日志如下:
rtc rtc0: invalid alarm value: 1900-02-01T00:00:00
参考文章
[1]34.Linux-printk分析、使用__FILE__, __FUNCTION__, __LINE__ 调试
[2]Linux内核之 printk 打印(转)
[3]内核printk原理介绍