LV06-01-内核模块-02-printk

本文主要是内核模块中常用的打印函数printk的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
buildroot 2023.05.1版本
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

一、概述

大部分常用的C库函数在Linux内核中都已经得到了实现。在所有没有实现的函数中,最著名的就数printf()函数了。内核代码虽然无法调用 printf()函数,但它可以调用printk()函数。printk()函数负责把格式化好的字符串拷贝到内核日志缓冲上,这样syslog程序就可 以通过读取该缓冲区来获取内核信息。

printk()函数是直接使用了向终端写函数tty_write()。而printf()函数是调用write()系统调用函数向标准输出设备写。所以 在用户态(如进程0)不能够直接使用printk()函数,而在内核态由于他已是特权级,所以无需系统调用来改变特权级,因而能够直接使用 printk()函数。printf是使用了标准的C库函数的时候才能使用的,而内核中无法使用标准的C库函数,所以就连最常见的printf都不能使用。

二、两个级别

1. 日志级别

1.1 有哪些?

printk相比printf来说还多了个:日志级别的设置,用来控制printk打印的这条信息是否在终端上显示的,当日志级别的数值小于控制台级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台。在我们内核中一共有8种级别(数字越小级别越高),他们定义在 include/linux/kern_levels.h 中,分别为:

1
2
3
4
5
6
7
8
#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 */

所有的printk()消息都会被打印到内核日志缓冲区,这是一个通过/dev/kmsg输出到用户空间的环 形缓冲区。读取它的通常方法是使用 dmesg 。日志级别指定了一条消息的重要性。内核根据日志级别和当前 console_loglevel (一个内核变量)决定是否立即显示消息(将其打印到当前控制台)。如果消息的优先级比 console_loglevel 高(日志级 别值较低),消息将被打印到控制台。如果省略了日志级别,则以 KERN_DEFAULT 级别打印消息。格式字符串虽然与C99基本兼容,但并不遵循完全相同的规范。它有一些扩展和一些限制(没 有 %n 或浮点转换指定符)。

1.2 怎么控制?

我们可以直接在printk中指定本条打印信息的级别,一般格式如下:

1
printk(KERN_INFO "Message: %s\n", arg);

直接在格式字符串前指定打印等级即可。

2. 控制台级别

2.1 有哪些?

上边提到了控制台级别,控制台级别定义在哪?它们定义在 include/linux/printk.h 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* printk's without a loglevel use this.. */
#define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT

/* We show everything that is MORE important than this.. */
#define CONSOLE_LOGLEVEL_SILENT 0 /* Mum's the word */
#define CONSOLE_LOGLEVEL_MIN 1 /* Minimum loglevel we let people use */
#define CONSOLE_LOGLEVEL_QUIET 4 /* Shhh ..., when booted with "quiet" */
#define CONSOLE_LOGLEVEL_DEFAULT 7 /* anything MORE serious than KERN_DEBUG */
#define CONSOLE_LOGLEVEL_DEBUG 10 /* issue debug messages */
#define CONSOLE_LOGLEVEL_MOTORMOUTH 15 /* You can't shut this one up */

extern int console_printk[];

#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])

我们看一下console_printk这个数组(定义在ernel/printk/printk.c中):

1
2
3
4
5
6
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 */
};
  • console_printk[0]:CONSOLE_LOGLEVEL_DEFAULT,控制台日志级别,优先级高于该值的消息将在控制台显示(也就是终端)。
  • console_printk[1]:MESSAGE_LOGLEVEL_DEFAULT,默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息。
  • console_printk[2]:CONSOLE_LOGLEVEL_MIN,最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级
  • console_printk[3]:CONSOLE_LOGLEVEL_DEFAULT,默认的控制台日志级别。

我们可以在linux系统中通过以下命令查看:

1
cat /proc/sys/kernel/printk
image-20230830220444772

如上图,

  • console_printk[0]:为CONSOLE_LOGLEVEL_DEFAULT:默认为7,所有优先级高于7的log等级(0~6),都会打印在终端上。
  • console_printk[1]:为MESSAGE_LOGLEVEL_DEFAULT:默认为4,printk打印消息时的默认等级。
  • console_printk[2]:为CONSOLE_LOGLEVEL_MIN:控制台日志级别可被设置的最小值(最高优先级),这里默认为1。
  • console_printk[3]:为CONSOLE_LOGLEVEL_DEFAULT:默认的控制台日志级别,默认为7。

2.2 怎么控制?

我们直接在终端输入以下指令即可:

1
2
3
echo 8 4 1 7 > /proc/sys/kernel/printk
# 也可以用下边的命令
dmesg -n 5 # 这种只能修改 控制台日志级别 也就是 console_printk[0]

中间的数字分别就代表各个等级,可以直接这样修改。例如:

image-20230830221715562

三、使用实例

代码可以看这里:01_module_load/printk_eg · sumumm/imx6ull-linuxdriver-eg - 码云 - 开源中国 (gitee.com)。操作的时候主要是看加载驱动的时候的打印信息,主要是以下信息:

1
2
3
4
5
6
7
8
printk(KERN_EMERG"KERN_EMERG:%s\r\n", KERN_EMERG);
printk(KERN_ALERT"KERN_ALERT:%s\r\n", KERN_ALERT);
printk(KERN_CRIT"KERN_CRIT:%s\r\n", KERN_CRIT);
printk(KERN_ERR"KERN_ERR:%s\r\n", KERN_ERR);
printk(KERN_WARNING"KERN_WARNING:%s\r\n", KERN_WARNING);
printk(KERN_NOTICE"KERN_NOTICE:%s\r\n", KERN_NOTICE);
printk(KERN_INFO"KERN_INFO:%s\r\n", KERN_INFO);
printk(KERN_DEBUG"KERN_DEBUG:%s\r\n", KERN_DEBUG);

然后通过下边的命令修改各个默认值来看内核日志打印情况:

1
2
echo 8 4 1 7 > /proc/sys/kernel/printk
echo 0 4 1 7 > /proc/sys/kernel/printk