
搞Linux内核的人谁还没被“设备重启后一切线索清零”坑过。RK3588这两年在中高端开发板、AI边缘计算盒子上出现频率非常高8核A76A55性能确实猛但越是多核高负载、GPU/NPU/PCIe全开的环境越容易在没人盯着的时候给你来个内核panic。最难受的是等你想起来拿串口去抓日志设备早就重启完好几轮dmesg环形缓冲区被清得干干净净连个眼神都没留下。所以我一直建议凡是拿RK3588做产品、做长期无人值守部署的第一件事就把pstore和ramoops这套内核崩溃日志机制配好。简单讲它能在内核panic/oops发生时把最后的“遗言”写进一块特意保留的内存区域系统随便重启日志都还在等系统恢复后一次性读出来。这篇就把pstore和ramoops的完整玩法讲清楚包括内核配置、设备树写法、验证步骤以及我在RK3588上踩过的一堆坑。1. 为什么崩溃日志会凭空消失pstore与ramoops的设计逻辑1.1 panic之后常规日志通道为什么全都不靠谱先想想没有pstore时内核崩溃后会发生什么。如果你恰好接着串口盯着也许能在屏幕上看到几屏panic backtrace但大多数RK3588设备是无人值守的机箱放在现场串口根本没引出就算引出了串口终端的缓冲也撑不住高频日志panic前几秒的输出很容易被冲掉。dmesg环形缓冲区本身就在DDR里软重启后内核重新初始化内存管理系统缓冲区内容直接作废。换句话说崩溃日志丢失不是偶发情况而是默认情况。那能不能崩溃时顺手写一下磁盘或者eMMC想归想基本做不到。内核进入panic处理路径后会关中断存储驱动、文件系统、块层这些可能已经处于不可用状态任何可能睡眠的操作都不能执行。你不能在中断关闭的上下文里正常调一次write()。于是内核换了个思路提前在物理内存里准备一小块区域崩溃时把日志原样拷贝进去重启后还能读回来。这就是ramoops的出发点。1.2 pstore是框架ramoops是其中一个后端pstore并不是某个具体设备它是一套内核持久化存储接口代码在fs/pstore/下。pstore定义了一系列record类型比如dmesg、console、pmsg、ftrace后端则负责把这些记录写到具体介质。Linux官方支持后端有好几个ramoops内存、efiUEFI变量、blk块设备等。对RK3588这类嵌入式Linux平台来说最常用的就是ramoops后端。它不依赖文件系统参与也不需要额外硬件只要DDR还在供电日志就能留到下一次启动。ramoops驱动启动时会做两件事一是从设备树或内核参数拿到预留内存的地址和大小把这块区域注册成持久化RAM二是注册panic/oops回调等内核真正崩溃时把printk缓冲区、console输出等内容写到这几块内存区域里。形象点说pstore像一块带格式的留言板ramoops就是留言板底下那张特别结实的白板——只要不断电重启后字还在。1.3 RK3588的场景里ramoops为什么是最优解对比其他日志方案能更清楚看出ramoops对RK3588这类板卡平台的优势。串口日志方案要求物理接线加有人盯着对产线设备、现场节点基本等于摆设。kdump/Crash kernel需要预留大块内存并配置kexec在嵌入式平台上资源本来就紧张还要维护两套内核体量太重。而ramoops只需要1MB左右的内存对RK3588动辄4GB、8GB、16GB的DDR来说几乎可以忽略不计却能覆盖“崩溃后重启找日志”的绝大部分需求。尤其RK3588经常跑Ubuntu、Debian这类完整发行版systemd启动时一般会自动挂载pstore文件系统配置好之后用户态取日志几乎零成本。2. 内核配置先把pstore全家桶打开2.1 必须确认的Kconfig开关RK3588官方BSP自带的defconfig不一定把pstore相关选项全打开。我遇到过几块板子只开了CONFIG_PSTORE没开CONFIG_PSTORE_RAM结果dmesg里从头到尾没有ramoops注册信息日志当然抓不到。建议在配置内核时把下面这几个选项一次性确认好配置项作用建议CONFIG_PSTOREpstore主框架总开关yCONFIG_PSTORE_RAMramoops内存后端核心yCONFIG_PSTORE_CONSOLE记录console输出yCONFIG_PSTORE_PMSG支持/dev/pmsg0用户态日志yCONFIG_PSTORE_FTRACE记录function trace按需开启如果习惯用menuconfigpstore相关选项一般在File systems - Miscellaneous filesystems目录下但不同内核版本菜单位置会有变化。更快的办法是进入menuconfig后直接按/搜索PSTORE跳转到对应选项。RK3588通常编的是arm64内核在SDK源码目录下执行make ARCHarm64 menuconfig搜索到选项后逐一确认保存退出。改完配置后一定要回到.config文件里再grep一遍确认是y而不是m。pstore后端如果编成模块启动时忘了insmod等于没配。2.2 哪些选项值得按需开启CONFIG_PSTORE_FTRACE属于可选项但很多人会忽略它的价值。调试难复现的随机崩溃时ftrace记录能还原panic前函数调用轨迹帮你判断是哪个路径走到崩溃点的。不过ftrace写入很频繁如果预留内存不大可能把dmesg记录挤掉。我的建议是DDR空间不紧张时把record-size适当调大后开ftrace如果只想要稳定抓到每次panic日常调试先不开ftrace免得干扰主线排障。CONFIG_PSTORE_PMSG也值得多说一句。打开后内核会提供/dev/pmsg0节点用户态可以往里写任意文本重启后再从pstore读回。对“用户态服务卡死、内存泄漏越搞越糟”这类问题你可以在关键路径上写几行时间戳崩溃后对比用户态和内核态的时间线。RK3588上跑长时间稳定性测试时这个功能非常实用。2.3 配置生效后先看一眼再继续编译烧录完先别急着制造崩溃先确认ramoops确实注册成功。正常时dmesg里应该能看到类似这样的输出具体文本随内核版本略有差异$ dmesg | grep -i ramoops ramoops: using 0x1100000xf0000 ramoops: attached 0xf00000x110000 (1 backend)如果什么输出都没有大概率是内核配置没编进去或者设备树节点没被解析到。也可以检查当前运行内核的配置$ zcat /proc/config.gz | grep PSTORE CONFIG_PSTOREy CONFIG_PSTORE_RAMy CONFIG_PSTORE_CONSOLEy CONFIG_PSTORE_PMSGy有些精简版板子没开CONFIG_IKCONFIG_PROC/proc/config.gz可能不存在。那就回到内核源码目录查看编译时实际生成的.config文件。3. 设备树配置给RK3588划一块“重启不丢”的内存3.1 为什么必须在设备树里预留内存ramoops能工作的前提是内核在启动早期就知道有一块物理内存“谁都不准碰”。如果这块内存没有放进reserved-memory节点内核的内存管理子系统就会把它当普通内存分配出去等你panic时想写日志写进去的内容随时可能被覆盖。所以预留动作必须写在设备树里让内核在初始化内存管理阶段就把这块区域排除出去。标准的做法是在reserved-memory节点下面定义子节点并用no-map属性告诉内核这块区域不要建立页表映射也不要纳入伙伴系统。no-map必须加不加的话ramoops虽然也能注册但日志很容易被内核自身的线性映射写坏读出来的内容可能是一堆空洞和乱码。3.2 完整的设备树节点写法给一个比较规范的写法/ { reserved-memory { #address-cells 2; #size-cells 2; ranges; ramoops_reserved: ramoops110000 { reg 0x0 0x110000 0x0 0x00100000; no-map; }; }; ramoops { compatible ramoops; memory-region ramoops_reserved; record-size 0x20000; console-size 0x20000; ftrace-size 0x0; pmsg-size 0x20000; }; };这种写法的好处是把“预留区域”和“ramoops参数”拆开了区域是否no-map、是否与其他reserved region冲突都能在reserved-memory节点里统一管理。如果不想用memory-region也可以直接在ramoops节点里写regRockchip老版本BSP里很常见。我推荐前者因为后期排查内存布局冲突时一眼就能看出问题。3.3 地址和大小怎么定四条实战经验地址选不好ramoops配置再对也白搭。RK3588平台的memory节点通常不是从物理0地址开始的很多板卡的可使用DDR区域从0x00200000起步低地址往往被BL31、TEE、U-Boot预留区占用。你拿到一块板子第一件事不是抄别人的dts而是打开实际加载的dtb对应源码先看memory节点和reserved-memory节点里已经有哪些区域。挑一块真正没人占用的空闲地址宁可多花半小时确认也不要闭眼抄。大小推荐1MB起步也就是0x100000。RK3588的DDR按GB算1MB连零头都算不上但这点空间足够保存几条完整的panic记录。所有size建议按0x20000128KB的整数倍来配最低也要4KB对齐。ramoops驱动对record-size、console-size等参数的对齐要求比较严格写得不对会直接probe失败。以3.2的配置为例1MB总预留去掉console的128KB和pmsg的128KBdmesg区大约剩768KB。单条记录record-size是128KB理论上能存6条实际去掉元数据和对齐大约能保留5到6条足够应对“连续崩溃几轮再开机取日志”的场景。如果只预留512KB可能只能存两三条崩溃一频繁前面的记录就被覆盖了。需要特别声明0x110000这个地址在很多瑞芯微SDK里是沿用下来的但它不是万能的。RK3588不同板卡的DDR起始地址、ATF预留、TEE reserved memory都可能不一样一定以你手里实际编译的dts为准。我见过有人拿着RK3399的dts直接改chip名就上RK3588ramoops地址完全不适用启动阶段莫名其妙挂掉排查起来非常痛苦。3.4 RK3588特有的低级坑U-Boot、ATF和DDRRK3588有几个坑我一直想专门写出来。ATF/BL31、OP-TEE会占用DDR低地址U-Boot也会用低端内存做堆和全局数据。如果ramoops预留地址和这些区域重叠表现往往非常极端U-Boot阶段就出问题串口压根不打印板子就像死了一样只能进maskrom重新烧录。这种问题比“日志没抓到”严重得多。所以在配置RK3588的ramoops地址时建议先在SDK里全局搜一下是否已有ramoops或reserved-memory节点。Rockchip官方BSP很多dts里其实已经预留好了你要做的只是确认它有没有被include进实际编译的dts以及大小是否满足需求。另外还要留神parameter分区和U-Boot环境变量区盲目把预留区改得过大可能侵占U-Boot环境变量区域导致环境变量读不出来启动流程整个乱掉。4. 实操验证自己制造一次内核崩溃再把日志捞回来4.1 制造崩溃之前要做的基础检查配置完成后建议先做一轮基础检查再触发崩溃避免白忙活。确认CONFIG_MAGIC_SYSRQ已经打开确认/sys/fs/pstore目录存在或者可以手动挂载再通过dmesg确认ramoops注册成功。顺手记录一下当前时间后面读日志时可以用来对照崩溃时刻。$ mount | grep pstore pstore on /sys/fs/pstore type pstore (rw,relatime)如果系统没有自动挂载可以手动挂载$ mkdir -p /sys/fs/pstore $ mount -t pstore pstore /sys/fs/pstoreUbuntu、Debian这类发行版上systemd一般会自动挂载BusyBox精简rootfs里经常需要手动处理。4.2 三种触发内核崩溃的方式最干净的方式是使用sysrq一条命令直接触发panic$ echo c /proc/sysrq-trigger执行后系统会立刻进入panic流程适合验证ramoops是否正常工作。这个操作需要在root权限下执行内核还要开启CONFIG_MAGIC_SYSRQ。如果觉得sysrq太“人工”想模拟更真实的oops可以写一个故意访问空指针的内核模块#include linux/module.h #include linux/kernel.h static int __init boom_init(void) { int *p NULL; *p 0xdeadbeef; return 0; } module_init(boom_init); MODULE_LICENSE(GPL);在内核源码目录下用M参数编译得到ko文件再在板子上insmod。加载瞬间就会触发oops如果内核开启了panic_on_oops会进一步演变成panic$ sysctl -w kernel.panic_on_oops1 $ insmod boom.ko还有一种方式是通过硬件看门狗让系统超时复位适合模拟无人值守设备死机的场景但需要提前确认看门狗驱动有输出。对大多数验证场景sysrq触发就足够了。4.3 重启后的日志提取系统崩溃后会重启重启完成后去/sys/fs/pstore目录看文件$ ls -l /sys/fs/pstore/ total 0 -r--r--r-- 1 root root 131072 Jan 1 00:03 console-ramoops-0 -r--r--r-- 1 root root 131072 Jan 1 00:03 dmesg-ramoops-0 -r--r--r-- 1 root root 65536 Jan 1 00:03 pmsg-ramoops-0dmesg-ramoops-0是内核崩溃时的主日志也就是平时dmesg能看到那套printk输出。console-ramoops-0包含console输出panic前的最后打印经常能在这里找到。pmsg-ramoops-0是用户态写入的内容如果你之前向/dev/pmsg0里写过数据重启后会在对应文件里读回。查看崩溃时间和backtrace$ cat /sys/fs/pstore/dmesg-ramoops-0 | tail -n 100一般能在文件尾部看到类似“Kernel panic - not syncing”或者“Call trace”的关键信息。抓完日志后建议顺手把文件删掉这样下一轮崩溃时记录序号会从干净状态开始不会和旧日志混在一起。4.4 从backtrace地址定位到具体代码日志里最有用的是Call trace部分的函数地址和符号。如果内核编译时开了CONFIG_KALLSYMS符号名会直接出现在backtrace里如果只有裸地址就需要配合vmlinux来解析。以arm64的RK3588内核为例可以用内核源码树下的faddr2line脚本$ ./scripts/faddr2line vmlinux do_mmap0x0/0x348也可以用交叉编译工具链里的addr2line$ aarch64-linux-gnu-addr2line -e vmlinux -f 0xffff0000103c4a18要注意内核如果开了KASLRpanic日志里的地址和vmlinux里的链接地址有偏移。模块崩溃时还要结合/proc/modules里的加载基址来换算。RK3588的发行版内核有些默认开KASLR分析日志时先看清楚有没有随机化偏移。5. 避坑指南配置了却没日志多半是这几个原因5.1 /sys/fs/pstore目录是空的ramoops却显示注册成功这种情况我遇到过好几次。ramoops注册成功说明驱动把预留内存认下来了但目录里没有文件通常原因是没有真正挂载pstore或者内核配置里CONFIG_PSTORE_CONSOLE、CONFIG_PSTORE_PMSG这些子选项没开只有dmesg类型时文件生成晚或者没生成。先在板子上手动执行mount -t pstore pstore /sys/fs/pstore再ls看一次。如果还是空的检查当前运行的内核配置和启动日志里pstore相关的报错。还有一个容易被忽略的点确认你烧录的内核确实是新编译的。RK3588的启动链路很长U-Boot可能从boot分区加载了旧的Image或者你改了dts但没重新打包boot.img导致ramoops节点根本没生效。排查时在启动日志里搜U-Boot加载镜像的文件名和时间确认加载的确实是新内核。5.2 日志读出来全是乱码或者一串0这说明预留内存在崩溃前就被写坏了。最常见原因是reserved-memory节点里漏了no-map属性内核正常建映射后某个驱动或DMA操作把这块区域当普通内存用了。另一个常见场景是dts里改了ramoops地址但U-Boot实际加载的还是旧dtbramoops在新地址压根没注册你读到的是其他内存区域的残留数据。检查U-Boot解析dtb的来源确认它加载的是不是最新编译的那一份。乱码还有一种更隐蔽的情况预留区和CMA、IOMMU的reserved region重叠。虽然都在reserved-memory节点里但有些驱动会通过DMA分配器申请物理内存如果ramoops区域没有no-map或者被DMA memory pool误纳一样会被写坏。建议把ramoops独立放在一个no-map区域和其他reserved region保持明显间距。5.3 panic日志被截断只有后半段没有开头这是record-size设太小造成的。内核oops打印包含寄存器、栈、backtrace内容动辄几十KB。如果record-size只有16KB一条panic记录会被拦腰截断而且dmesg和console记录各自独立两边内容还衔接不上。我的建议是record-size至少0x20000128KB预留区总大小至少1MB。如果系统里printk输出特别多还可以在kernel cmdline里增大log_buf_len但要注意这和使用ramoops的record-size是两个维度别搞混。另外如果开启ftrace的同时又把record-size调得很小ftrace会频繁写记录很快把dmesg区域挤掉。这种情况下优先保证record-sizeftrace用不用看需求。5.4 配置完ramoops后板子起不来这是最糟心的一种情况很多RK3588用户在第一次配置时遇到过。RAMoops预留地址选得不好和ATF、OP-TEE、U-Boot的保留区冲突轻则启动早期报内存分配错误重则U-Boot阶段直接挂掉串口无输出只能重新进maskrom。遇到这种情况先用U-Boot的bdinfo命令看当前内存布局或者在内核早期启动日志里找reserved-memory的打印信息。如果是地址冲突把ramoops地址改到别的空闲区或者干脆沿用SDK自带的默认ramoops节点不要自己拍脑袋选地址。这也解释了为什么我建议用reserved-memory memory-region的规范写法当系统起不来时你可以直接在U-Boot阶段或者内核早期打印里看到reserved region列表快速判断是不是地址撞车。地址写在ramoops节点里也不是不行但排查起来确实多绕几步。5.5 多次崩溃后记录被覆盖甚至完全找不到旧日志ramoops的dmesg区是环形缓冲区里面能存多条记录序号依次递增。崩溃次数超过可容纳的记录数后最早的记录会被覆盖。所以每次取完日志记得把/sys/fs/pstore下已经确认没用的文件删掉。删除文件时pstore会同步擦除对应record下一轮崩溃就是从第一条开始写不会被旧记录干扰。5.6 拔电或者彻底冷启动后日志消失这个坑最隐蔽但一定要记住ramoops里的数据放在DDR里DDR一掉电内容就没了。很多人一开始验证时系统panic之后自动重启日志还在觉得一切正常隔几天彻底断电后再上电发现/sys/fs/pstore是空的以为是配置失效。其实不是ramoops保的是“同一轮电源周期内的重启”不是掉电持久化。RK3588平台如果是看门狗复位warm reset或者系统panic后PMIC直接重启只要DDR供电没断日志就能保住但拔掉电源线、整板断电那就真的救不回来了。若需要掉电持久化就要考虑pstore的其他后端比如efi或者块设备后端那是另外一个话题了。我在RK3588上把ramoops整套配置好之后第一件事就是故意触发几次panic再反复重启把这个流程完整跑通。这个动作看着傻但非常值因为真到无人值守现场设备出问题时你根本没有试错机会。踩过这些坑之后我的体会是ramoops这套机制能不能用八成取决于你是否尊重它那块预留内存no-map有没有写地址和别人的保留区有没有撞车size是不是按对齐规则来任何一项松松垮垮它就会在关键时刻给你颜色看。配置时多花半小时确认内存布局比现场抓不到日志懊恼一整天要划算得多。