目录
一、printk 介绍
在Linux内核中,使用 printk 能将打印信息输出到控制台。
printk函数主要做两件事情:
- 将信息记录log_bug中(一般为/var/log/message);
- 调用控制台驱动来将log_bug信息输出到控制台;
printk 将内核信息输出到内核信息缓冲区中,内核缓冲区在 kernel/printk/printk.c 中定义:
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
1、打印级别
Linux 内核共提供了八种不同的消息级别,分为级别 0~7。数值越大,表示级别越低。宏定义在 include/linux/kern_levels.h 中:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
说明:
KERN_EMERG | 表示紧急事件,一般是系统崩溃之前提示的消息 |
KERN_ALERT | 表示必须立即采取行动的消息 |
KERN_CRIT | 表示临界状态,通常涉及严重的硬件或软件操作失败 |
KERN_ERR | 用于报告错误状态,设备驱动程序会经常使用该级别来报告来自硬件的问题 |
KERN_WARNING | 对可能出现问题的情况进行警告,这类情况通常不会对系统造成严重的问题 |
KERN_NOTICE | 表示有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报 |
KERN_INFO | 表示内核提示信息,很多驱动程序在启动的时候,用这个级别打印出它们找到的硬件信息 |
KERN_DEBUG | 用于调试信息 |
注意:无论当前控制台日志级别是何值,即使没有在控制台打印出来,都可以通过下面两种方法查看日志:
- 第一种是使用 dmesg 命令打印;
- 第二种是通过 cat /proc/kmsg 来打印。
2、查看、修改printk打印等级
2.1 查看 printk 打印等级
通过 /proc/sys/kernel/printk 文件可以查看、修改 printk 的打印等级
$ cat /proc/sys/kernel/printk
4 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);
2.2 配置menuconfig修改默认打印等级
修改CONFIG_MESSAGE_LOGLEVEL_DEFAULT的值,然后重新编译,更新内核。menuconfig配置路径如下:
Kernel hacking --->
printk and dmesg options --->
(4) Default message log level (1-7)
如图:
2.3 Linux系统命令修改printk打印等级
修改 /proc/sys/kernel/printk 中的值来改变内核打印效果。例如:屏蔽掉所有的内核 printk 打印,只需要把第一个数值调到最小值1或者0
echo "1 4 1 7" > /proc/sys/kernel/printk
3、代码中使用 printk
printk(KERN_ERR "debug info\n"); // 打印信息以 KERN_ERR 等级输出
二、dev_xxx 系列函数
Linux 提供了一些驱动模型诊断宏,例如dev_err、dev_warn、dev_info等等。使用它们,不仅可以按标记的消息级别打印,还会打印对应的设备和驱动信息。目前在Linux内核驱动代码中,不再建议直接使用printk添加打印信息,而是使用dev_info、dev_dbg、dev_err之类的函数代替。
dev_xxx函数的本质还是使用printk打印的,但是相比起printk区别:
- 支持打印模块信息、dev信息
- 支持动态调试(dynamic debug)方式
1、dev_xxx源码
下面简述下这几个dev_xxx函数的基本使用规则,以及动态调试使用方式。
- dev_info():启动过程、或者模块加载过程等“通知类的”信息等,一般只会通知一次,例如probe函数;
- dev_dbg():一般使用在普通错误,如-EINVAL、-ENOMEM等errno发生处,用于调试;
- dev_err():一般使用在严重错误,尤其是用户无法得到errno的地方,或者程序员不容易猜测系统哪里出了问题的地方;
/* drivers/base/core.c */
#define define_dev_printk_level(func, kern_level) \
void func(const struct device *dev, const char *fmt, ...) \
{ \
struct va_format vaf; \
va_list args; \
\
va_start(args, fmt); \
\
vaf.fmt = fmt; \
vaf.va = &args; \
\
__dev_printk(kern_level, dev, &vaf); \
\
va_end(args); \
} \
EXPORT_SYMBOL(func);
define_dev_printk_level(_dev_emerg, KERN_EMERG);
define_dev_printk_level(_dev_alert, KERN_ALERT);
define_dev_printk_level(_dev_crit, KERN_CRIT);
define_dev_printk_level(_dev_err, KERN_ERR);
define_dev_printk_level(_dev_warn, KERN_WARNING);
define_dev_printk_level(_dev_notice, KERN_NOTICE);
define_dev_printk_level(_dev_info, KERN_INFO);
/* include/linux/device.h */
#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__)
2、测试结果
dev_warn(&rtc->dev, "invalid alarm value: %ptR\n", &alarm->time); // %ptR输出时间
输出日志如下:
rtc rtc0: invalid alarm value: 1900-02-01T00:00:00
三、修改日志输出位置
uboot的bootargs里配置“console=ttySA0,115200”,将会将日志信息输出在串口UART0上。同样如果配置“console=tty1 console=ttySA0,115200”,将会同时将将日志信息输出到串口UART0以及LCD屏幕上。