printk 内核打印
Posted 阿基米东
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了printk 内核打印相关的知识,希望对你有一定的参考价值。
printk 在内核源码中用来记录日志信息的函数,只能在内核源码范围内使用,用法类似于 printf 函数。
printk 函数主要做两件事情:
- 将信息记录到 log 中;
- 调用控制台驱动来将信息输出。
printk 介绍
printk 将内核信息输出到内核信息缓冲区中,内核缓冲区在 kernel/printk/printk.c 中定义:
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
内核信息缓冲区是一个环形缓冲区(Ring Buffer),因此,如果塞入的消息过多,则就会将之前的消息冲刷掉。
消息级别
Linux 内核共提供了八种不同的消息级别,分为级别 0~7。数值越大,表示级别越低,对应的消息越不重要。相应的宏定义在 include/linux/kern_levels.h
文件中。
#define KERN_SOH "\\001" /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\\001'
#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 */
说明:
KERN_EMERG
表示紧急事件,一般是系统崩溃之前提示的消息;KERN_ALERT
表示必须立即采取行动的消息;KERN_CRIT
表示临界状态,通常涉及严重的硬件或软件操作失败;KERN_ERR
用于报告错误状态,设备驱动程序会经常使用该级别来报告来自硬件的问题;KERN_WARNING
对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题;KERN_NOTICE
表示有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报;KERN_INFO
表示内核提示信息,很多驱动程序在启动的时候,用这个级别打印出它们找到的硬件信息;KERN_DEBUG
用于调试信息。
内核 printk 文件
通过 /proc/sys/kernel/printk
文件可以调节 printk 的输出等级,该文件有 4 个数字值。例如,在 Ubuntu 上的值如下:
$ cat /proc/sys/kernel/printk
4 4 1 7
四个数值的含义如下:
- 控制台日志级别:优先级高于该值的消息将被打印至控制台;
- 默认的消息日志级别:将用该优先级来打印没有优先级的消息;
- 最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);
- 默认的控制台日志级别:控制台日志级别的缺省值。
这四个值是在 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 中修改
修改 CONFIG_MESSAGE_LOGLEVEL_DEFAULT
的值,然后重新编译,更新内核。menuconfig 配置路径如下:
Kernel hacking --->
printk and dmesg options --->
(4) Default message log level (1-7)
在系统中修改
在系统运行期间,可以通过修改 /proc/sys/kernel/printk 中的值来改变内核打印效果。例如,屏蔽掉所有的内核 printk 打印,只需要把第一个数值调到最小值1或者0。
echo 1 4 1 7 > /proc/sys/kernel/printk
使用示例
在使用 printk 时我们会将消息级别放到最开始的位置,例如:
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");
如果没有设置消息的日志级别,默认使用 default_message_loglevel
级别(该值可在 menuconfig 中修改)。
当 printk 中的消息日志级别小于当前控制台的日志级别(console_printk[0]
)时,printk 的信息就会在控制台上显示。但无论当前控制台日志级别是何值,即使没有在控制台打印出来,都可以通过下面两种方法查看日志:
- 第一种是使用 dmesg 命令打印;
- 第二种是通过
cat /proc/kmsg
来打印。
另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/messages.log 中。
查看打印
将上述示例代码添加到 最简单的内核模块 的 hello_init
函数中,编译并执行 insmod hello.ko
安装模块。
dmesg
执行 dmesg | tail
将会看到如下打印:
[20120.194934] module init success
[20120.194935] GetIot: KERN_EMERG
[20120.194937] GetIot: KERN_ALERT
[20120.194938] GetIot: KERN_CRIT
[20120.194939] GetIot: KERN_ERR
[20120.194940] GetIot: KERN_WARNING
[20120.194940] GetIot: KERN_NOTICE
[20120.194940] GetIot: KERN_INFO
[20120.194941] GetIot: KERN_DEBUG
/proc/kmsg
也可以执行 cat /proc/kmsg
等待内核打印:
$ sudo cat /proc/kmsg
<6>[20120.194934] module init success
<0>[20120.194935] GetIot: KERN_EMERG
<1>[20120.194937] GetIot: KERN_ALERT
<2>[20120.194938] GetIot: KERN_CRIT
<3>[20120.194939] GetIot: KERN_ERR
<4>[20120.194940] GetIot: KERN_WARNING
<5>[20120.194940] GetIot: KERN_NOTICE
<6>[20120.194940] GetIot: KERN_INFO
<7>[20120.194941] GetIot: KERN_DEBUG
其他用法
pr_xxx
除了直接使用 printk 加消息级别的方式,在 <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__)
#define pr_devel(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_debug(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
需要注意的是,其他 pr_XXX()
函数能无条件地打印,但 pr_debug()
却不能。因为默认情况下它不会被编译,除非定义了 DEBUG
或设定了 CONFIG_DYNAMIC_DEBUG
。
dev_xxx
对于驱动程序,在 <linux/device.h>
里也提供了一些驱动模型诊断宏,例如 dev_err、dev_warn、dev_info 等等。使用它们,不仅可以按标记的消息级别打印,还会打印对应的设备和驱动信息,这对于驱动调试来说相当重要。
#define dev_emerg(dev, fmt, ...) _dev_emerg(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_crit(dev, fmt, ...) _dev_crit(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_alert(dev, fmt, ...) _dev_alert(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_err(dev, fmt, ...) _dev_err(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_warn(dev, fmt, ...) _dev_warn(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_notice(dev, fmt, ...) _dev_notice(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_info(dev, fmt, ...) _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
#if defined(CONFIG_DYNAMIC_DEBUG)
#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
同样需要注意,deb_dbg()
也不是无条件打印的,除非定义了 DEBUG
或设定了 CONFIG_DYNAMIC_DEBUG
。一个相关约定是在已经开启了 DEBUG
时,使用 VERBOSE_DEBUG
来添加 dev_vdbg()
。
以上是关于printk 内核打印的主要内容,如果未能解决你的问题,请参考以下文章
linux内核打印数据到串口控制台,printk数据不打印问题