资讯动态

深入解析Pstore:崩溃现场持久化存储的原理与实现

发布时间:2026/10/26 14:55:03 来源:尧图企业网站定制
1. 为什么值得花时间啃 Pstore 这块硬骨头第一次翻到pstore这个目录的时候我其实是有点懵的。它在内核源码树里位置不算显眼fs/pstore/底下就那么几个文件代码量跟 ext4、xfs 这些动辄几万行的大家伙比起来简直是小巫见大巫。但真正在项目里踩过一次坑之后我才明白这块小代码承担的责任一点都不小——它管的是系统崩溃那一瞬间怎么把最后那点现场信息给留下来。Pstore全称 Persistent Storage直译过来就是持久化存储。名字听着朴素干的事却很关键在系统发生 panic、oops、硬件异常重启这类来不及做任何收尾的场景下把内核日志、崩溃现场、甚至一段自定义数据写进一块掉电不丢的存储介质里等系统重新起来之后再读出来。你可以把它理解成飞机的黑匣子——平时它安安静静待着不干活一旦出事它就是唯一能告诉你刚才到底发生了什么的东西。这个内容适合谁来啃我的判断是三类人。第一类是做嵌入式、车载、工业控制这类对稳定性要求极高的开发者设备跑在现场出了问题你不可能每次都抱着调试器去现场抓只能靠 pstore 留下的现场。第二类是做内核调试、故障分析的同学panic 之后串口没输出、日志没落盘的情况太常见了pstore 是兜底手段。第三类是想深入理解内核异常路径设计的爱好者pstore 的代码把在极端受限环境下还要保证可靠这件事演绎得很典型读它有举一反三的价值。这篇是系列的第二篇第一篇我们聊了 Pstore 是什么、能干什么、有哪些后端。这一篇往深里走专门讲原理它凭什么能在系统都快挂掉的时候还能写数据它的数据是怎么组织的前端和后端是怎么分工的为什么它要设计成现在这个样子我会尽量把每个为什么都掰开讲清楚因为这块东西如果只记结论换个平台、换个后端你又会懵。文中涉及的具体操作和参数我会基于常见实践给出可复现的方案同时明确标注哪些是通用做法、哪些需要结合你自己的平台调整。2. Pstore 的整体架构与设计思路拆解2.1 前端与后端分离一个刻意为之的分层Pstore 最核心的设计思想就是把记录什么和存到哪里彻底拆开。这个拆分不是随便分的而是被现实逼出来的。你想想崩溃现场能用的存储介质五花八门有的是内存里一块掉电不丢的区域比如各种 RAMoops 类的方案有的是板子上的 flash 分区有的是通过固件接口暴露出来的一小块存储还有的是 UEFI 变量区。这些介质的读写方式、擦除粒度、寿命特性完全不同。如果每接一种介质就要重写一遍怎么记录日志、怎么组织记录的逻辑那代码会烂成一锅粥。所以 Pstore 抽象出了两层。前端frontend负责记录这件事它定义了记录的类型dmesg、console、pmsg、ftrace 等、记录的格式、怎么把一条记录写进去、怎么读出来、怎么删除。后端backend负责存储这件事它只关心最原始的字节读写和擦除向上提供一组统一的回调接口。前端调用后端的回调后端不关心你写的是日志还是别的什么。这个分层带来的直接好处是新增一种存储介质你只需要实现后端的那几个回调read、write、erase 之类前端逻辑一行都不用动反过来新增一种记录类型也只需要在前端加后端无感。我在实际项目里换过一次后端介质从一种内存保留区换到 flash 分区前端代码确实没动只改了设备树里的描述和对应的后端驱动这个体验很能说明分层设计的价值。提示理解这个分层是读懂整个fs/pstore/目录的钥匙。pstore.h里那个struct pstore_info就是后端要实现的接口契约前端所有的操作最终都落到这个结构体里的函数指针上。2.2 记录类型的设计为什么不是简单写个文件很多人第一反应是崩溃了往存储里写个文本文件不就完了搞这么复杂干嘛这个想法在正常系统里成立但在崩溃路径上完全不成立。正常写文件要走 VFS、要经过文件系统、要分配块、要更新元数据、要保证一致性这一整套流程在 panic 上下文里根本跑不起来——中断可能已经关了调度器可能已经停了内存分配可能已经不可用了文件系统的锁可能还握在别的 CPU 手里。你在这个环境下调用文件系统接口大概率是二次崩溃或者直接死锁。所以 Pstore 的记录格式是极简的、自描述的、不依赖任何文件系统的。每条记录在存储介质里就是一段连续的字节头部带一个固定格式的描述结构记录类型、长度、校验信息都在里面。读取的时候按这个头部去解析不需要目录、不需要 inode、不需要任何外部索引。这种设计牺牲了灵活性比如你不能像文件一样随机改某一条的中间部分换来的是在极端环境下的可靠性。记录类型目前主要有这么几类我列个表对照着看更清楚记录类型典型用途数据来源特点dmesg内核日志内核 printk 环形缓冲区崩溃时把整个日志缓冲 dump 下来信息量最大console控制台输出控制台驱动只保留真正打印到控制台的部分比 dmesg 精简pmsg用户态消息用户空间通过接口写入应用层主动记录关键信息崩溃前可预埋ftrace函数跟踪ftrace 框架记录函数调用轨迹用于性能与流程分析自定义业务数据驱动或应用按需扩展格式自定这里有个细节值得说dmesg 和 console 看起来重复其实定位不同。dmesg 抓的是内核日志缓冲区里的全部内容包括那些因为日志级别没打印到控制台的console 抓的是真正输出到控制台设备的内容。崩溃时两者都存能互相印证有时候 dmesg 里有但 console 里没有的那几条恰恰是关键线索。2.3 环形存储与记录淘汰空间有限下的取舍存储介质给 Pstore 的空间通常很小几 KB 到几 MB 不等不可能无限往里写。所以 Pstore 采用的是环形或者说循环使用的策略空间写满了就淘汰最老的记录给新记录腾地方。这个策略背后有个很实际的判断崩溃现场分析最新的记录往往最有价值。系统崩溃前最后那几秒的日志比一小时前的日志重要得多。所以淘汰最老的、保留最新的是符合故障分析直觉的。但这里有个坑我得提前说淘汰是按记录粒度做的不是按字节。如果一条 dmesg 记录特别大比如日志缓冲区设得很大它可能一次就占掉大半个存储区把之前所有记录都挤掉。所以实际配置时存储区大小和日志缓冲区大小要匹配着来不能一个设得巨大另一个给得可怜否则要么浪费要么频繁淘汰。这个参数匹配的问题我在第 4 节会具体讲怎么算。2.4 为什么要在崩溃路径上做这么多保护Pstore 的代码里到处是防御性的写法加锁要小心、内存分配要用特殊标志、写操作要尽量短、失败要能优雅降级。这些不是过度设计每一条都对应着崩溃路径上的一个真实风险。比如内存分配正常路径用GFP_KERNEL可以睡眠等待但崩溃路径上调度器可能已经停了一睡眠就永远醒不过来。所以 Pstore 在崩溃写入路径上用的分配标志是那种不允许睡眠、不允许重试的类型宁可分配失败也不能卡住。再比如加锁崩溃时可能是在持有某把锁的上下文里触发的如果 Pstore 自己再用同一把锁或者有依赖关系的锁就会死锁。所以它的锁设计得非常克制临界区尽量短。这些保护措施单看每一条都很琐碎但合起来就构成了在系统濒死状态下还能可靠工作的能力。读这部分代码的时候我建议你带着如果这里出问题会怎样的思路去看会比单纯记流程收获大得多。3. 核心数据结构与关键机制解析3.1 pstore_info后端必须遵守的契约前面说了前端后端分离那这个分离面具体长什么样答案就是struct pstore_info。这个结构体是后端驱动必须填充的前端所有操作都通过它来发起。它里面最关键的几个成员我按重要性排一下。首先是name后端的名字注册后会出现在/sys/module/pstore/或者对应的 sysfs 节点下方便识别当前用的是哪个后端。然后是flags一堆标志位描述这个后端支持哪些能力比如是否支持压缩、是否是某种特殊类型。接着是核心的回调函数指针read用来从指定偏移读一段数据write用来往指定偏移写erase用来擦除一个范围。这几个是必须实现的缺一个后端就跑不起来。还有几个成员容易被忽略但很重要。bufsize表示后端单次读写能处理的最大块大小前端在组织数据时要按这个来分块超过就得拆。total_size是后端能提供的总空间。这两个值直接决定了 Pstore 能用多大空间、单条记录最大能多大。我在调试时就遇到过后端bufsize设得太小导致大日志被拆成很多小块、写入效率极低的情况后来把bufsize调大才顺畅。注意bufsize不是越大越好。它受限于后端介质的实际能力比如某些 flash 的擦除块大小、某些接口的单次传输上限。设得超过介质能力写入会失败或者被底层悄悄截断反而更难排查。设之前先查清楚你的介质规格。3.2 pstore_record一条记录的完整生命周期如果说pstore_info是后端的契约那struct pstore_record就是前端操作的基本单位。一条记录从产生到消亡大致经历这么几个阶段。创建阶段前端根据记录类型从对应的数据源把内容抓过来填进 record 结构。比如 dmesg 类型就是从内核日志缓冲区里 dump。这个阶段要确定记录的大小如果超过后端单次能写的量就要考虑压缩或者截断。写入阶段前端把 record 序列化成字节流调用后端的write回调按偏移写进存储。写入前会先检查空间够不够不够就触发淘汰逻辑把老记录删掉腾地方。这里有个顺序问题是先淘汰再写还是边写边淘汰实现上是先确保有足够空间再写避免写到一半发现空间不够导致记录残缺。读取阶段系统重启后前端遍历存储区按记录头部的描述结构解析出每条记录通过 sysfs 或者/dev/pstore/下的文件暴露给用户态。用户态读文件前端就去后端读对应偏移的数据返回。删除阶段用户态删除某条记录比如rm /dev/pstore/dmesg-xxx前端调用后端erase把对应区域擦掉标记为可复用。这个生命周期里最微妙的是写入阶段的空间检查 淘汰逻辑。因为淘汰本身也是写操作要擦除如果淘汰过程中又崩溃了状态可能不一致。Pstore 对此的处理是尽量让每条记录自包含、带校验读取时校验不过就跳过这样即使中途出问题也不会把整个存储区搞坏最多丢一条记录。这种单条记录失败不影响整体的设计思路在可靠性要求高的系统里很值得借鉴。3.3 压缩机制小空间里塞更多信息存储空间紧张日志又大怎么办压缩。Pstore 支持对记录做压缩用的是内核里现成的压缩算法常见的是 zlib 或者 lzo 这类具体取决于配置。压缩的收益很直接内核日志文本重复度高、可压缩性强压缩比经常能到 3 倍甚至更高。也就是说同样 64KB 的空间不压缩只能存 64KB 日志压缩后可能存下接近 200KB 的日志内容这在崩溃分析时差别巨大——多出来的那部分很可能就包含关键线索。但压缩也有代价。第一是 CPU 开销崩溃路径上做压缩会消耗时间如果压缩算法太重可能还没压完系统就彻底挂了。所以选算法时要权衡压缩比和速度通常选偏快的算法。第二是内存开销压缩需要额外的缓冲区崩溃路径上内存紧张这个缓冲区要提前准备好或者用固定的小块。第三是兼容性压缩后的数据读取时要解压如果解压算法在恢复后的系统里不可用数据就读不出来所以压缩算法要和内核配置匹配好。我在实际配置时的经验是如果存储空间确实很紧张比如只有几十 KB压缩基本是必开的如果空间比较宽裕几 MB可以不开压缩换取更简单的崩溃路径和更快的写入。这个取舍没有标准答案看你的场景更怕存不下还是更怕写不完。3.4 多后端共存与优先级一个系统里能不能同时挂多个 Pstore 后端可以。比如既有内存保留区又有 flash 分区两个都注册成后端。这时候前端会怎么选Pstore 支持给后端指定优先级写入时会优先用高优先级的后端。这个设计的意义在于不同介质的可靠性、容量、速度不同你可以让最可靠的那个优先承接崩溃现场其他的作为补充或者备份。比如内存保留区速度快、崩溃时写入成功率高但容量小flash 容量大但写入慢、崩溃时可能来不及。两个都挂上让内存区优先flash 兜底是个常见组合。不过多后端共存也带来复杂度空间是各自独立的淘汰也是各自进行的读的时候要分别遍历。调试多后端问题时先确认当前实际生效的是哪个后端看 sysfs 里的信息别对着一个没在用的后端调半天这是我踩过的坑。4. 实操过程从配置到验证的完整链路4.1 确认内核配置先把开关打开原理讲再多配置没开都是空谈。Pstore 相关的内核配置项分散在几个地方我按顺序列一下你对着menuconfig或者.config检查。首先是总开关在文件系统相关的配置里有个 Pstore 文件系统支持项必须选上否则fs/pstore/整个不参与编译。然后是各个后端的配置比如内存保留区类的后端、flash 类的后端按你实际用的介质选。再然后是记录类型的配置dmesg、console、pmsg、ftrace 这些用哪个开哪个全开也行但会占空间。最后如果要用压缩还得把对应的压缩算法配置打开。这里有个容易漏的点某些后端依赖特定的底层机制比如内存保留区依赖内核启动时预留内存的参数这个参数不在 Pstore 配置里而在启动参数或者设备树里。配置项开了但启动参数没给后端注册会失败日志里能看到明确的报错。我第一次配的时候就漏了这个对着后端注册失败的日志找了半天最后发现是启动参数没加。提示配置完之后用zcat /proc/config.gz | grep -i pstore如果内核开了 config 导出或者直接查.config确认相关项都是y或m。别凭记忆一定实际查一遍。4.2 后端空间大小的计算与设定这是最容易拍脑袋、也最容易出问题的一步。空间给多大我给个计算思路你套自己的参数。先估算单条记录的最大尺寸。以 dmesg 为例它的大小约等于内核日志缓冲区的大小log_buf_len参数决定。如果你的日志缓冲区设的是 128KB那一条 dmesg 记录压缩前就可能接近 128KB。再看压缩假设压缩比 3 倍压缩后约 43KB。那么存储区至少要能放下这一条压缩后的记录还得留出余量给其他类型console、pmsg 等。再考虑要保留几条历史记录。如果你希望崩溃后能看到最近 3 次崩溃的现场那空间至少是单条记录大小乘以 3 再留余量。把这些加起来就是存储区的下限。上限则受介质实际可用空间限制不能超过。举个具体的算例日志缓冲区 128KB压缩比按 3 算单条 dmesg 约 43KBconsole 记录假设 16KBpmsg 预留 8KB希望保留 2 份历史则总量约 (43168)×2 ≈ 134KB向上取整到 256KB 比较稳妥。这个 256KB 就是你要在启动参数或设备树里给后端配置的大小。反过来如果你给的空间只有 64KB而单条 dmesg 压缩后就要 43KB那基本只能存一条第二条一来就把第一条挤掉历史记录根本留不住。这种配置在排查偶发问题时很吃亏因为偶发问题往往需要对比多次崩溃的现场。4.3 挂载与基础验证确认它真的在工作配置好、启动起来之后第一件事是确认 Pstore 挂上了。Pstore 通过一个伪文件系统暴露给用户态需要挂载才能访问。挂载点通常选/sys/fs/pstore或者/dev/pstore具体看你的系统约定。挂载命令大致是这样不同发行版路径可能略有差异按实际调整mount -t pstore pstore /sys/fs/pstore挂载成功后ls一下挂载点正常情况下应该是空的还没产生记录。这时候可以做个主动测试往 pmsg 写一条消息看能不能读出来。pmsg 的接口在/dev/pmsg0这类设备节点上直接echo写入即可echo pstore test message /dev/pmsg0然后重新挂载或者刷新一下看挂载点下有没有出现对应的记录文件cat出来内容对不对。这一步能验证前端、后端、存储介质整条链路是通的。如果写进去读不出来问题可能出在后端写入失败、或者读取解析失败得看内核日志里的报错。4.4 模拟崩溃验证真刀真枪测一次pmsg 测试通过只说明正常路径通了Pstore 的真正价值在崩溃路径。所以必须做一次真实的崩溃测试看崩溃时 dmesg 能不能落盘。测试方法要谨慎因为会真的让系统挂掉。常见做法是触发一次内核 panic比如通过 sysrq 接口。操作前务必确认当前系统是可以随时重启的测试环境重要数据已保存而且你有办法在重启后重新进入系统。生产设备上千万别这么干。触发 panic 的大致流程是先确保 sysrq 功能开启/proc/sys/kernel/sysrq的值允许触发然后往/proc/sysrq-trigger写入触发 panic 的指令。系统会立即崩溃重启。重启后重新挂载 Pstore看挂载点下有没有出现 dmesg 记录cat出来看内容是不是崩溃前的那段日志。这个测试有几个观察点。第一记录有没有产生——没有的话说明崩溃路径写入失败可能是后端在 panic 上下文里不可用或者空间不够。第二记录内容完不完整——如果只有开头一小段可能是写入被截断检查bufsize和空间。第三记录时间戳对不对——能帮你确认这条记录确实是这次崩溃产生的不是上次残留的。我实测下来内存保留区类的后端在 panic 场景下成功率比较高因为它本质就是往一块保留内存里写不涉及复杂的介质操作flash 类后端成功率受介质状态影响大一些如果崩溃时正好在擦除 flash可能写不进去。这个差异在做方案选型时要有数。5. 常见问题与排查技巧实录5.1 挂载后看不到任何记录这是最高频的问题。现象是挂载成功但崩溃重启后挂载点空空如也。排查要按链路从后往前捋。先确认后端有没有注册成功。看内核日志里有没有后端注册相关的信息注册失败会有明确报错常见原因是启动参数没给、预留内存冲突、或者介质初始化失败。后端没注册成功前端就是无源之水写什么都没地方去。再确认崩溃时写入有没有发生。有些后端在正常运行时能写pmsg 测试通过但 panic 上下文里写失败因为 panic 时某些资源不可用了。这种情况要看后端实现是否针对 panic 路径做了特殊处理比如是否用了不允许睡眠的分配、是否避免了可能死锁的锁。最后确认读取解析有没有问题。数据可能写进去了但读取时校验不过被跳过。可以看内核日志里有没有校验失败的提示或者用底层工具直接 dump 存储区原始字节看头部结构对不对。5.2 记录被频繁覆盖历史留不住现象是每次只能看到最新一条之前的都没了。根因基本是空间不够或者淘汰策略太激进。先算一下单条记录实际占多大。把挂载点下记录文件的大小看一下注意压缩后的实际占用可能和文件显示大小不同要看后端报告的实际使用量。如果单条就接近或超过总空间那必然只能存一条。解决办法要么加大空间要么减小单条记录比如调小日志缓冲区、提高压缩比。还要注意淘汰是按记录粒度做的。如果某类记录特别大它会一次性挤掉很多小记录。可以调整各类记录的优先级或者大小配比让关键类型比如 dmesg有稳定空间非关键类型比如 ftrace限制大小。5.3 压缩开启后读取乱码或失败压缩相关的故障排查思路和普通记录不同。首先确认写入端和读取端的压缩算法配置一致——写入时用什么算法压的读取时就得用什么算法解配置不匹配必然乱码。其次确认压缩缓冲区够大压缩后数据如果超过后端bufsize写入会被拆块读取时拼接逻辑如果处理不好就会出错。还有一种隐蔽情况压缩算法在崩溃路径上执行时间过长导致写入没完成系统就彻底挂了留下半条残缺记录。这种记录读取时校验不过会被跳过表现为记录丢了。如果怀疑是这个原因可以临时关掉压缩测试一次看记录能不能完整落盘以此定位。5.4 多后端场景下行为不符合预期挂了多个后端但实际行为和你想的不一样。先确认当前生效的是哪个——看 sysfs 里各后端的状态和优先级。有可能你以为在用 A实际因为优先级配置在用 B。再确认各后端的空间是独立的A 写满了不会自动用 B 的空间除非前端有跨后端的逻辑通常没有各管各的。如果确实需要多后端协同要明确设计意图是主备关系一个挂了用另一个还是分工关系不同类型记录去不同后端。Pstore 原生更偏向多个独立后端各自工作复杂的协同逻辑需要自己在应用层或者驱动层补别指望框架自动帮你做。5.5 常见问题速查表现象可能原因排查方向处理建议挂载点为空后端未注册查内核日志注册信息补启动参数、检查介质崩溃后无记录panic 路径写入失败查后端是否支持崩溃上下文换后端或改实现只有最新一条空间不足或单条过大算单条实际占用加空间或减记录大小记录乱码压缩算法不匹配核对读写两端配置统一压缩算法记录残缺写入被截断查 bufsize 与空间调大 bufsize 或分块多后端行为异常优先级或分工不清查 sysfs 状态明确主备或分工设计注意排查这类问题内核日志永远是你的第一手资料。Pstore 在注册、写入、读取各环节都有日志输出养成先看日志再动手的习惯能省掉大量瞎猜的时间。6. 我在实际项目里攒下的几点体会Pstore 这块东西看文档和真上手之间隔着一层。我把自己踩过的、觉得最有价值的几点单独拎出来说说。第一别把它当普通文件系统用。它的接口长得像文件但语义完全不同——没有随机写、没有目录、没有权限模型记录是整体产生整体删除的。用文件系统的思维去用它会在为什么不能改这一条这类问题上卡住。把它当成一个只追加、按类型分、满了淘汰最老的环形日志区理解就顺了。第二空间规划要在项目早期做。我见过不少项目是出了问题才想起来加 Pstore结果发现预留内存不够、flash 分区没规划、启动参数改不动处处受限。如果项目一开始就预判到需要崩溃现场留存提前把空间和参数留出来后面会轻松很多。第三崩溃路径的可靠性要实测不能靠推理。代码看着没问题实际 panic 时就是写不进去这种情况太常见了。每换一种介质、每改一次配置都老老实实做一次真实崩溃测试看记录到底落没落盘、内容完不完整。测试环境里多崩几次比现场出问题时抓瞎强得多。第四压缩是把双刃剑按场景选。空间紧张就开空间宽裕就关别为了看起来更专业无脑开压缩。开了之后一定要验证崩溃路径下压缩能不能在有限时间内完成否则压缩反而成了丢记录的原因。最后分享一个小技巧调试阶段可以临时把 Pstore 的日志级别调高让它把每次写入的偏移、长度、校验结果都打出来对着这些数字看比看最终结果更容易定位是哪一步出的问题。等调通了再把日志级别降回去避免正常运行时刷屏。这个法子在我排查记录写进去了但读不出来的问题时特别管用一眼就看出是写入偏移算错了还是读取解析错了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑