kdump_mmc

简介

当内存发生panic的时候,需要把panic的内容以日志的方式记录下来。目前有几种方式,kdump,mtdoops,crashlog(openwrt特有),以及pstore。

kdump主要用在x86系统上,因为它使用大量的内存和硬盘信息。

mtdoops和crashlog主要用于嵌入式系统。记录文本日志。

mtdoops

mtdoop功能在发生oops时,把msg区写入特定的mt分区。写入过程,不支持文件系统。直接二进制文本写。 它需要由mtd驱动的支持,就是mtd驱动支持mtd_panic_write。也就是原子式写入。不能被中断。一般flash不支持。 在mtdoops.c文件,并在标准内核的drivers/mtd/mtdcore中。

1
2
3
mtd_panic_write
--> _panic_write
---> mtd->_panic_write = panic_nand_write / onenand_panic_write / concat_panic_write 在具体驱动实现

在初始化过程中,需要指定写入哪个分区,对应的分区名/号,可以写入的size是多少。使用kmsg_dump_register注册一个cxt->dump.dump绑定的回调函数 (eg:mmcoops_do_dump),此函数主要是读取kmsg区的内容,直接调用mtd->write的驱动进行写操作。

crashlog

在linux内核启动的时候,保留一块64K的内存,用于记录panic日志,crashlog发生在oop时候,把msg写入之前分配好的MEM区域,并加上magic,再重启后,check magic ok,则把上次日志放入到/sys/kernel/debug/crashlog。

pstore

pstore最初是用于系统发生oops或panic时,自动保存内核log buffer中的日志。不过在当前内核版本中,其已经支持了更多的功能,如保存console日志、ftrace消息和用户空间日志。同时,它还支持将这些消息保存在不同的存储设备中,如内存、块设备或mtd设备。 为了提高灵活性和可扩展性,pstore将以上功能分别抽象为前端和后端,其中像dmesg、console等为pstore提供数据的模块称为前端,而内存设备、块设备等用于存储数据的模块称为后端,pstore core则分别为它们提供相关的注册接口。

通过模块化的设计,实现了前端和后端的解耦,因此若某些模块需要利用pstore保存信息,就可以方便地向pstore添加新的前端。而若需要将pstore数据保存到新的存储设备上,也可以通过向其添加后端设备的方式完成。

image-20240924102113713

除此之外,pstore还设计了一套pstore文件系统,用于查询和操作上一次重启时已经保存的pstore数据。当该文件系统被挂载时,保存在backend中的数据将被读取到pstore fs中,并以文件的形式显示。

源码在/fs/pstore/ram_core.c

1
2
3
4
5
6
7
8
9
10
fs/pstore/
├── ftrace.c # ftrace 前端的实现
├── inode.c # pstore 文件系统的注册与操作
├── internal.h
├── Kconfig
├── Makefile
├── platform.c # pstore 前后端功能的核心
├── pmsg.c # pmsg 前端的实现
├── ram.c # pstore/ram 后端的实现,dram空间分配与管理
├── ram_core.c # pstore/ram 后端的实现,dram的读写操作

oops/panic日志位于 pstore 目录下的dmesg-ramoops-x文件中,根据缓冲区大小可以有多个文件,x从0开始。
函数调用序列日志位于 pstore 目录下的ftrace-ramoops文件中。

使用方法

内核配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_RAM=y
CONFIG_PANIC_TIMEOUT=-1

由于log数据存放于DDR,不能掉电,只能依靠自动重启机制来查看,故而要配置:CONFIG_PANIC_TIMEOUT,让系统在 panic 后能自动重启。

mtdoops:
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_MTD_OOPS=y
CONFIG_MAGIC_SYSRQ=y

blkoops:
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_BLK=y
CONFIG_MTD_PSTORE=y
CONFIG_MAGIC_SYSRQ=y

设备树配置

1
2
3
4
5
6
7
8
9
10
11
12
13
ramoops_mem: ramoops_mem {
reg = <0x0 0x110000 0x0 0xf0000>;
reg-names = "ramoops_mem";
};

ramoops {
compatible = "ramoops";
record-size = <0x0 0x20000>;
console-size = <0x0 0x80000>;
ftrace-size = <0x0 0x00000>;
pmsg-size = <0x0 0x50000>;
memory-region = <&ramoops_mem>;
};

bootargs分区配置

1
2
3
4
5
6
7
8
9
10
11
12
13
方案1:
bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs mtdoops.mtddev=pstore";
(blk则为 pstore_blk.blkdev=pstore)

blkparts = "mtdparts=spi0.0:64k(spl)ro,256k(uboot)ro,64k(dtb)ro,128k(pstore),3m(kernel)ro,4m(rootfs)ro,-(data)";

方案2:
bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs mtdoops.mtddev=pstore";
在设备树中
partition@60000 {
label = "pstore";
reg = <0x60000 0x20000>;
};

挂载pstore文件系统

1
mount -t pstore pstore /sys/fs/pstore

summary

以上几种方案都使用到了kmsg_dump的注册机制。 注册很简单,就是把一个全局变量结构挂到一个全局list中。 kmsg_dump是oops时进入kmsg_dump的入口。由panic,die,oops_exit等函数调用。它会一一调用回调函数。 每一个回调函数都会用到kmsg_dump_get_buffer。它先是计算dump还有多少空间,然后把kmsg中最后的一部分写进去。