Linux 内核调试篇5(基于Linux6.6)

Linux 内核调试篇5(基于Linux6.6)---printk使用分析

一、概述

在 Linux 内核中,printk 是一个非常重要的日志打印函数,它用于将调试信息、错误信息、警告信息等输出到控制台或日志系统。它是内核中用于信息输出的主要方式之一,类似于用户空间的 printf,但有一些特殊的功能和行为,适用于内核环境。

1. printk 函数简介

printk 函数的基本原型如下:

int printk(const char *fmt, ...);

printk 函数接受一个格式化字符串(类似于 printf)和相应的参数,输出格式化后的日志消息。它并不像 printf 那样直接输出到标准输出,而是将消息写入内核日志缓冲区,并最终输出到控制台或日志文件。

2. printk 的日志级别

printk 提供了多个日志级别,用于标识日志消息的重要性和严重性。每个日志级别都有一个对应的常量,通常以 KERN_* 开头。常见的日志级别包括:

  • KERN_EMERG: 紧急情况(例如,系统崩溃),这是最严重的日志级别。
  • KERN_ALERT: 需要立即采取措施的紧急情况。
  • KERN_CRIT: 严重错误,通常表示设备或内核组件发生了致命错误。
  • KERN_ERR: 错误,表示系统发生了错误,需要注意。
  • KERN_WARNING: 警告,表示潜在问题,但系统仍然可以继续运行。
  • KERN_NOTICE: 提示,表示系统状态的变化,但不一定是问题。
  • KERN_INFO: 信息,用于输出一般的日志消息,常用于显示系统状态或操作。
  • KERN_DEBUG: 调试信息,用于调试时输出详细信息,通常在内核开发或调试过程中使用。

日志级别决定了日志消息的严重性,也影响了日志是否会被实际打印出来。例如,KERN_DEBUG 级别的日志可能在默认配置下被忽略,而 KERN_ERR 或更高级别的日志会被输出。

3. 如何使用 printk

printk 的使用方式与 printf 类似,可以输出各种类型的信息,包括字符串、整数、浮点数等。以下是一些常见的使用示例:

输出一般信息

printk(KERN_INFO "This is an informational message.\n");

输出错误信息

printk(KERN_ERR "An error occurred: %s\n", strerror(-ENOMEM));

输出调试信息

printk(KERN_DEBUG "Debugging value: %d\n", some_value);

输出多行信息

printk(KERN_INFO "Starting process...\n");
printk(KERN_INFO "Process ID: %d\n", task->pid);

4. 日志的输出和控制

printk 输出的信息通常会被发送到内核日志缓冲区,然后通过内核的控制台系统(console)输出到控制台设备。控制台设备可能是虚拟终端、串口、framebuffer 或其他设备,具体取决于系统配置。

日志缓冲区

printk 输出的所有信息会被写入一个内核日志缓冲区。这个缓冲区是一个环形缓冲区(环形队列),有一个固定的大小。当日志缓冲区满时,旧的日志会被新日志覆盖。通常,日志缓冲区会保留日志信息以供 dmesg 等工具查询。

日志过滤

系统可以根据日志级别过滤输出。例如,dmesg 命令会显示所有级别的日志,而某些控制台设置可能只会显示 KERN_ERR 或更严重的日志级别。

控制台输出

  • 虚拟控制台:通常,内核会将日志信息输出到系统的虚拟终端(如 /dev/tty1/dev/tty2 等)。当系统处于单用户模式或在没有图形界面的环境下时,这种方式最常见。
  • 串口输出:在某些嵌入式系统中,内核日志可能通过串口输出,便于调试。
  • Framebuffer 输出:在图形界面环境中,内核日志可以通过 framebuffer 输出到屏幕上。

二、实际应用

所以内核在使用了打印等级,让使用者根据需要来控制需要打印的信息。

2.1、内核启动后更改打印等级 

方式1,内核启动后,修改proc文件系统中的打印等级:

在 /proc/sys/kernel/printk 会显示4个数值(可由 echo 修改),分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最小(最高)允许设置的控制台日志级别、引导时默认的日志级别。当 printk() 中的消息日志级别小于当前控制台日志级别时,printk 的信息(要有\n符)就会在控制台上显示。但无论当前控制台日志级别是何值,通过 /proc/kmsg (或使用dmesg)总能查看。另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/messages.log 中。

比如,修改默认的打印等级

echo "7 7 1 7" > /proc/sys/kernel/printk

当然这种只能在内核启动后修改。

下面给出bootloader启动内核时,没指定打印等级时的log信息(默认为4)

 Bytes transferred = 3649656 (37b078 hex)
## Booting kernel from Legacy Image at 30007fc0 ...
   Image Name:   Linux-4.19.0-ga2a89a6-dirty
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    3649592 Bytes = 3.5 MiB
   Load Address: 30008000
   Entry Point:  30008000
## Flattened Device Tree blob at 40000000
   Booting using the fdt blob at 0x40000000
   XIP Kernel Image ... OK
   Loading Device Tree to 3fff7000, end 3ffffeb2 ... OK
 
Starting kernel ...
 

可以看到默认打印等级4,打印出来的是不全或者说不是所有的打印信息。

2.2、内核启动前更改打印等级 

还可以在内核启动前修改打印等级。

比如在编译内核前,可以通过配置和printk相关的CONFIG来决定打印等级。

内核在编译时的配置文件(/.config)中包含了多个与 printk 相关的选项。通过调整这些配置选项,可以在不同的级别上控制 printk 输出的日志消息。

1、CONFIG_PRINTK
  • 描述CONFIG_PRINTK 是一个启用或禁用 printk 功能的配置选项。一般情况下,这个选项是启用的。如果禁用,内核将不会输出任何通过 printk 打印的日志。

  • 配置

CONFIG_PRINTK=y

如果你没有禁用 printk,那么日志输出功能会被启用。

2、CONFIG_PRINTK_DEFAULT_LOGLEVEL
  • 描述CONFIG_PRINTK_DEFAULT_LOGLEVEL 控制内核在启动时的默认日志级别。这个设置决定了内核启动时 printk 会输出哪些级别的日志。

  • 配置: 这个配置项设置了内核默认的日志级别。它的值对应于 printk 的日志级别常量(如 KERN_EMERG, KERN_ALERT 等)。常见的配置值如下:

    • 0: KERN_EMERG - 紧急情况,内核崩溃时。
    • 1: KERN_ALERT - 警告,需要立即采取行动。
    • 2: KERN_CRIT - 严重错误。
    • 3: KERN_ERR - 错误信息。
    • 4: KERN_WARNING - 警告信息。
    • 5: KERN_NOTICE - 提示信息。
    • 6: KERN_INFO - 信息日志。
    • 7: KERN_DEBUG - 调试信息。
CONFIG_PRINTK_DEFAULT_LOGLEVEL=4  # 默认输出 KERN_WARNING 级别及以上的日志

如果你想让内核在启动时输出更多的日志(比如调试信息),可以将它设置为更低的级别(例如 32)。设置为 7 时,所有的日志都会被输出,包括调试级别的日志。

3、CONFIG_DYNAMIC_DEBUG
  • 描述CONFIG_DYNAMIC_DEBUG 是用于启用动态调试的配置项。动态调试允许在运行时控制调试消息的打印,可以根据需要开启或关闭特定代码路径的日志输出。

    如果启用了 CONFIG_DYNAMIC_DEBUG,可以使用 debug 文件系统来动态调整内核日志输出。

CONFIG_DYNAMIC_DEBUG=y

log输出与否是通过打印等级和console_loglevel 变量比较来实现了,如果我们修改了console_loglevel 变量,那么打印等级自然也就该了。

 static bool suppress_message_printing(int level)
{
	return (level >= console_loglevel && !ignore_loglevel);
}

2.3、其他方式

下面给出几种方式:

如果不想编译内核,可以通过bootloader传参中指定loglevel等级来实现,

下面就是 内核使用文档给出的一个例子:

bootloader传参,loglevel的解析函数

init/main.c 

 static int __init loglevel(char *str)
{
	int newlevel;
 
	/*
	 * Only update loglevel value when a correct setting was passed,
	 * to prevent blind crashes (when loglevel being set to 0) that
	 * are quite hard to debug
	 */
	if (get_option(&str, &newlevel)) {
		console_loglevel = newlevel;
		return 0;
	}
 
	return -EINVAL;
}
 
early_param("loglevel", loglevel);

当然也可以在bootloader指定具体的名字来指定打印等级。

init/main.c 

 static int __init debug_kernel(char *str)
{
	console_loglevel = CONSOLE_LOGLEVEL_DEBUG;
	return 0;
}
 
static int __init quiet_kernel(char *str)
{
	console_loglevel = CONSOLE_LOGLEVEL_QUIET;
	return 0;
}
 
early_param("debug", debug_kernel);
early_param("quiet", quiet_kernel);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值