Linux Pstore 崩溃现场快照:原理、后端选型与实战配置 Pstore 这个东西第一次接触的人多半会觉得它有点反直觉——它干的事情是在系统崩溃、重启之后还能把上一次运行的最后一点现场信息留下来。你可能会想日志系统不是已经有一堆了吗dmesg、syslog、journald哪个不能记日志问题就在这儿这些常规日志方案全都依赖一个前提——系统还活着文件系统还能正常读写。一旦内核 panic 或者硬件突然掉电内存里的日志缓冲区还没来得及刷到磁盘机器就已经重启了现场信息全部蒸发。Pstore 要解决的恰恰是这个临终遗言的问题。我在实际做嵌入式设备故障定位的时候被这个问题折磨过很久。设备偶发重启用户只会说它自己重启了而常规日志里干干净净什么都查不到。后来把 Pstore 配起来才第一次在重启后看到 panic 前那几行调用栈定位效率完全是两个量级。这篇就围绕 Linux 5.10 这个版本把 Pstore 的原理掰开揉碎讲清楚它凭什么能在重启后保住数据、后端存储到底怎么选、内核里那套注册与读写流程是怎么走的、以及实际用起来有哪些坑。适合已经知道 Pstore 大概是什么、想搞明白它内部机制的读者也适合正准备在项目里落地它、需要判断可行性的工程师。1. 先搞清楚 Pstore 到底在跟谁抢时间要理解 Pstore 的设计得先理解它面对的是一个什么样的敌人。这个敌人不是日志太多写不下而是系统在写日志之前就已经死了。这两者的区别决定了整个架构的走向。1.1 常规日志方案为什么在崩溃场景下必然失效我们平时用的日志路径本质上都是内存缓冲 定期落盘的模式。内核 printk 把消息写进 ring buffer用户态的 klogd 或者 systemd-journald 再把它读出来写进文件。这条链路上有太多环节依赖系统处于健康状态调度器要能跑、文件系统要能挂载、块设备驱动要能响应。系统一旦 panic内核会走 panic 流程此时调度基本停摆你指望它再去做一次完整的文件写入风险极高而且很多情况下根本来不及。更麻烦的是掉电场景。就算你配了panic_on_oops让它别乱跑硬件断电是没有任何商量余地的内存里的东西说没就没。所以常规日志方案在崩溃/掉电这个场景下失效是结构性的不是配置问题。你得换一个思路不要依赖系统活着而是依赖一块重启后内容还在的存储介质并且在崩溃发生的那一刻用尽可能短的路径把数据怼进去。Pstore 就是按这个思路设计的。它把存储后端和记录前端解耦前端负责在 panic、oops、重启等关键时刻抓取数据后端负责把数据写进一块重启后不会丢的地方。这个解耦是整个设计的关键也是理解它的入口。1.2 Pstore 的定位不是日志系统是崩溃现场快照很多人一开始会把 Pstore 当成另一个日志系统这个理解会带偏。Pstore 不追求日志的完整性、时序性、可检索性它追求的是最后那一刻的快照。它记录的东西很有限内核 panic/oops 的日志片段、部分控制台输出、以及一些前端主动写进去的标记信息。它不保证你能看到崩溃前十分钟发生了什么只保证你能看到崩溃前最后那几 KB。这个定位决定了它的容量通常很小。后端存储往往只有几十 KB 到几 MB比如 ACPI ERST 表、UEFI 变量、或者一块专门划出来的 RAM 保留区。你不能指望往里面塞大量数据所以 Pstore 的写入策略是截断式的——只保留最新的、最关键的部分老的记录会被覆盖。理解这一点很重要后面配置容量和判断能抓到多少信息时全靠这个认知。提示如果你的需求是完整审计日志Pstore 不是答案如果你的需求是崩溃后想知道死在哪一行Pstore 才是对的方向。别把两者混为一谈。1.3 前端与后端一张表看清职责划分Pstore 的代码结构里前端frontend和后端backend是两个独立注册的实体。前端关心什么时候写、写什么后端关心写到哪、怎么读回来。这种分层让同一套前端逻辑可以适配不同的硬件存储介质扩展性很好。角色代表实现职责触发时机前端pstore_ramoops、pstore_console、pstore_ftrace抓取数据、格式化记录panic、oops、控制台输出、主动写后端ramoops、ACPI ERST、EFI var、块设备提供持久化存储、注册读写回调由前端调用核心层pstore 核心fs/pstore管理记录链表、注册接口、sysfs 暴露贯穿始终这张表是我理解 Pstore 时最有用的一个抓手。你去看代码fs/pstore/目录下platform.c、ram.c、inode.c这些文件基本就是围绕这三层展开的。前端通过pstore_register()注册自己后端通过pstore_register_backend之类的接口挂上去核心层负责把两边接起来并在/sys/fs/pstore/下暴露记录文件。搞清这三层后面看任何具体实现都不会迷路。2. 后端存储介质的选择逻辑与取舍Pstore 能不能用、好不好用八成取决于后端选得对不对。不同后端在掉电是否保留容量写入速度平台依赖这几个维度上差异巨大选错了要么抓不到数据要么平台根本跑不起来。2.1 ramoops最通用但也最容易被误解的方案ramoops 是 Pstore 里最常被提到的后端名字里的 ram 容易让人误以为它不持久。实际上 ramoops 用的是persistent RAM——一块在重启过程中不会被固件或内核清零的保留内存区域。它通过设备树或者内核命令行参数指定一段物理地址范围内核启动时不去碰它崩溃时往里写重启后再读出来。它的关键参数有这么几个mem_address指定保留区起始物理地址mem_size指定大小record_size指定单条记录最大长度console_size和ftrace_size分别给控制台和 ftrace 前端预留空间。这几个参数必须满足一个约束record_size console_size ftrace_size不能超过mem_size否则注册会失败。我见过不少人卡在这里日志里报ramoops: total size exceeds mem_size其实就是加法没算对。ramoops 的优点是平台无关、配置灵活、读写速度快缺点是它依赖这块内存重启后真的不被清。有些平台的 bootloader 或者内存初始化流程会把整片 DRAM 擦一遍那 ramoops 就废了。所以用之前一定要确认平台的保留内存语义别想当然。2.2 ACPI ERST 与 EFI 变量平台绑定但更正规ACPI ERSTError Record Serialization Table是服务器平台上比较常见的后端它通过 ACPI 表定义了一套把错误记录写进平台固件存储的机制。好处是这块存储由固件保证持久性不依赖内核去保留内存坏处是强绑定平台消费级设备基本没有。EFI 变量后端则是利用 UEFI 变量存储来保存记录。它的持久性由固件保证但变量存储通常容量很小而且写入有诸多限制比如变量大小上限、写入频率限制。我在一些 x86 平台上试过能存下的记录条数非常有限适合抓 panic 那一小段不适合长期累积。这两个后端的共同特点是正规但挑平台。如果你的项目跑在服务器或者有完整 UEFI 的环境上值得优先考虑如果是嵌入式或者消费级硬件大概率还是得回到 ramoops 或者块设备方案。2.3 块设备后端与伪持久化的陷阱Pstore 也支持把记录写到块设备上走的是通用的块设备读写路径。听起来很美——容量大、持久性好。但这里有个大坑崩溃发生时块设备驱动和文件系统可能已经不可用了你指望它在 panic 流程里完成一次可靠的块写入成功率并不高。而且块设备写入涉及 IO 调度、缓存回写路径太长在临终场景下不可靠。所以块设备后端更适合重启前主动写的场景比如你在系统正常时通过 pstore 接口写一些标记而不是指望它在 panic 时救命。真正救命的后端必须是写入路径极短、不依赖复杂子系统的ramoops 和 ERST 之所以靠谱就是因为它们绕过了大部分内核基础设施。注意判断一个后端是否适合崩溃场景就看它的写入路径有多短。路径越长panic 时失败的概率越高。这是选型的核心标准比容量和速度都重要。3. 内核里那套注册与读写流程是怎么跑起来的理解了存储介质接下来得看内核代码是怎么把前端、后端、核心层串起来的。这部分是原理学习的重头戏也是很多人看代码看到一半就放弃的地方。我尽量按实际执行顺序讲而不是按文件目录讲。3.1 核心层的记录管理链表、zone 与 inodePstore 核心层用一条链表管理所有记录每条记录对应一个pstore_record结构里面记录了类型dmesg、console、ftrace、pmsg 等、时间戳、数据缓冲区。记录不是随便堆的它按zone划分——每个前端对应一个 zonezone 内部再按记录类型组织。这样设计的好处是不同前端的数据互不干扰读取时也能按类型过滤。在文件系统层面Pstore 通过pstore_get_inode之类的逻辑把记录映射成/sys/fs/pstore/下的文件。你ls那个目录看到的dmesg-ramoops-0、console-ramoops-0这些文件背后就是一条条记录。文件名里的数字是记录序号序号会循环复用所以你会看到-0、-1这样轮着来。这个映射关系是理解为什么重启后能看到文件的关键——文件不是真的存在磁盘上而是内核启动时从后端把记录读出来动态生成的。这里有个细节值得说Pstore 在启动时会调用后端的read回调把后端里所有有效记录读进内存链表然后才在 sysfs 里暴露出来。所以你看到的文件内容其实是启动时从持久存储里捞出来的而不是实时写进去的。这个时序对排查问题很重要——如果启动早期就读可能有些后端还没初始化好记录就读不到。3.2 前端注册pstore_register 做了什么前端注册的入口是pstore_register()它做的事情可以概括为分配 zone、设置读写回调、把 zone 挂到全局链表、在 sysfs 里创建对应目录。以 ramoops 前端为例它在初始化时会根据设备树或命令行参数算出各个 zone 的大小然后逐个注册。注册过程中有几个容易出问题的地方。第一是 zone 名字冲突如果两个前端用了同一个名字后注册的会失败。第二是大小计算前面提过的record_size console_size ftrace_size mem_size约束不满足直接返回错误。第三是后端是否就绪前端注册时后端必须已经注册好否则拿不到存储句柄。这个顺序在设备树场景下由 probe 顺序保证但在某些动态加载场景下需要自己控制。我踩过一次坑把 ramoops 配成模块结果它在根文件系统挂载后才加载此时早期的 panic 信息早就没了。后来改成内建built-in问题解决。这个经验说明Pstore 相关配置尽量往早期放越早注册能抓到的信息越全。3.3 崩溃时刻的写入路径从 panic 到落盘这是整个 Pstore 最核心也最精妙的部分。当内核 panic 时会走panic()流程其中会调用pstore_dump()之类的钩子把当前 dmesg 缓冲区的内容按记录大小切分逐条写进后端。写入走的是后端的write回调ramoops 的写回调就是简单的 memcpy 到保留内存路径极短成功率高。写入时有个截断逻辑如果一条记录超过record_size会被截断如果后端空间满了老记录会被覆盖。所以你在 panic 后看到的 dmesg 往往是最后那一段而不是完整的。这个行为是设计使然不是 bug。理解这一点你就不会抱怨为什么只抓到几行。另外控制台前端pstore_console的写入时机不一样它是在控制台输出时实时写的走的是 console 注册机制。这意味着控制台输出量大时会频繁写后端对性能有影响所以console_size一般不会配太大。ftrace 前端类似按需开启。这几个前端的触发时机差异是配置时必须要考虑的。4. 实际落地时的配置、验证与踩坑清单原理讲完落到实操。这部分我按配—验—排的顺序来都是实际项目里验证过的路径你可以直接对照着做。4.1 设备树与命令行两种配置方式ramoops 的配置有两条路设备树节点和内核命令行。设备树方式更清晰适合产品化命令行方式适合调试。设备树节点大概长这样reserved-memory { #address-cells 1; #size-cells 1; ranges; ramoops: ramoops8f000000 { compatible ramoops; reg 0x8f000000 0x100000; record-size 0x10000; console-size 0x40000; ftrace-size 0x0; pmsg-size 0x20000; }; };这里reg的大小是 1MBrecord-size64KBconsole-size256KBpmsg-size128KB加起来 448KB没超过 1MB合法。注意ftrace-size设成 0 表示不启用 ftrace 前端按需开。命令行方式则是ramoops.mem_address0x8f000000 ramoops.mem_size0x100000 ramoops.record_size0x10000 ...参数名和含义一一对应。配置完还要确保这块内存真的被保留。设备树里用reserved-memory节点声明内核的 memblock 会把它从可用内存里摘出去不会分配给普通用途。这一步漏了ramoops 写进去的数据可能被别的分配覆盖重启后读出来就是垃圾。4.2 验证是否生效的三步法配完别急着信按这三步验一遍。第一步启动后看dmesg | grep -i pstore应该有类似pstore: Registered ramoops as persistent store backend的输出说明后端注册成功。第二步ls /sys/fs/pstore/如果之前有过崩溃应该能看到记录文件如果是全新启动目录可能是空的这正常。第三步主动制造一次记录——可以echo test /sys/fs/pstore/pmsg-ramoops-0pmsg 前端支持主动写然后重启再看这个文件是否还在。如果重启后内容还在说明整条链路通了。这三步里第三步最关键因为它验证的是持久性这个核心属性。我见过配置看起来全对、注册日志也正常但重启后数据没了的情况最后查出来是保留内存被 bootloader 清了。所以别跳过主动写测试。4.3 常见故障对照表现象可能原因排查方向注册报 total size exceeds mem_size各 zone 大小之和超过 mem_size重新核算 recordconsoleftracepmsg注册成功但重启后无记录保留内存未生效或被清零检查 reserved-memory 节点、bootloader 行为只能看到部分 dmesgrecord_size 太小或空间被覆盖适当增大 record_size理解截断机制控制台输出变慢console 前端频繁写后端减小 console_size 或关闭该前端模块加载后抓不到早期 panic注册时机太晚改为内建提前注册这张表是我这些年遇到问题后攒下来的基本覆盖了八成以上的现场情况。遇到新问题先往这几个方向套能省不少时间。4.4 几个只有踩过才知道的细节第一个细节pmsg前端特别适合做用户态标记。你可以在系统正常时往/sys/fs/pstore/pmsg-ramoops-0写一些状态标记比如进入某关键流程崩溃后结合 dmesg 一起看能还原出崩溃前系统在干什么。这个用法比单纯抓 panic 日志有用得多我在排查偶发问题时经常用。第二个细节记录文件是只读的除了 pmsg你别想着去改它。读取时用cat就行但注意有些记录是二进制或者带特殊格式直接 cat 可能乱码得用对应工具解析。第三个细节多后端共存时记录会分散在不同后端里/sys/fs/pstore/下文件名会带后端名后缀。排查时别只看一个后端的文件容易漏。第四个细节Pstore 的记录在读取后不会自动删除会一直占着后端空间直到被新记录覆盖。如果你希望读一次就清得手动处理或者依赖覆盖机制。这个行为在容量紧张时要特别注意。5. 从原理反推什么样的场景才真正适合上 Pstore讲完机制和实操最后聊聊判断标准。不是所有项目都值得上 Pstore用错了地方反而是负担。5.1 适合与不适合的典型场景对照适合的场景有这么几类偶发重启且常规日志抓不到现场的设备、需要定位内核 panic 的嵌入式产品、对临终快照有强需求的可靠性场景。这些场景的共同点是崩溃信息价值高、常规手段拿不到。不适合的场景也很明确需要完整审计日志的合规场景Pstore 容量根本不够、崩溃频率极低且常规日志够用的场景上了也是浪费保留内存、以及平台根本不支持持久保留内存的场景硬上只会得到一堆空文件。判断的核心就一句话崩溃现场信息对你值不值那几十 KB 到几 MB 的保留内存。值就上不值就别折腾。5.2 容量规划的一个实用估算方法容量怎么定我的经验是按你想抓多少行 dmesg来反推。一行 dmesg 平均 100 字节左右你想抓 500 行就是 50KBrecord_size至少给到 64KB 留余量。控制台如果也要抓按输出速率估一般 128KB 到 256KB 够用。pmsg 用来放标记32KB 到 128KB 足矣。加起来通常 256KB 到 1MB 就能覆盖大部分需求没必要一上来就划几 MB保留内存也是资源。5.3 和其他调试手段的配合关系Pstore 不是孤立的它和 kdump、硬件看门狗、串口控制台这些手段是互补的。kdump 能抓完整的内存转储但依赖第二个内核和较大的保留内存配置复杂Pstore 轻量、路径短适合快速定位。实际项目里我经常是Pstore 抓第一现场kdump 做深度分析两者配合效率最高。串口控制台则是最后的兜底Pstore 抓不到的时候还能靠它看实时输出。我在实际使用中的体会是Pstore 最大的价值不在于它多强大而在于它在最坏的情况下还能工作。系统越复杂越需要这么一个简单、短路径、不依赖太多东西的兜底机制。把它配好、验证好平时可能感觉不到它的存在但真出问题时那几行 panic 日志往往就是破案的关键。配的时候多花十分钟把保留内存和注册时机确认清楚比事后对着空日志抓瞎强太多。