LWN: printk()的难点以及解决方案

printk函数分析 - 中断关闭原理探究 但是,如果中断被关闭了,它就会尝试获取锁,并立即释放该锁。这样的行为导致了一个问题:如果printk函数被调用时中断是开启的,那么在函数执行过程中,由于中断被关闭了,所以可能会导致重要的中断处理被延迟。总结一下,printk函数在内部调用vprintk函数,在vprintk函数中会判断当前中断是否被关闭,如果中断被关闭,则尝试获取锁并立即释放该锁。可以看到,在printk函数中,它实际上是调用了另外一个函数vprintk,这个函数才是真正的输出函数。在printk函数中,它的参数为。 阅读详情
640
点击上方蓝色“Linux News搬运工”关注我们~

Why printk() is so complicated (and how to fix it)

By Jonathan Corbet


LPC

内核的printk()函数在普通人想象中应该是个非常简单的函数,只要处理好字符串格式化然后输出到kernel log里就好。其实这里隐藏着非常多的复杂问题,28年过去了,kernel开发者对printk()仍然非常不满意。在2019 Linux Plumbers Conference会议上,John Ogness介绍了printk()的实现复杂在什么地方,以及近期的相关工作。

这里的核心问题是kernel代码必须要能在任何上下文(context)都可以调用printk()。在atomic context调用的话需要确保printk()不能导致阻塞,而在non-maskable interrupts (NMIs)上下文调用的话甚至连spinlock也不能用了。同时,系统出错时printk()的输出内容非常重要,开发者不愿意丢掉任何一行信息,哪怕系统就要crash或者hang住了。这些信息要在console设备上打印出来,通常是一个串口,或者是经过显卡显示在屏幕上,或者通过网络连接送出来。此外,printk()不应该干扰系统的正常执行过程。

他总结说,printk()看起来简单并且到处都在用,而它的底层实现其实跟系统的方方面面都搅在一起。

The path to the present

Ogness介绍了printk()的发展史,可以参见他的ppt(https://www.linuxplumbersconf.org/event/4/contributions/290/attachments/276/463/lpc2019_jogness_printk.pdf)。第一版kernel v0.0.1发布时就包含了printk(),当时这个函数是同步行为(synchronous),会直接把信息利用一些汇编代码写入TTY端口。这种行为很可靠,不过无法扩展,今后kernel开始支持多CPU之后就必须要更改这里的行为了。

内核 0.99.7a版本里就增加了console registration(注册机制)。在0.99.13k版本里增加了“log level”设置。在2.4.0里面增加了bust_spinlocks()机制,用来避免系统crash以至于无法正常工作的时候还要进行不必要的等待spinlock操作。从2.4.10开始,printk()也支持异步(asynchronous)工作模式了。2.6.24版本之前,printk()时不时会导致偶发的特别高的延迟,在这个版本里面大家会在latency tracer里面忽略printk(),避免干扰人们的分析。3.4版本里增加了structured logging,sequence numbers,以及/dev/kmsg接口。4.10里面增加了"safe buffers"机制,用来在NMI context上下文来做输出。在4.15版本里,修复了一个bug可能导致CPU不停地输出信息。在5.0版本里,加入了caller identifier(调用者标记)功能。

也就是说这么多年来printk()一直在持续改进,不过仍然有很多遗留问题。其中之一就是关于用来保护ring buffer的raw spinlock,它没法在NMI上下文调用,因此printk()必须要先输出到不依赖lock的safe buffer里。这样会导致message最终被copy到真正的ring buffer的时候更新的timestamp不精确,也可能会导致message丢失,或者导致CPU异常offline的时候buffer没有被刷出去。

此外console驱动这边也有麻烦,因为它不仅很慢,并且还是在关中断模式下调用的。大多数console device设计时都没有考虑过kernel panic的场景,在这种最需要它的场景下表现得不够可靠。

其他还有个问题是printk()对各个级别的log一视同仁。某些频繁输出的信息如果被错误设置成urgent级别,可能会导致latency问题,从而让大家会调整level来导致丢失其他的urgent信息。虽然我们修复了一个CPU被卡住持续输出log的bug,不过最后一个CPU来接管log输出的时候也可能会被大量工作塞满。这时可能每个printk()调用都会费不少时间。而bust_spinlocks()机制其实就是忽略所有lock,寄希望于系统仍能正常工作。他觉得应该还有更好的方案解决这个问题。

The better way

Ogness认为过去多年printk()所面临的这些问题现在已经聚焦在了“非侵入性”(Non-interference,不干扰系统运行)和“可靠性”这两方面的矛盾了。大家已经明白了没法在同一个地方满足这两方面的需求。那么一个比较好的方案就是把它们分开。非侵入性的实现,要求让printk()变得可以被抢占,ring buffer要在各个context下都可靠,还有把console处理移到专用的kernel thread上去。而可靠性要求,则需要提供一个synchronous channel来放重要的信息,以及一个“atomic console"概念,还有要着重处理"emergency messages"紧急消息。

两个目标都依赖printk()的ring buffer。这个buffer有多个同时进行的reader,仅有一个writer。这个buffer在一片连续内存里,通过一个特别的可以在一个CPU上获取多次的spinlock("CPU lock")来保护着。这个锁他觉得更像是以前旧版本kernel里非常著名的那个big kernel lock。

printk()开发时也按照kernel开发里面比较好的工作模式来做,首先创建了一个新的ring buffer来解决当前方案里的问题。这个ring buffer是完全不用锁保护的,支持多个reader和writer,在任意上下文进行访问。metadata则会通过一个单独的描述机制来记录,包括记录时间戳以及message的序列号等。这个ring buffer有很多优势,不过也非常复杂,使用了至少9对memory-barrier操作,很难写好相关的文档,也很难review,他也没觉得这里实现的支持多个writer(导致复杂性大增)有什么现实需求。

新增了每个console都有一个独立的kernel thread,用来把printk()调用者和console相关处理工作分隔开。每个console现在都可以按自己的速度尽快打印,每个都有自己独立的log level设置。这样就把console的责任简化了很多,不过还是有一些lock相关的问题,以及大家有点怀疑这种基于线程的实现方式是否能保证任何情况都把message输出出去。不过Ogness提醒说,可靠性是靠其它机制保证的,这里每个console的独立线程是用来改善非侵入性(non-interference)需求的机制。

对于可靠性,他的计划是增加一个"atomic console"概念。支持这个功能的console都会有一个write_atomic()功能函数,可以在任何上下文调用都不会出错。这个函数内部实现是完全同步的操作,也就是说会大大拖慢系统,因此只有emergency(紧急)信息才应该用它。不过好处就是不再需要bust_spinlocks(),也不需要依赖oops_in_progress这个全局变量了。

这里的难点在于在console驱动里面正确实现write_atomic()。他基于8250 UART实现了一个console驱动,工作量不小。肯定会有不少系统上完全没有atomic-console功能,这样就需要其他一些方案了,例如创建一个特殊的console来写入一块内存区域,或者试着在atomic context上下文之外才进行synchronous打印,或者干脆就回退到原来的方案进行打印。

之所要使用atomic console,主要是为了能正确处理“emergency message”。这里最大的问题就是要判断哪些消息是重要信息。log level有点算是一个灰色地带,不是一个可靠的翻案。还有其他一些情况下printk()的输出是非常重要的,正确的实现方法应该是跟BUG()等这些函数关联起来。

Ogness指出这个工作在今年2月份开始,目前的版本是8月份发布的。上面提到的绝大多数功能都已经实现出来了,开发者们可以上手把玩一下了。

Further discussion

后来的一个session,LWN编辑没能参加。Ogness发出来的总结(https://lwn.net/ml/linux-kernel/87k1acz5rx.fsf@linutronix.de/)里面包含了大家达成一致的一些观点。他也感谢大家参与这个会议,认为“省下了需要用来读写邮件的几百个小时”。

从上述总结来看,后面会用Petr Mladek实现的另一种ring buffer方案,这个ring buffer实现更加简单,review也更容易做。Ogness把他的工作也移植到这个ring buffer上,证明是可行的。而每个console独立的内核线程会继续使用。

这里提到的“emergency message"概念,最后被“emergency state”观点取代了,它可以反应整个系统的状态。当kernel在这个状态市,所有信息都会通过尽可能地使用write_atomic()函数来输出出来。CPU lock会继续使用,不过目的变成了当系统在emergency state状态时,同步(synchronize)所有console线程。

还有其他一些改动,包括增加了pr_flush()函数,用来等待直到所有信息都被输出给console了。目前修改后的patch还没有发出来,不过会很快了。

[Your editor thanks the Linux Foundation, LWN's travel sponsor, for supporting his travel to this event.]

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

极度欢迎将文章分享到朋友圈 
热烈欢迎转载以及基于现有协议修改再创作~

长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~

640?wx_fmt=jpeg

linux内核调试和性能优化 Linux内核调试和性能优化 阅读详情

相关推荐

Linux之“死”

人固有一死或重于泰山或轻于鸿毛,死法不一殊途同归,留下的是后人的精神瞻仰。linux系统在某些异常情况产生之后会选择“死去”,来看下它是如何死去的。 linux version:4.14.224 arch:mips file:arch\mips\kernel\traps.c traps.c有个die函数: void __noreturn die(const char *str, struct pt_regs *regs) { static int die_counter; //die函数

Suvine的专栏 1077

printk打印消息机制

2.1.4  printk打印消息机制 在内核中,函数printk将消息打印到环形缓冲区__log_buf中,并将消息传给控制台进行显示。控制台驱动程序根据控制台的日志级别显示日志消息。 应用程序通过系统调用sys_syslog管理环形缓冲区__log_buf,它可以读取数据、清除缓冲区、设置日志级别、开/关控制台等。 当系统调用sys_syslog从环形缓冲区__log_buf读取数据时,

wxie的Linux人生 1621

printk问题

环形缓冲区是Linux内核的核心部分。驱动程序,子系统和很多通用功能用printk()共享错误和普通消息。输入dmesg或sudo dmesg在终端中查看其内容。 John Ogness提供了长达57分钟的视频,标题为“为什么printk()如此复杂? ” (https://linuxreviews.org/Why_is_printk()_so_complicated%3F),详细介绍了Linux内核的环形缓冲区的历史,自1991年Linux 0.01以来,2019 Linux会议上,他指出了print

地推 407

LWN:关于printk()的讨论!

关注了就能看到更多这么棒的文章哦~A discussion on printk()By Jake EdgeOctober 4, 2022LPCDeepL assisted translationhttps://lwn.net/Articles/909980/出于各种原因,内核的打印函数 printk() 多年来一直有许多与之相关的改进工作。printk() 的一个顽固问题是它带来的 latency...

Linux News搬运工 478

LWN:对print()输出消息生成索引列表!

关注了就能看到更多这么棒的文章哦~printk() indexingBy Jonathan CorbetMay 27, 2021DeepL assisted translationhttp...

Linux News搬运工 247

LWN:实时抢占真的快要完工了!

关注了就能看到更多这么棒的文章哦~The real realtime preemption end gameBy Jonathan CorbetNovember 16, 2023LPCChatGPT translationhttps://lwn.net/Articles/951337/Linux 实时性支持的加入是一个漫长的故事;它首次在 2004 年的 LWN 文章中亮相。在很长一段时间里,似乎...

Linux News搬运工 914

printk 续行问题

最近在代码工程升级kernel版本后发现 代码中使用for循环dump 一些值的时候printk(“0x%x ”,temp_data)不能续行打印,导致每个没有换行\n结束的printk在打印时自动换行。 查看后发现kernel4.9之后printk续行打印需要强制加上KERN_CONT flag. 参考 https://lwn.net/Articles/732420/有详细介绍。 加完KERN_CONT后 续行问题是解决了,但是发现了一些奇怪的现象。 example1: printk("txt..

u014044624的博客 2450

LWN:关于realtime patch的一次问答!

关注了就能看到更多这么棒的文章哦~A Q&A about the realtime patchesBy Jake EdgeJuly 18, 2023EOSSChatGPT assisted translationhttps://lwn.net/Articles/938236/在 2023 年实时 Linux 峰会上,Thomas Gleixner 回答了关于内核实时特性、其现状以及 Rea...

Linux News搬运工 1164

LWN:利用BPF来检查kernel数据结构内容!

关注了就能看到更多这么棒的文章哦~Dumping kernel data structures with BPFByJonathan CorbetApril 27, 2020原文来自:...

Linux News搬运工 791

LWN:要提供更好的 OOM 调试工具!

关注了就能看到更多这么棒的文章哦~Better tools for out-of-memory debuggingBy Jonathan CorbetMay 11, 2022LSFMMDeepL assisted translationhttps://lwn.net/Articles/894546/Out-of-memory(OOM)的情况是用户、系统管理员以及内核开发人...

Linux News搬运工 398

告别printk:用Linux内核Tracepoint给你的驱动调试换个活法(附ext4实战代码)

本文探讨了Linux内核调试中printk的局限性,并介绍了Tracepoint技术的优势。通过ext4文件系统的实战代码,展示了Tracepoint如何实现高性能、低开销的内核调试,包括动态监控、精准过滤和用户空间工具trace-cmd的使用。文章还提供了自定义Tracepoint的进阶技巧和性能对比数据,帮助开发者提升调试效率。

weixin_42524945的博客 269

LWN:5.15 合并周期后半部分!

关注了就能看到更多这么棒的文章哦~The rest of the 5.15 merge windowBy Jonathan CorbetSeptember 13, 2021DeepL as...

Linux News搬运工 448

RV1109与hi3861L SD卡槽WiFi驱动移植实战:内核适配与调试技巧

本文详细介绍了将海思hi3861L WiFi模块移植到瑞芯微RV1109平台的实战经验,重点解决SD卡槽SDIO协议适配、内核版本差异(4.9到4.19)带来的结构体变更、定时器接口更新等技术难题,并分享设备树配置、中断处理及性能调优等关键技巧,为嵌入式WiFi驱动移植提供实用参考。

weixin_30399155的博客 403

linux内核2.6中设备模块编程的解决方法

近日尝试linux内核设备模块编程,使用《linux内核编译》一书,但在新版的2.6内核中,例子程序无法编译通过,在网上搜寻了很久,都没有找到一个完整的解决方案,最后终于在网站http://lwn.net/获得帮助,现总结如下init和clear命名方式改变,makefile改变用一个hello world程序说明原版如下:#include #include #if C

rainman1981的专栏 2062

使用TRACE_EVENT宏添加Tracepoint(1/3部分)

其他的追踪点的头文件必须使用除"sched"和"_TRACE_SCHED_H"之外,其他的内容。TP_fast_assign宏中的代码是普通的C语言代码。其中,"#define TRACE_SYSTEM sched"起到了这样的效果,TRACE_SYSTEM定义了头文件中的TRACE_EVENT()宏属于哪个组,同时也表明了TRACE_EVENT()宏中定义的追逐点属于"/sys/kernel/debug/tracing/events/"目录下的哪个子目录,这种分组决定了用户按组启用或者禁用追踪点。

生活需要深度 928

单片机开发者转型Linux:从裸机到操作系统的实战指南

嵌入式开发领域中,单片机与Linux系统代表了两种不同的技术范式。单片机开发以直接硬件控制和确定性执行为特点,而Linux操作系统则引入了进程调度、虚拟内存等现代计算机科学概念。理解Linux的系统架构(如内核空间/用户空间隔离、设备驱动模型)是开发复杂嵌入式系统的关键。在工业物联网和智能硬件快速发展的背景下,掌握Linux开发能力能显著提升设备的多任务处理、网络通信和安全性。通过构建交叉编译工具链、学习字符设备驱动开发等实践,开发者可将单片机积累的硬件知识转化为Linux环境下的底层控制优势。典型应用场景

weixin_34326429的博客 366

Rust在Linux内核中的应用与优化实践

Rust作为一种内存安全的系统编程语言,近年来在Linux内核开发中展现出显著的技术价值。其所有权模型和类型系统有效解决了传统C语言中的内存安全问题,特别适用于驱动开发和内核模块编写。通过双堆内存管理和智能指针封装,Rust与内核现有基础设施实现了无缝集成,在GPIO、I2C等子系统中已得到验证。工程实践中,Rust模块通常能减少40%代码量并消除内存安全漏洞,尽管可能带来约5%的上下文切换开销。随着谷歌等企业推动Rust在Binder驱动等核心组件的应用,其在内核中的占比正快速增长。当前技术焦点集中在稳定

weixin_30552811的博客 394

Linux 中断上半部与下半部机制简析

本文先整体说明Linux 中断为什么要分两半,给出中断处理全景;再逐一分析上半部(request_irq、返回值与 IRQF 标志、genirq 框架、设备树时代的 irq_domain 中断映射)与下半部的四大机制(softirq、tasklet、工作队列、中断线程化);随后梳理从 2.4 到 6.12 的关键版本演进节点(genirq、threaded IRQ、cmwq、irq_domain、WQ_BH、PREEMPT_RT 主线化);最后给出机制对比表、选型决策树与调试观测手段。

smallerxuan的博客 1万+
上一篇: LWN: 监控kernel内部的ABI
下一篇: LWN: 数据库开发者对Linux kernel的要求
LinuxNews搬运工
博客等级 码龄7年 419粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值