linux内核调试-printk

----------------------------------------------------------------------------------------------------------------------------

内核版本: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文件中:

/* record buffer */
#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);
static char *log_buf = __log_buf;
static u32 log_buf_len = __LOG_BUF_LEN;

内核日志缓冲区__log_buf的大小为$2^{CONFIG\_LOG\_BUF\_SHIFT}$ ,CONFIG_LOG_BUF_SHIFT默认为17,即128KB,可以通过make menuconfig配置,对应的配置项为LOG_BUF_SHIFT。

用户态可以通过syslog相关的系统调用或者/proc文件以及/dev/kmsg设备节点来查看__log_buf的信息,这些操作都是通过do_syslog的系统调用接口来实现的。

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要打印的信息才会在控制台打印出来,否则不会显示在控制台。

printk的用法与printf基本一致, 最大的区别是printk对%p的扩展,在应用层使用printf打印一个指针的地址时直接使用%p即可, 但是内核为了防止泄漏内存布局的敏感信息,直接使用%p输出的地址是经过hash处理的, 如果想实现和printf的%p一样的效果, printk需要使用%px。

1.4 查看日志

无论当前控制台日志级别是何值,即使没有在控制台打印出来,都可以通过下面两种方法查看日志:

  • 第一种是使用dmesg命令查看日志;
  • 第二种是通过cat /proc/kmsg来查看日志;

另外如果配置好并运行了syslogd 或klogd,没有在控制台上显示的printk的信息也会追加到/var/log/messages.log中。

1.4.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.4.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.5 调整日志级别

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.5.1 配置menuconfig

修改CONFIG_MESSAGE_LOGLEVEL_DEFAULT的值,然后重新编译,更新内核。menuconfig配置路径如下:

Kernel hacking  --->
    printk and dmesg options  --->
        (4) Default message log level (1-7)

如下图所示:

1.5.2 在系统中修改

在系统运行期间,可以通过执行以下命令,修改当前控制态的日志级别:

echo "新的打印级别  4    1    7" > /proc/sys/kernel/printk

如屏蔽掉所有的内核printk打印,只需要把第一个数值调到最小值1或者0,此时可以敲如下命令:

echo "1       4       1      7" > /proc/sys/kernel/printk
1.5.3 打开调试日志

内核中的大部分驱动都使用了dev_dbg接口打印调试信息,日志级别为7,默认是不会输出到控制台的。在需要打印dev_dbg调试信息的驱动文件开头定义DEBUG宏,注意必须是在include/linux/device.h前面:

#define DEBUG

打开DEBUG宏是第一步,这个时候还是不能输出到控制台的,还必须要修改控制台日志级别为8。

二、其他常见用法

为了方便开发者使用,在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,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值