Linux Kernel调试:强大的printk(一)

引言

想了好久,还是觉得这个标题才配得上printk!^_^

我相信,不管做什么开发,使用最多的调试手段应该就是打印了,从我们学习编程语言第一课开始,写的第一段代码,就是打印"Hello, world"。

内核也不例外,到处都有打印语句,只不过在内核中的打印函数是printk,printk作为内核中不可或缺的基础工具,几乎可以在内核的任何上下文中都可以使用,正应了那句话,它明明是那么普通,却又那么强大~~

关于printk的内容还挺多,所以分多篇深入介绍,这是第一篇,介绍printk的基本用法,后面介绍printk的进阶用法。

如果本篇你已经熟悉,可以直接阅读后面的内容,快捷跳转:

Linux Kernel调试:强大的printk(二):pr_xxx相关的内容

Linux Kernel调试:强大的printk(三):dev_xxx相关的内容,以及限制打印速率

printk的特性

printk是一个在内核中使用的打印函数,用于向内核日志系统输出信息,其通过将消息写入内核日志缓冲区,然后由不同的工具读取,比如 dmesg 命令或者查看 /var/log/syslog 文件(根据系统配置不同而有所变化)。

它对于调试和报告错误很有用,并且可以在中断上下文中使用,但是使用时要小心:如果机器的控制台中充斥着printk消息则会无法使用。

相比于用户空间的printf,printk 有一些独特的特性和用途:

  • 日志级别:printk 允许你指定日志级别,这可以控制该消息是否显示到控制台。例如,紧急错误可能需要立即显示,而调试信息则不一定。

  • 缓冲区:printk 的输出首先被写入一个环形缓冲区(ring buffer)中。这意味着即使没有控制台,或者控制台驱动程序尚未初始化,也可以记录消息。你可以通过读取 /proc/kmsg 或者使用 dmesg 命令来查看这些消息。

  • 时间戳:每个 printk 消息都会附带一个时间戳,这个时间戳是从系统启动开始计算的时间,单位是秒和微秒。这对于调试非常有用,因为它可以帮助确定事件发生的顺序和间隔。

  • 异步行为:与 printf 不同,printk 并不保证消息会立刻出现在控制台上。这是因为内核代码不能阻塞等待 I/O 完成,所以 printk 实际上将消息放入了一个队列中,稍后由另一个内核线程负责将其实际输出到控制台或其他目标。

printk的函数原型

printk定义于include/linux/printk.h文件中,原型如下:

#define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__)#define printk_index_wrap(_p_func, _fmt, ...)                                \({                                                                \__printk_index_emit(_fmt, NULL, NULL);                        \_p_func(_fmt, ##__VA_ARGS__);                                \})int _printk(const char *fmt, ...);

printk的用法

printk的调用方法如下:

printk(KERN_INFO "My printk message\n");

这里的 KERN_INFO 是一个宏,它实际上只是一个字符串常量,用来表示这条消息的日志级别。其他可用的日志级别稍后会讲。我们可以使用dmesg(1)之类的查看日志的工具根据日志级别进行过滤。

尽管 printk 很方便,但在性能关键的路径中应谨慎使用,因为即使是简单的日志记录操作也可能引入显著的开销。另外,在一些时序要求严格的场合,一定要在关闭日志的情况下充分测试,因为有时打印日志会改变时序,会出现关闭log时,程序反而不能正常工作。

日志级别

日志级别在include/linux/kern_levels.h文件中定义:

#define KERN_SOH        "\001"                /* ASCII Start Of Header */
#define KERN_SOH_ASCII        '\001'#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 *//** Annotation for a "continued" line of log printout (only done after a* line that had no enclosing \n). Only to be used by core/arch code* during early bootup (a continued line is not SMP-safe otherwise).*/
#define KERN_CONT        KERN_SOH "c"

  • 可以看到,日志级别有0~7种,从名称也可以看得出来,数值越小越紧急,优先级越高

  • KERN_SOH是一个控制字符,用于标记日志级别的开始,当解析到KERN_SOH后,其后的一个字符就是日志级别,或者KERN_CONT

  • KERN_CONT的意思是这是前一条消息的续行消息,不会添加时间戳、日志级别等新行信息,也不会换行

  • 如果没有解析到KERN_SOH则使用默认级别,KERN_DEFAULT定义为空,这就可以通过编译时配置或者运行时设置默认的日志级别了

printk输出到哪里

以下是 printk 输出信息可能到达的设备或位置:

内核日志缓冲区

printk 首先将消息写入内核日志缓冲区,这是一个环形缓冲区,用于临时存储内核消息,直到它们被用户空间的工具(如 dmesg 或日志守护进程)读取。

控制台(Console)

根据内核的配置和日志级别,printk 输出的消息可能会直接显示在控制台上。

  • 虚拟控制台(TTY):在本地终端(如 /dev/tty1 或 /dev/ttyS0)上显示消息。

  • 串行控制台:通过串行端口(如 COM 端口)输出消息,常用于服务器或嵌入式设备的调试。

  • 图形控制台:在图形界面的终端窗口中显示消息(如 X 终端)。

通过内核启动参数(如 console=ttyS0,115200)或运行时配置(如 dmesg 的日志级别设置)来控制哪些消息会显示在控制台上。

日志文件

用户空间的日志守护进程(如 systemd-journald 或 rsyslogd)会从内核日志缓冲区读取消息,并将它们写入日志文件。

常见日志文件:

  • /var/log/syslog 或 /var/log/messages:系统日志文件,存储内核消息和其他系统日志。

  • /var/log/dmesg:专门存储内核日志的文件,通常由 dmesg 命令更新。

日志守护进程的配置文件(如 /etc/rsyslog.conf 或 /etc/systemd/journald.conf)决定了日志的存储位置和格式。

网络日志服务器

在某些配置下,内核日志可以通过网络发送到远程日志服务器,便于集中管理和监控。

实现方式:通过日志守护进程(如 rsyslogd)配置网络日志功能,将日志消息发送到指定的远程服务器。

其他输出目标

  • 串行端口:除了作为控制台外,串行端口也可以被配置为单独的日志输出目标,用于调试或日志备份。

  • USB 调试接口:在一些嵌入式设备中,printk 输出可以通过 USB 调试接口发送到连接的主机。

  • 自定义设备:通过内核模块或驱动程序,可以将 printk 输出重定向到其他自定义设备(如存储设备或专用的调试接口)。

用户空间程序

用户空间程序可以通过 /dev/kmsg 或 dmesg 等接口直接读取内核日志,并进行进一步处理或显示。

  • dmesg:用于显示内核日志的命令行工具。

  • journalctl(systemd 系统):用于查看和管理内核日志的工具。

Ubuntu控制台日志级别配置

我们在ubuntu上,有时会发现,有些log不会显示到控制台上,通过dmesg命令才可以看到,这是因为内核向控制台输出的信息取决于其日志级别,通过 /proc/sys/kernel/printk 文件控制,我们查看该文件内容如下:

这4个数字的含义如下:

第一个数字是控制台日志级别(console_loglevel),表示只有严重程度高于或等于这个级别的内核消息会被打印到控制台上。

第二个数字是默认的消息日志级别(default_message_loglevel),它设定了当调用内核函数 printk() 而没有明确指定优先级时,给消息分配的默认优先级。

第三个数字是最低的控制台日志级别(minimum_console_loglevel),系统允许的最小控制台日志级别。例如,如果这个值为 1,则控制台日志级别不能设置为 0KERN_EMERG)。

第四个数字是默认的控制台日志级别(default_console_loglevel),代表启动时的默认控制台日志级别。它是一个参考值,在某些情况下,系统可能会重置控制台日志级别回到这个默认值。

所以第一个数字是4,就表示比KERN_WARNING级别高的打印信息才会出现在控制台上。这在一定程度上过滤了一些并不是很重要的信息。

printk格式占位符

一些常见的printk格式占位符:

  • 对于size_t和ssize_t类型(分别表示有符号和无符号整数),请分别使用%zu和%zd格式说明符

  • 在打印内核空间中的地址(指针)时:‚

    • 非常重要:出于安全考虑,请使用%pK(它只会输出哈希值,有助于防止信息泄露,这是一个严重的安全问题)

    • 对于实际的指针,请使用%px查看实际地址(不要在生产环境中这样做!)

    • 对于物理地址,请使用%pa

  • 若要将原始缓冲区打印为十六进制字符串,请使用%*ph(其中*由字符数量代替;对于少于65个字符的缓冲区使用此方法,对于更多字符的缓冲区则使用print_hex_dump_bytes()函数)。有多种变体可用(参见随后的内核文档链接)

完整的printk格式占位符列表,包括使用示例,请查阅内核文档:

中文:Documentation/translations/zh_CN/core-api/printk-formats.rst

英文:Documentation/core-api/printk-formats.rst

内核模块中使用printk

虽然在内核模块中使用printk显得很low(一般都是使用pr_xxx或者dev_xxx),但是由于我们这一篇就是讲printk的,所以还是以printk为例写一个内核模块,来实际使用一下printk,关于写内核模块的知识这里不会讲解,后面看看,如有必要,会补上这部分知识,现在缺少这部分知识的同学需要自己学习一下

下面是代码,共有两个文件printk_usage.c和Makefile文件,可到这里获取https://gitee.com/coolloser/linux-kerenl-debug:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>// 定义模块加载函数
static int __init printk_module_init(void)
{printk(KERN_INFO "====printk模块加载成功!====\n");printk(KERN_DEBUG "这是一个DEBUG级别的信息\n");printk(KERN_INFO "这是一个INFO级别的信息\n");printk(KERN_WARNING "这是一个WARNING级别的信息\n");printk(KERN_ERR "这是一个ERROR级别的信息\n");printk(KERN_CRIT "这是一个CRITICAL级别的信息\n");printk(KERN_ALERT "这是一个ALERT级别的信息\n");printk(KERN_EMERG "这是一个EMERGENCY级别的信息\n");// 使用KERN_CONT继续上一条日志消息printk(KERN_INFO "这是一条需要继续的信息...");printk(KERN_CONT "...这是继续的部分\n");// 使用KERN_DEFAULT设置默认日志级别printk(KERN_DEFAULT "这是默认日志级别的信息\n");return 0; // 返回0表示模块加载成功
}// 定义模块卸载函数
static void __exit printk_module_exit(void)
{printk(KERN_INFO "====printk模块卸载成功!====\n");
}// 注册模块加载和卸载函数
module_init(printk_module_init);
module_exit(printk_module_exit);// 模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("your_name");
MODULE_DESCRIPTION("一个简单的printk内核模块示例");
MODULE_VERSION("0.1");
# 定义模块名称
MODULE_NAME := printk_usage# 定义内核构建目录,替换成你自己的路径
KERNEL_BUILD_DIR := /home/leo/debug_kernel/linux-6.12.28# 定义目标文件
obj-m += $(MODULE_NAME).o# 默认目标
all:@echo "Building the $(MODULE_NAME) kernel module..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) modules# 清理目标
clean:@echo "Cleaning up the build environment..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) clean

直接执行make命令进行编译:

会生成printk_usage.ko

为了测试哪些log会直接显示到终端上,我们需要输入ctrl+alt+F3打开一个虚拟终端(tty3),然后登录

执行如下命令加载模块:

cd modules/001_printk_usage
sudo insmod printk_usage.ko

会显示如下内容:

忽略中文乱码问题^_^,还是能看到ERROR,CRITICAL,ALERT,EMERGENCY级别的log直接显示到终端上了,这符合/proc/sys/kernel/printk文件控制的log级别

然后我们回到图形界面(ctrl+alt+F1),输入如下命令:

sudo dmesg

可以看到如下内容:

模块中打印的所有内容都可以看到,可以看到KERN_CONT对应的log接续在上一条的后面

总结

本篇介绍了printk的基础知识和基本用法,最后通过一个内核模块演示了printk的级别等用法,以及如何查看这些log,由于printk篇幅较长,一篇写完太长,阅读起来比较累,所以分多篇进行介绍,后面会介绍printk的进阶用法,如pr_xxx和dev_xxx,还有printk_ratelimited等等的

请继续阅读下一篇:

Linux Kernel调试:强大的printk(二)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/81305.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于NLP技术的客户投诉与需求文本分类方法研究

目录 摘要 1. 引言 2. 文本分类基础 2.1 文本分类的定义与类型 2.2 文本分类的评价指标 3. 传统文本分类方法 3.1 基于TF-IDF和SVM的方法 3.2 基于主题模型和词向量的改进方法 4. 深度学习文本分类方法 4.1 TextCNN模型 4.2 BiLSTM模型 4.3 注意力机制与Transformer…

#RabbitMQ# 消息队列入门

目录 一 MQ技术选型 1 运行rabbitmq 2 基本介绍 3 快速入门 1 交换机负责路由消息给队列 2 数据隔离 二 Java客户端 1 快速入门 2 WorkQueue 3 FanOut交换机 4 Direct交换机 5 Topic交换机 *6 声明队列交换机 1 在配置类当中声明 2 使用注解的方式指定 7 消息转…

【深度学习】多目标融合算法(六):渐进式分层提取模型PLE(Progressive Layered Extraction)

目录 一、引言 二、PLE&#xff08;Progressive Layered Extraction&#xff0c;渐进式分层提取模型&#xff09; 2.1 技术原理 2.2 技术优缺点 2.3 业务代码实践 2.3.1 业务场景与建模 2.3.2 模型代码实现 2.3.3 模型训练与推理测试 2.3.4 打印模型结构 三、总结 一…

【Java开发日记】如何使用Java开发在线生成 pdf 文档

一、介绍 在实际的业务开发的时候&#xff0c;研发人员往往会碰到很多这样的一些场景&#xff0c;需要提供相关的电子凭证信息给用户&#xff0c;例如网银&#xff0f;支付宝&#xff0f;微信购物支付的电子发票、订单的库存打印单、各种电子签署合同等等&#xff0c;以方便用…

Oracle 11g 单实例使用+asm修改主机名导致ORA-29701 故障分析

解决 把服务器名修改为原来的&#xff0c;重启服务器。 故障 建表空间失败。 分析 查看告警日志 ORA-1119 signalled during: create tablespace splex datafile ‘DATA’ size 2000M… Tue May 20 18:04:28 2025 create tablespace splex datafile ‘DATA/option/dataf…

消息队列的使用

使用内存队列来处理基于内存的【生产者-消费者】场景 思考和使用Disruptor Disruptor可以实现单个或多个生产者生产消息&#xff0c;单个或多个消费者消息&#xff0c;且消费者之间可以存在消费消息的依赖关系 使用Disruptor需要结合业务特性&#xff0c;设计要灵活 什么业务…

《帝国时代1》游戏秘籍

资源类 PEPPERONI PIZZA&#xff1a;获得 1000 食物。COINAGE&#xff1a;获得 1000 金。WOODSTOCK&#xff1a;获得 1000 木头。QUARRY&#xff1a;获得 1000 石头。 建筑与生产类 STEROIDS&#xff1a;快速建筑。 地图类 REVEAL MAP&#xff1a;显示所有地图。NO FOG&#xf…

使用JSP踩过的坑

虽然说jsp已经过时了&#xff0c;但是有时维护比较老的项目还是需要的。 下面说下&#xff0c;我使用jsp踩过的坑&#xff1a; 1.关于打印输出 在jsp中输出使用 out.println("hello");而不是 System.out.println("hello");如果在定义函数部分需要打印…

redis集群创建时手动指定主从关系的方法

适用场景&#xff1a; 创建主从关系时默认参数 --cluster-replicas 1 会自动分配从节点。 为了能精确控制 Redis Cluster 的主从拓扑结构&#xff0c;我们通过 Redis Cluster 的手动分片功能来实现 一、手动指定主从关系的方法 使用 redis-cli --cluster-replicas 0 先创建纯…

ROS合集(七)SVIn2声呐模块分析

文章目录 一、整体思想二、具体误差建模流程三、总结明确&#xff08;预测值与观测值&#xff09;四、选点逻辑五、Sonar 数据处理流水线1. ROS Launch 配置&#xff08;imagenex831l.launch&#xff09;2. SonarNode 节点&#xff08;sonar_node.py&#xff09;3. Subscriber …

Python爬虫实战:研究PySpider框架相关技术

1. 引言 1.1 研究背景与意义 网络爬虫作为互联网数据采集的重要工具,在信息检索、舆情分析、市场调研等领域发挥着重要作用。随着互联网信息的爆炸式增长,如何高效、稳定地获取所需数据成为了一个关键挑战。PySpider 作为一款功能强大的 Python 爬虫框架,提供了丰富的功能…

《大模型开源与闭源的深度博弈:科技新生态下的权衡与抉择》

开源智能体大模型的核心魅力&#xff0c;在于它构建起了一个全球开发者共同参与的超级协作网络。想象一下&#xff0c;来自世界各个角落的开发者、研究者&#xff0c;无论身处繁华都市还是偏远小镇&#xff0c;只要心怀对技术的热爱与追求&#xff0c;就能加入到这场技术狂欢中…

大数据模型对陌生场景图像的识别能力研究 —— 以 DEEPSEEK 私有化部署模型为例

摘要 本研究聚焦于已训练的大数据模型能否识别未包含在样本数据集中的陌生场景图像这一问题&#xff0c;以 DEEPSEEK 私有化部署模型为研究对象&#xff0c;结合机器学习理论&#xff0c;分析模型识别陌生场景图像的影响因素&#xff0c;并通过理论探讨与实际应用场景分析&…

STM32——从点灯到传感器控制

STM32基础外设开发&#xff1a;从点灯到传感器控制 一、前言 本篇文章总结STM32F10x系列基础外设开发实例&#xff0c;涵盖GPIO控制、按键检测、传感器应用等。所有代码基于标准库开发&#xff0c;适合STM32初学者参考。 二、硬件准备 STM32F10x系列开发板LED模块有源蜂鸣器…

[特殊字符] 使用增量同步+MQ机制将用户数据同步到Elasticsearch

在开发用户搜索功能时&#xff0c;我们通常会将用户信息存储到 Elasticsearch&#xff08;简称 ES&#xff09; 中&#xff0c;以提高搜索效率。本篇文章将详细介绍我们是如何实现 MySQL 到 Elasticsearch 的增量同步&#xff0c;以及如何通过 MQ 消息队列实现用户信息实时更新…

MyBatis缓存机制全解析

在MyBatis中&#xff0c;缓存分为一级缓存和二级缓存&#xff0c;它们的主要目的是减少数据库的访问次数&#xff0c;提高查询效率。下面简述这两种缓存的工作原理&#xff1a; 一、 一级缓存&#xff08;SqlSession级别的缓存&#xff09; 一级缓存是MyBatis默认开启的缓存机…

【短距离通信】【WiFi】WiFi7关键技术之4096-QAM、MRU

目录 3. 4096-QAM 3.1 4096-QAM 3.2 QAM 的阶数越高越好吗&#xff1f; 4. MRU 4.1 OFDMA 和 RU 4.2 MRU 资源分配 3. 4096-QAM 摘要 本章主要介绍了Wi-Fi 7引入的4096-QAM对数据传输速率的提升。 3.1 4096-QAM 对速率的提升 Wi-Fi 标准一直致力于提升数据传输速率&a…

【二刷力扣】【力扣热题100】今天的题目是:283.移动零

题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输…

机器学习中的多GPU训练模式

文章目录 一、数据并行&#xff08;Data Parallelism&#xff09;二、模型并行&#xff08;Model Parallelism&#xff09;1. 模型并行2. 张量并行&#xff08;Tensor Parallelism&#xff09; 三、流水线并行&#xff08;Pipeline Parallelism&#xff09;四、混合并行&#x…

《JavaScript 性能优化:从原理到实战的全面指南》

《JavaScript 性能优化&#xff1a;从原理到实战的全面指南》 一、JavaScript 性能优化基础理论 在深入探讨 JavaScript 性能优化技术之前&#xff0c;我们需要明白JavaScript 的执行机制和性能瓶颈产生的根本原因。JavaScript 是一种单线程、非阻塞的脚本语言&#xff0c;其…