pstore 块 oops/panic 日志记录器

简介

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

mount -t pstore pstore /sys/fs/pstore

pstore 块概念

pstore/blk 为 pstore/blk 提供了高效的配置方法,它将所有配置分为两部分,用户配置和驱动程序配置。

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

驱动程序配置是关于块设备和非块设备的所有内容,例如块设备的总大小和读/写操作。

用户配置

所有这些配置都支持 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。UUID 可以是 EFI/GPT UUID,也可以使用 SSSSSSSS-PP 格式引用 MSDOS 分区,其中 SSSSSSSS 是 32 位“NT 磁盘签名”的零填充十六进制表示,而 PP 是从 1 开始的分区号的零填充十六进制表示。

  3. PARTUUID=<UUID>/PARTNROFF=<int> 选择与具有已知唯一 ID 的分区相关的分区。

  4. <major>:<minor> 设备的 major 和 minor 号,用冒号分隔。

它接受 MTD 设备的以下变体

  1. <device name> MTD 设备名称。“pstore” 是推荐的。

  2. <device number> MTD 设备编号。

kmsg_size

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

根据除其他 pstore 前端之外的剩余空间,oops/panic 前端有多个块。

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 前端类似,根据 cpu 处理器的数量,ftrace 前端有多个块。每个块的大小等于 ftrace_size / processors_count。

所有 ftrace 日志都将附加到该块。在重新启动时,这些内容被组合并在 * /sys/fs/pstore/ftrace-pstore-blk-0 * 中可用。

持久函数跟踪可能有助于调试软件或硬件相关的挂起。这是一个用法示例

# 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 中定义的那样。例如,要同时存储 Oopses 和 Panics,max_reason 应设置为 2 (KMSG_DUMP_OOPS),要仅存储 Panics,max_reason 应设置为 1 (KMSG_DUMP_PANIC)。将其设置为 0 (KMSG_DUMP_UNDEF) 意味着原因过滤将由 printk.always_kmsg_dump 启动参数控制:如果未设置,则为 KMSG_DUMP_OOPS,否则为 KMSG_DUMP_MAX。

驱动程序配置

设备驱动程序使用 register_pstore_devicestruct pstore_device_info 注册到 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 等等。转储文件的时间戳记录触发时间。要从块设备中删除存储的记录,只需取消链接相应的 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 块内部结构

为了方便开发人员参考,以下是所有重要的结构和 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;
};

成员

off

存储的区域偏移量

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

要填充的 sturct pstore_blk_config

描述

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