Linux Kernel 4.4 `printk` 源码分析与使用详解

Linux Kernel 4.4printk源码分析与使用详解

  • 参考资料:百问网 - UART子系统
  • Kernel版本:Linux 4.4.154
  • 开发板:Firefly-RK3288
  • 关键文件kernel/printk/printk.c,include/linux/kern_levels.h

一、printk 的基本使用与打印级别

调试内核驱动最简单的方法就是使用printk函数。它与用户空间的printf格式类似,但多了一个**日志级别(Log Level)**的概念。

1.1 printk 使用示例

在驱动程序中,我们通常这样调用:

printk("This is an example\n");// 未指定级别,使用默认级别printk(KERN_WARNING"This is an example\n");// 指定为 WARNING 级别

底层原理
printk实际上支持在字符串头部加入\001n格式的字符来指定级别(n 为 0~7)。KERN_WARNING等宏本质上就是这个字符串前缀。

/* include/linux/kern_levels.h */#defineKERN_SOH"\001"/* ASCII Start Of Header */#defineKERN_WARNINGKERN_SOH"4"/* warning conditions */

1.2 打印级别定义

Linux 内核定义了 8 个打印级别(数值越小,优先级越高):

宏名称字符串说明
KERN_EMERG“0”系统不可用 (System is unusable)
KERN_ALERT“1”必须立即采取行动 (Action must be taken immediately)
KERN_CRIT“2”临界条件 (Critical conditions)
KERN_ERR“3”错误条件 (Error conditions)
KERN_WARNING“4”警告条件 (Warning conditions)
KERN_NOTICE“5”正常但重要的情况
KERN_INFO“6”信息性消息
KERN_DEBUG“7”调试级别消息

1.3 控制台打印控制(核心宏)

include/linux/kernel.h(实际上数据定义在kernel/printk/printk.c) 中,有四个核心宏决定了消息是否会打印到硬件控制台上。

#defineconsole_loglevel(console_printk[0])#definedefault_message_loglevel(console_printk[1])#defineminimum_console_loglevel(console_printk[2])#definedefault_console_loglevel(console_printk[3])

它们对应的数组定义如下:

/* kernel/printk/printk.c */intconsole_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 */};

详细解释:

  1. console_loglevel(当前控制台级别)

    • 作用:这是决定打印与否的“门槛”。
    • 规则:只有消息级别 < console_loglevel时,消息才会显示在终端上。
    • 示例:若设为 4,则只有 0~3 级的消息会显示。
  2. default_message_loglevel(默认消息级别)

    • 作用:当printk("msg")没有指定级别宏时,赋予该消息的默认级别。
    • 注意:通常默认为KERN_WARNING(4)。
  3. minimum_console_loglevel

    • 作用:安全底线,防止用户把console_loglevel设得太低导致连 Panic 都看不见。通常为 1。
  4. default_console_loglevel

    • 作用:系统启动时的初始console_loglevel

1.4 在用户空间修改打印级别

我们可以通过/proc文件系统动态查看和修改这 4 个值,无需重新编译内核。

查看当前值:

cat/proc/sys/kernel/printk# 输出示例: 4 4 1 7

这意味着当前只有级别 0~3 的消息会打印。

修改示例:打开所有调试信息
如果你想看到KERN_DEBUG信息,需要将第一个值设为 8(因为 7 < 8):

echo8>/proc/sys/kernel/printk# 或者一次性设置4个值echo"8 4 1 7">/proc/sys/kernel/printk

二、printk 的整体架构与数据流

理解printk最好的方式是跟踪数据流向。


(图源:百问网)

我们可以将上图分为四个阶段:

第一阶段:源头(驱动层)

  • 驱动调用printk
  • 如果未指定级别,内核自动补上default_message_loglevel

第二阶段:缓存(内核 Buffer 层)

  • 格式化:内核将消息封装结构体(包含长度.len、级别.level、内容"abc")。
  • 存入log_buf:这是全局环形缓冲区。
  • 关键点:无论级别高低,所有printk的内容都会存入log_buf。这也是为什么dmesg命令能看到所有历史日志的原因。

第三阶段:分发与过滤(Console 驱动层)

  • 数据从log_buf取出。
  • 过滤判断:在此处进行if (level < console_loglevel)的判断。
  • 如果不满足条件,流程终止(只存不打)。
  • 如果满足条件,调用具体驱动的write函数。

第四阶段:物理输出(硬件层)

  • Console 驱动:如ttyS0(串口) 或tty0(屏幕)。
  • 调用底层的 UART 寄存器操作将字符发送出去。

三、Kernel 4.4 源码深度剖析

让我们深入kernel/printk/printk.c看看这一切是如何实现的。

3.1 入口函数printk

/* kernel/printk/printk.c */asmlinkage __visibleintprintk(constchar*fmt,...){printk_func_tvprintk_func;va_list args;intr;va_start(args,fmt);// 获取当前CPU的打印函数指针vprintk_func=this_cpu_read(printk_func);r=vprintk_func(fmt,args);va_end(args);returnr;}EXPORT_SYMBOL(printk);

3.2 为什么使用函数指针vprintk_func

这里涉及到一个设计细节:防止 NMI(不可屏蔽中断)死锁

  • 默认情况下,printk_func指向vprintk_default
  • 场景:如果系统正在打印(持有锁)时发生 NMI,NMI 处理程序如果也调用printk,尝试获取同一个锁,就会导致死锁。
  • 机制:在 NMI 上下文中,内核会将该指针临时切换为vprintk_nmi,将数据写入临时的 NMI 安全缓冲区,从而避免死锁。

3.3 核心处理vprintk_emit

vprintk_default最终会调用vprintk_emit,这是核心大管家。

asmlinkageintvprintk_emit(intfacility,intlevel,...){// 1. 将数据写入 log_buf (Ring Buffer)// 无论级别如何,先存下来!printed_len+=log_store(0,2,LOG_PREFIX|LOG_NEWLINE,0,NULL,0,text,text_len);// 2. 尝试唤醒控制台驱动进行输出if(!in_sched){// 获取 console 信号量/锁if(console_trylock_for_printk())console_unlock();// 重点在这里}returnprinted_len;}

3.4 消费与输出console_unlock

数据存好了,现在要发给硬件。这个工作由console_unlock完成。它是一个循环,不断从log_buf取数据。

voidconsole_unlock(void){for(;;){// ... 从 log_buf 读取一条 msg ...// 格式化消息len+=msg_print_text(msg,...);// ... 释放 logbuf_lock (允许并发写 buffer) ...// 调用驱动发送数据// 注意:这里传入了 msg->levelcall_console_drivers(level,ext_text,ext_len,text,len);}}

3.5 真正的过滤逻辑call_console_drivers

在 Linux 4.4 中,打印级别的判断逻辑被封装在call_console_drivers内部。

staticvoidcall_console_drivers(intlevel,constchar*text,size_tlen,...){// --- 核心过滤逻辑 ---// 如果 消息级别 >= console_loglevel,且没有强制忽略级别// 则直接返回,不进行硬件操作。#ifndefCON_PSTOREif(level>=console_loglevel&&!ignore_loglevel)return;#endif// 遍历所有 console (如串口、屏幕)for_each_console(con){if(con->write)con->write(con,text,len);// 最终操作硬件}}

总结执行链:
printk->vprintk_emit->log_store(存入内存) ->console_unlock->call_console_drivers(检查级别) ->uart_console_write(硬件输出)。


四、硬件选择:内核怎么知道往哪打?

内核可能有多个输出设备(VGA、串口、网络),它通过console参数来决定。

4.1 命令行参数 (cmdline)

在系统启动日志或/proc/cmdline中可以看到:

cat/proc/cmdline# 输出: ... console=ttyFIQ0 ...

这表示内核选择名为ttyFIQ0的设备作为控制台。

4.2 参数来源

这些参数通常来自Device Tree (设备树)chosen节点,或者由U-Boot动态传递。

设备树示例:

/ { chosen { bootargs = "console=ttymxc1,115200"; }; };

U-Boot 环境变量示例 (IMX6ULL):

=>print consoleconsole=ttymxc0=>print mmcargsmmcargs=setenv bootargsconsole=${console},${baudrate}...

五、总结

  1. Printk 级别:由console_loglevel控制,数值越小优先级越高。
  2. 动态调试:通过/proc/sys/kernel/printk可以实时修改过滤规则。
  3. 核心机制
    • 先存:所有日志无条件存入log_buf(dmesg可见)。
    • 后显:只有满足level < console_loglevel的日志才会推送到串口。
  4. 源码路径:Linux 4.4 中,主要逻辑在kernel/printk/printk.c,过滤逻辑位于call_console_drivers

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

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

相关文章

融合DWA的青蒿素优化算法(Artemisinin Optimization Algorithm, AOA)求解无人机三维动态避障路径规划附MATLAB代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1f34…

【课程设计/毕业设计】基于python-cnn机器学习的罗马数据集训练识别

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

在Windows11下编译openjdk 21

在Windows11下编译openjdk 21 下载openjdk 20作为boot jdk&#xff0c;假设我下载解压后的路径如下 D:\Downloads\jdk-20.0.2_windows-x64_bin\jdk-20.0.2首先下载Cygwin&#xff0c;因为这是在Windows中模拟UNIX&#xff0c;在安装程序界面选择要安装的包 autoconf make zip u…

5G时代下联邦学习在AI原生应用中的新机遇

5G联邦学习&#xff1a;AI原生应用的下一个爆发点 一、引言&#xff1a;AI原生应用的“数据困局”与破局之道 清晨7点&#xff0c;自动驾驶汽车在早高峰的车流中平稳行驶&#xff0c;它通过路侧单元&#xff08;RSU&#xff09;实时获取前方施工路段的临时交通灯信息&#xff0…

【四旋翼控制】基于6自由度四旋翼跟踪轨迹(利用LQR整体动作设定点控制,姿态控制和PD路径跟踪控制器Matlab仿真)

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1f34…

牙齿拥挤数据集3206张yolo

牙齿拥挤数据集3206张VOCYOLO格式 数据集格式&#xff1a;VOC格式YOLO格式 压缩包内含&#xff1a;3个文件夹&#xff0c;分别存储图片、xml、txt文件 JPEGImages文件夹中jpg图片总计&#xff1a;3206 Annotations文件夹中xml文件总计&#xff1a;3206 labels文件夹中txt文件总…

Microsoft 开发的关系型数据库管理系统(RDBMS)

SQL Server 简介 SQL Server 是由 Microsoft 开发的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;支持企业级数据管理、分析和应用开发。其核心功能包括数据存储、事务处理、商业智能&#xff08;BI&#xff09;和高可用性解决方案。 SQL Server 版本 企业…

Java进阶知识-反射

获取Class对象 有三种方式获取Class对象&#xff1a;根据类的完整包名获取Class Class clazz Class.forName(“com.example.xjp.demo.reflect.PersonInfo”);根据类名直接获取Class Class clazz PersonInfo.class;根据实例类的对象获取Class PersonInfo personInfo new Pers…

Gemini认证疑难解答会

Gemini认证疑难解答会通常是指围绕Google推出的Gemini大模型系列在使用、集成或开发过程中遇到的技术问题进行讨论与解决的会议或交流活动。这类会议可能由开发者社区、企业技术团队或Google官方组织&#xff0c;旨在帮助用户更好地理解Gemini API的认证机制、权限配置、访问控…

Django 视图基础

Django 视图基础Django 视图是处理用户请求并返回响应的核心组件。视图可以是函数或类&#xff0c;通常定义在 views.py 文件中。函数视图示例&#xff1a;from django.http import HttpResponsedef hello_world(request):return HttpResponse("Hello, World!")类视图…

Java 进阶:异常影响性能吗?

Java 进阶异常影响性能吗 catch 中不做任何事情catch 中输出异常到日志catch 中获取异常栈总结 Java 进阶&#xff1a;异常影响性能吗&#xff1f; 曾经在给一个业务系统增加限流功能&#xff0c;使用的限流组件在流量超过阈值时&#xff0c;会直接抛异常&#xff0c;异常导…

【Qt改变虚拟键盘的大小】

默认情况下qtvirtualkeyboard占据了半个屏幕 可以通过修改源码的方式来修改其大小。 1.找到desktopinputpanel.cpp,参考路径 2.修改show函数注释的代码为源文件原来的代码。 3.键盘样式修改需要修改对应的qml文件&#xff0c;路径为qtvirtualkeyboard/src/virtualkeyboard/cont…

java进阶训练营 极客,关于架构极客大学java进阶训练营

C语言中&#xff0c; 数组[2]属于结构数据类型。一个数组能够合成为多个数组元素&#xff0c;这些数组元素能够是根本数据类型或是构造类型。因而按数组元素的类型不同&#xff0c;数组又可分为数值数组、字符数组、指针数组、构造数组等各种类别。 对于可变长数组(VLA)的问题&…

Java进阶整理

对于一个程序员不能只是停留在满足平常的业务开发的水平&#xff0c;所以今天来整理一下Java的进阶知识。 通过以下几个方面来讲一讲Java的进阶知识&#xff1a; Jvm Jvm结构类加载对象的分配过程 、对象存储布局Java的内存模型、GC&#xff08;MinorGC&#xff08;新生代&a…

java头歌-数组进阶

第一关 public static void main(String[] args) {//动态构建arr1int[] arr1 new int[3];Scanner sc new Scanner(System.in);for(int i 0 ; i< arr1.length ; i){arr1[i] sc.nextInt();}/********** Begin **********///创建数组arr2int[] arr2 new int[3];for (int i…

java进阶知识点

java回收机制 浅谈java中的反射 依赖注入的简单理解 通过接口的引用和构造方法的表达&#xff0c;将一些事情整好了反过来传给需要用到的地方~ 这样做得好处&#xff1a;做到了单一职责&#xff0c;并且提高了复用性&#xff0c;解耦了之后&#xff0c;任你如何实现&#xf…

破解空间困局:看紧凑型ARM工控机如何一机多能

在智能工厂的角落、自动化产线的缝隙、或是移动设备的内部&#xff0c;工程师们常常面临一个经典难题&#xff1a;空间极其有限&#xff0c;但需要连接和控制的设备却一点不少。 摄像头、传感器、PLC、扫码枪、显示屏、机械臂……每一个都需要一个“对话”的接口。传统的工控机…

Java基础进阶-水仙花数

/* 功能&#xff1a;求水仙花数&#xff0c;打印并统计总个数。 思路&#xff1a; 水仙花数是定义范围100-999&#xff0c;满足每个位上的数子的3次方相加和等于这个数 第一步&#xff1a;循环遍历数据范围 第二步&#xff1b;取出当前数字的个位&#xff0c;十位&#xff0c;百…

Java进阶教程(二)代码块

Java进阶教程&#xff08;二&#xff09; 代码块 构造代码块&#xff1a;给所有的对象进行统一的初始化。对象一建立就运行并且优先于构造函数。 静态代码块&#xff1a;随着类的加载而加载。只执行一次&#xff0c;用于给类进行初始化。public class Demo {public static void…

提升Python AI模型训练速度:从入门到进阶的实战优化方案

你在训练AI模型时&#xff0c;最头疼的莫过于“等了几小时甚至几天&#xff0c;模型还没训练完”——不管是图片分类、文本分析还是更复杂的深度学习模型&#xff0c;训练速度直接影响开发效率。 一、硬件层面&#xff1a;先把“基础算力”用到位&#xff08;性价比最高的提速方…