Linux内核之 printk 打印
前言一、printk 介绍1. printk 消息级别2.内核 printk 文件二、调整打印级别1.在 menuconfig 中修改2.在系统中修改(常用)三、使用示例四、查看打印1.dmesg2./proc/kmsg五、其他常见的用法1.pr_xx 用法2.dev_xxx 用法3.动态打印调试方法前言
printk 在内核源码中用来记录日志信息的函数,只能在内核源码范围内使用,用法类似于 printf 函数。
printk 函数主要做两件事情:
1、将信息记录到 log 中;
2、调用控制台驱动来将信息输出。
一、printk 介绍
printk 将内核信息输出到内核信息缓冲区中,内核缓冲区在kernel/printk/printk.c
中定义:
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
1. printk 消息级别
Linux 内核共提供了八种不同的消息级别,分为级别 0~7。数值越大,表示级别越低,对应的消息越不重要。相应的宏定义在include/linux/kern_levels.h
文件中。
#define KERN_SOH "\001"/* ASCII Start Of Header */#define KERN_SOH_ASCII '\001'#define KERN_EMERGKERN_SOH "0" /* system is unusable */#define KERN_ALERTKERN_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_NOTICEKERN_SOH "5" /* normal but significant condition */#define KERN_INFO KERN_SOH "6" /* informational */#define KERN_DEBUGKERN_SOH "7" /* debug-level messages */
说明:
KERN_EMERG
表示紧急事件,一般是系统崩溃之前提示的消息;KERN_ALERT
表示必须立即采取行动的消息;KERN_CRIT
表示临界状态,通常涉及严重的硬件或软件操作失败;KERN_ERR
用于报告错误状态,设备驱动程序会经常使用该级别来报告来自硬件的问题;KERN_WARNING
对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题;KERN_NOTICE
表示有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报;KERN_INFO
表示内核提示信息,很多驱动程序在启动的时候,用这个级别打印出它们找到的硬件信息;KERN_DEBUG
用于调试信息。
2.内核 printk 文件
通过/proc/sys/kernel/printk
文件可以调节 printk 的输出等级,该文件有 4 个数字值。例如,在 Ubuntu 上的值如下:
$ cat /proc/sys/kernel/printk4 4 1 7
四个数值的含义如下:
控制台日志级别
:优先级高于该值的消息将被打印至控制台;
默认的消息日志级别
:将用该优先级来打印没有优先级的消息(即 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);
二、调整打印级别
调整 printk 打印级别可以使得在印 Log 时控制输出 Log 的信息量以及控制自己想要输出的信息级别,下面则介绍调整 printk 打印级别的几种方法:
1.在 menuconfig 中修改
修改CONFIG_MESSAGE_LOGLEVEL_DEFAULT
的值,然后重新编译,更新内核。menuconfig
配置路径如下:
Kernel hacking --->printk and dmesg options --->(4) Default message log level (1-7)
2.在系统中修改(常用)
在系统运行期间,可以通过修改/proc/sys/kernel/printk
中的值来改变内核打印效果。例如,屏蔽掉所有的内核 printk 打印,只需要把第一个数值调到最小值1或者0,此时可以敲如下 cmd:
echo 1 4 17 > /proc/sys/kernel/printk
或
echo 1 > /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
级别(该值可由上述两种方式修改,即第二个数值)。
当 printk 中的消息日志级别小于当前控制台的日志级别(console_printk[0])时,printk 的信息就会在控制台上显示。但无论当前控制台日志级别是何值,即使没有在控制台打印出来,都可以通过下面两种方法查看日志:
第一种是使用 dmesg 命令打印;第二种是通过 cat /proc/kmsg 来打印。
另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/messages.log 中。
四、查看打印
将上述示例代码添加到 最简单的内核模块 的 hello_init 函数中,编译并执行insmod hello.ko
安装模块。
1.dmesg
执行dmesg | tail
将会看到如下打印:
[0.194934] module init success[0.194935] GetIot: KERN_EMERG[0.194937] GetIot: KERN_ALERT[0.194938] GetIot: KERN_CRIT[0.194939] GetIot: KERN_ERR[0.194940] GetIot: KERN_WARNING[0.194940] GetIot: KERN_NOTICE[0.194940] GetIot: KERN_INFO[0.194941] GetIot: KERN_DEBUG
2./proc/kmsg
也可以执行cat /proc/kmsg
等待内核打印:
$ sudo cat /proc/kmsg <6>[0.194934] module init success<0>[0.194935] GetIot: KERN_EMERG<1>[0.194937] GetIot: KERN_ALERT<2>[0.194938] GetIot: KERN_CRIT<3>[0.194939] GetIot: KERN_ERR<4>[0.194940] GetIot: KERN_WARNING<5>[0.194940] GetIot: KERN_NOTICE<6>[0.194940] GetIot: KERN_INFO<7>[0.194941] GetIot: KERN_DEBUG
五、其他常见的用法
1.pr_xx 用法
在 Linux 内核中除了直接使用 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__)#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.dev_xxx 用法
对于 Linux 驱动程序,在<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()。
补充:
在menuconfig
中开启动态调试:
Kernel hacking --->printk and dmesg options --->[*] Enable dynamic printk() support
目前在 kernel 驱动代码中,都不再建议直接使用 printk 直接添加打印信息,而是使用 dev_info、dev_dbg、dev_err 之类的函数代替,虽然这些 dev_xxx 函数的本质还是使用 printk 打印的,但是相比起 printk,dev_xxx 的好处是:
支持打印模块信息、dev 信息;支持动态调试(dynamic debug)方式。
3.动态打印调试方法
打开内核动态调试开关,make menuconfig
选中CONFIG_DYNAMIC_DEBUG
以及CONFIG_DEBUG_FS
。Linux 启动后,使用命令行挂载上 dbgfs。
mkdir /mnt/dbgmount -t debugfs none /mnt/dbg
使用下面方式控制你想输出 dev_dbg() 信息
控制某个文件所有 dev_dbg()
echo -n "file xxx.c +p" > /mnt/dbg/dynamic_debug/control
控制某个函数所有 dev_dbg()
echo -n "func xxx +p" > /mnt/dbg/dynamic_debug/control
运行程序,使用 dmesg 则可以看到相应 dev_dbg() 的输出信息。当调试结束,不再想输出 dev_dbg() 信息了,使用下面命令关闭即可。
echo -n "file xxx.c -p" > /mnt/dbg/dynamic_debug/controlecho -n "func xxx -p" > /mnt/dbg/dynamic_debug/control
动态打印调试的原理是,当编译选项CONFIG_DYNAMIC_DEBUG
打开的时候,在编译阶段,kernel 会把所有使用 dev_dbg() 的信息记录在一个 table 中,这些信息我们可以从/mnt/dbg/dynamic_debug/control
解析出来。
开发版本,打开CONFIG_DYNAMIC_DEBUG
和CONFIG_DEBUG_FS
,配合 dbgfs 动态观测和调试内核代码;正式版本,关闭CONFIG_DYNAMIC_DEBUG
和CONFIG_DEBUG_FS
,所有 dbgfs 以及 dev_dbg 信息都从编译阶段自动移除。
参考链接