pstore block oops/panic logger

简介

pstore block (pstore/blk) 是一个 oops/panic 日志记录器,它在系统崩溃前将日志写入块设备和非块设备。你可以通过挂载 pstore 文件系统来获取这些日志文件,例如

mount -t pstore pstore /sys/fs/pstore

pstore block 概念

pstore/blk 为其自身提供了高效的配置方法,将所有配置分为两部分:用户配置和驱动配置。

用户配置决定了 pstore/blk 的工作方式,例如 pmsg_size、kmsg_size 等。它们都支持 Kconfig 和模块参数,但模块参数的优先级高于 Kconfig。

驱动配置全部关于块设备和非块设备,例如块设备的 total_size 以及读/写操作。

用户配置

所有这些配置都同时支持 Kconfig 和模块参数,但模块参数的优先级高于 Kconfig。

以下是模块参数的一个示例

pstore_blk.blkdev=/dev/mmcblk0p7 pstore_blk.kmsg_size=64 best_effort=y

你可能会对每个配置的细节感兴趣。

blkdev

要使用的块设备。大多数情况下,它是块设备的一个分区。这是 pstore/blk 所必需的。它也可用于 MTD 设备。

当 pstore/blk 被构建为模块时,“blkdev”接受以下变体形式

  1. /dev/<disk_name> 表示磁盘的设备号

  2. /dev/<disk_name><decimal> 表示分区的设备号 - 磁盘的设备号加上分区号

  3. /dev/<disk_name>p<decimal> - 与上述相同;当带分区的磁盘的名称以数字结尾时使用此形式。

当 pstore/blk 被编译进内核时,“blkdev”接受以下变体形式

  1. <hex_major><hex_minor> 十六进制表示的设备号,没有前导的 0x,例如 b302。

  2. PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF 如果分区表提供了分区的唯一 ID,则表示该 ID。UUID 可以是 EFI/GPT UUID,或者使用 SSSSSSSS-PP 格式引用 MSDOS 分区,其中 SSSSSSSS 是 32 位“NT 磁盘签名”的零填充十六进制表示,PP 是从 1 开始的分区号的零填充十六进制表示。

  3. PARTUUID=<UUID>/PARTNROFF=<int> 用于选择相对于具有已知唯一 ID 的分区的某个分区。

  4. <major>:<minor> 设备的主设备号和次设备号,中间用冒号隔开。

对于 MTD 设备,它接受以下变体形式

  1. <device name> MTD 设备名称。建议使用“pstore”。

  2. <device number> MTD 设备号。

kmsg_size

oops/panic 前端的数据块大小(以 KB 为单位)。它 必须 是 4 的倍数。如果你不关心 oops/panic 日志,它是可选的。

oops/panic 前端有多个数据块,具体取决于扣除其他 pstore 前端后剩余的空间。

pstore/blk 将逐个记录到 oops/panic 数据块中,如果没有更多的空闲数据块,总是会覆盖最旧的数据块。

pmsg_size

pmsg 前端的数据块大小(以 KB 为单位)。它 必须 是 4 的倍数。如果你不关心 pmsg 日志,它是可选的。

与 oops/panic 前端不同,pmsg 前端只有一个数据块。

Pmsg 是一个用户空间可访问的 pstore 对象。写入 /dev/pmsg0 的内容会被追加到该数据块中。重新启动后,其内容可在 /sys/fs/pstore/pmsg-pstore-blk-0 中获取。

console_size

控制台前端的数据块大小(以 KB 为单位)。它 必须 是 4 的倍数。如果你不关心控制台日志,它是可选的。

类似于 pmsg 前端,控制台前端也只有一个数据块。

控制台的所有日志都会被追加到该数据块中。重新启动后,其内容可在 /sys/fs/pstore/console-pstore-blk-0 中获取。

ftrace_size

ftrace 前端的数据块大小(以 KB 为单位)。它 必须 是 4 的倍数。如果你不关心 ftrace 日志,它是可选的。

类似于 oops 前端,ftrace 前端有多个数据块,具体取决于 CPU 处理器的数量。每个数据块的大小等于 ftrace_size / processors_count。

ftrace 的所有日志都会被追加到该数据块中。重新启动后,这些内容会合并并在 /sys/fs/pstore/ftrace-pstore-blk-0 中可用。

持久化函数跟踪可能对调试软件或硬件相关的挂起(hangs)有用。以下是一个使用示例

# mount -t pstore pstore /sys/fs/pstore
# mount -t debugfs debugfs /sys/kernel/debug/
# echo 1 > /sys/kernel/debug/pstore/record_ftrace
# reboot -f
[...]
# mount -t pstore pstore /sys/fs/pstore
# tail /sys/fs/pstore/ftrace-pstore-blk-0
CPU:0 ts:5914676 c0063828  c0063b94  call_cpuidle <- cpu_startup_entry+0x1b8/0x1e0
CPU:0 ts:5914678 c039ecdc  c006385c  cpuidle_enter_state <- call_cpuidle+0x44/0x48
CPU:0 ts:5914680 c039e9a0  c039ecf0  cpuidle_enter_freeze <- cpuidle_enter_state+0x304/0x314
CPU:0 ts:5914681 c0063870  c039ea30  sched_idle_set_state <- cpuidle_enter_state+0x44/0x314
CPU:1 ts:5916720 c0160f59  c015ee04  kernfs_unmap_bin_file <- __kernfs_remove+0x140/0x204
CPU:1 ts:5916721 c05ca625  c015ee0c  __mutex_lock_slowpath <- __kernfs_remove+0x148/0x204
CPU:1 ts:5916723 c05c813d  c05ca630  yield_to <- __mutex_lock_slowpath+0x314/0x358
CPU:1 ts:5916724 c05ca2d1  c05ca638  __ww_mutex_lock <- __mutex_lock_slowpath+0x31c/0x358

max_reason

可以通过 max_reason 值来限制存储哪种类型的 kmsg 转储,该值在 include/linux/kmsg_dump.h 的 enum kmsg_dump_reason 中定义。例如,要同时存储 Oops 和 Panic,max_reason 应设置为 2 (KMSG_DUMP_OOPS);若仅存储 Panic,则应设置为 1 (KMSG_DUMP_PANIC)。将其设置为 0 (KMSG_DUMP_UNDEF) 意味着原因过滤将由启动参数 printk.always_kmsg_dump 控制:如果未设置,则为 KMSG_DUMP_OOPS,否则为 KMSG_DUMP_MAX。

驱动配置

设备驱动程序使用带有 struct pstore_device_inforegister_pstore_device 来注册到 pstore/blk。

int register_pstore_device(struct pstore_device_info *dev)

向 pstore/blk 注册非块设备

参数

struct pstore_device_info *dev

非块设备信息

返回

  • 0 - 成功

  • 其他 - 发生错误。

void unregister_pstore_device(struct pstore_device_info *dev)

从 pstore/blk 注销非块设备

参数

struct pstore_device_info *dev

非块设备信息

压缩与头部

块设备对于未压缩的 oops 数据来说足够大。实际上我们不推荐进行数据压缩,因为 pstore/blk 会在 oops/panic 数据的第一行插入一些信息。例如

Panic: Total 16 times

这意味着这是自首次启动以来的第 16 次 OOPS|Panic。有时,自首次启动以来 oops|panic 发生的次数对于判断系统是否稳定非常重要。

下面这一行由 pstore 文件系统插入。例如

Oops#2 Part1

这意味着这是上一次启动时的第 2 次 OOPS。

读取数据

可以从 pstore 文件系统中读取转储数据。这些文件的格式为:oops/panic 前端为 dmesg-pstore-blk-[N],pmsg 前端为 pmsg-pstore-blk-0,依此类推。转储文件的时间戳记录了触发时间。要从块设备中删除存储的记录,只需取消链接(unlink)相应的 pstore 文件即可。

panic 读/写 API 的注意事项

如果发生 panic,内核将无法运行太久,任务将不会被调度,并且大多数内核资源将停止服务。这看起来就像是在单核计算机上运行的单线程程序。

panic 读/写 API 需要特别注意以下几点

  1. 不能分配任何内存。如果你需要内存,请在块驱动程序初始化时进行分配,而不是等到 panic 发生时。

  2. 必须轮询,不能由中断驱动。不再有任务调度。块驱动程序应进行延迟以确保写入成功,但不能休眠。

  3. 不能获取任何锁。没有其他任务,也没有任何共享资源;你可以安全地打破所有锁。

  4. 仅使用 CPU 进行传输。除非你确定 DMA 不会保持锁,否则不要使用 DMA 进行传输。

  5. 直接控制寄存器。请直接控制寄存器,而不是使用 Linux 内核资源。在初始化时进行 I/O 映射,而不是等到 panic 发生。

  6. 必要时重置你的块设备和控制器。如果你不确定 panic 发生时块设备和控制器的状态,停止并重置它们是安全的。

pstore/blk 支持定义在 linux/pstore_blk.h 中的 psblk_blkdev_info(),以获取正在使用的块设备的信息,例如整个磁盘的设备号、扇区数和起始扇区。

pstore block 内部机制

供开发人员参考,以下是所有重要的结构体和 API

struct psz_buffer

要刷新到存储区的区域头部

定义:

struct psz_buffer {
#define PSZ_SIG (0x43474244) ;
    uint32_t sig;
    atomic_t datalen;
    atomic_t start;
    uint8_t data[];
};

成员

sig

用于指示头部的签名(PSZ_SIG 异或 PSZONE 类型值)

datalen

data 中的数据长度

start

data 中存储字节开始处的偏移量

data

区域数据。

struct psz_kmsg_header

要刷新到存储区的 kmsg 转储专用头部

定义:

struct psz_kmsg_header {
#define PSTORE_KMSG_HEADER_MAGIC 0x4dfc3ae5 ;
    uint32_t magic;
    struct timespec64 time;
    bool compressed;
    uint32_t counter;
    enum kmsg_dump_reason reason;
    uint8_t data[];
};

成员

magic

kmsg 转储头部的魔数

time

kmsg 转储触发时间

compressed

是否已压缩

counter

kmsg 转储计数器

reason

kmsg 转储原因(例如 oops、panic 等)

data

指向日志数据的指针

描述

这是 kmsg 转储的子头部,位于 psz_buffer 之后。

struct pstore_zone

单个存储的缓冲区

定义:

struct pstore_zone {
    loff_t off;
    const char *name;
    enum pstore_type_id type;
    struct psz_buffer *buffer;
    struct psz_buffer *oldbuf;
    size_t buffer_size;
    bool should_recover;
    atomic_t dirty;
};

成员

关闭

存储区的区域偏移量

name

该区域的前端名称

type

该区域的前端类型

缓冲区 (buffer)

指向由此区域管理的数据缓冲区的指针

oldbuf

指向旧数据缓冲区的指针

buffer_size

buffer->data 中的字节数

should_recover

此区域是否应从存储区恢复

dirty

buffer 中的数据是否为脏数据

描述

内存中的区域结构体。

struct psz_context

关于 pstore/zone 运行状态的一切

定义:

struct psz_context {
    struct pstore_zone **kpszs;
    struct pstore_zone *ppsz;
    struct pstore_zone *cpsz;
    struct pstore_zone **fpszs;
    unsigned int kmsg_max_cnt;
    unsigned int kmsg_read_cnt;
    unsigned int kmsg_write_cnt;
    unsigned int pmsg_read_cnt;
    unsigned int console_read_cnt;
    unsigned int ftrace_max_cnt;
    unsigned int ftrace_read_cnt;
    unsigned int oops_counter;
    unsigned int panic_counter;
    atomic_t recovered;
    atomic_t on_panic;
    struct mutex pstore_zone_info_lock;
    struct pstore_zone_info *pstore_zone_info;
    struct pstore_info pstore;
};

成员

kpszs

kmsg 转储存储区域

ppsz

pmsg 存储区域

cpsz

控制台存储区域

fpszs

ftrace 存储区域

kmsg_max_cnt

kpszs 的最大计数

kmsg_read_cnt

读取的 kmsg 转储总数计数器

kmsg_write_cnt

kmsg 转储写入总数计数器

pmsg_read_cnt

读取的 pmsg 区域总数计数器

console_read_cnt

读取的控制台区域总数计数器

ftrace_max_cnt

fpszs 的最大计数

ftrace_read_cnt

最大读取 ftrace 区域计数器

oops_counter

oops 转储计数器

panic_counter

panic 转储计数器

recovered

是否已完成从存储区恢复数据

on_panic

是否正在发生 panic

pstore_zone_info_lock

指向 pstore_zone_info 的锁

pstore_zone_info

来自后端的信息

pstore

pstore 的结构体

enum psz_flush_mode

psz_zone_write() 的刷新模式

常量

FLUSH_NONE

不刷新到存储区,但更新内存中的数据

FLUSH_PART

仅将部分数据(包括元数据)刷新到存储区

FLUSH_META

仅将区域的元数据刷新到存储区

FLUSH_ALL

刷新整个区域

int psz_recovery(struct psz_context *cxt)

从存储区恢复数据

参数

struct psz_context *cxt

pstore/zone 的上下文

描述

恢复意味着在重启后从存储区读回数据

返回

成功返回 0,失败返回其他值。

struct pstore_zone_info

pstore/zone 后端驱动结构体

定义:

struct pstore_zone_info {
    struct module *owner;
    const char *name;
    unsigned long total_size;
    unsigned long kmsg_size;
    int max_reason;
    unsigned long pmsg_size;
    unsigned long console_size;
    unsigned long ftrace_size;
    pstore_zone_read_op read;
    pstore_zone_write_op write;
    pstore_zone_erase_op erase;
    pstore_zone_write_op panic_write;
};

成员

owner

负责此后端驱动程序的模块。

name

后端驱动程序的名称。

total_size

pstore/zone 可以使用的总大小(以字节为单位)。它必须大于 4096 且是 4096 的倍数。

kmsg_size

oops/panic 区域的大小。零表示禁用,否则它必须是 SECTOR_SIZE(512 字节)的倍数。

max_reason

要存储的最大 kmsg 转储原因。

pmsg_size

pmsg 区域的大小,与 kmsg_size 相同。

console_size

控制台区域的大小,与 kmsg_size 相同。

ftrace_size

ftrace 区域的大小,与 kmsg_size 相同。

read

通用读操作。函数参数 sizeoffset 都是相对于存储区的相对值。成功时应返回字节数,其他值表示错误。

write

read 相同,但具有以下错误号:-EBUSY 表示稍后重试写入。-ENOMSG 表示尝试下一个区域。

erase

针对具有特殊移除作业的设备的通用擦除操作。函数参数 sizeoffset 都是相对于存储区的相对值。成功返回 0,失败返回其他值。

panic_write

仅用于 panic 情况的写操作。如果你不关心 panic 日志,它是可选的。参数是相对于存储区的相对值。成功时应返回字节数,除 -ENOMSG 外的其他值表示错误。-ENOMSG 表示尝试下一个区域。

struct pstore_device_info

后端 pstore/blk 驱动结构体。

定义:

struct pstore_device_info {
    unsigned int flags;
    struct pstore_zone_info zone;
};

成员

flags

参考定义在 linux/pstore.h 中以 PSTORE_FLAGS 开头的宏。它表示该设备支持哪些前端。零表示为了兼容性支持所有后端。

zone (区域)

struct pstore_zone_info 详情。

struct pstore_blk_config

pstore_blk 后端配置

定义:

struct pstore_blk_config {
    char device[80];
    enum kmsg_dump_reason max_reason;
    unsigned long kmsg_size;
    unsigned long pmsg_size;
    unsigned long console_size;
    unsigned long ftrace_size;
};

成员

device

所需块设备的名称

max_reason

要存储到块设备的最大 kmsg 转储原因

kmsg_size

kmsg 转储的总大小

pmsg_size

pmsg 存储区域的总大小

console_size

控制台存储区域的总大小

ftrace_size

ftrace 日志数据的总大小(适用于所有 CPU)

int pstore_blk_get_config(struct pstore_blk_config *info)

获取 pstore_blk 后端配置的副本

参数

struct pstore_blk_config *info

要填写的 pstore_blk_config 结构体

描述

失败返回负错误码,成功返回 0。