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”接受以下变体形式
/dev/<disk_name> 表示磁盘的设备号
/dev/<disk_name><decimal> 表示分区的设备号 - 磁盘的设备号加上分区号
/dev/<disk_name>p<decimal> - 与上述相同;当带分区的磁盘的名称以数字结尾时使用此形式。
当 pstore/blk 被编译进内核时,“blkdev”接受以下变体形式
<hex_major><hex_minor> 十六进制表示的设备号,没有前导的 0x,例如 b302。
PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF 如果分区表提供了分区的唯一 ID,则表示该 ID。UUID 可以是 EFI/GPT UUID,或者使用 SSSSSSSS-PP 格式引用 MSDOS 分区,其中 SSSSSSSS 是 32 位“NT 磁盘签名”的零填充十六进制表示,PP 是从 1 开始的分区号的零填充十六进制表示。
PARTUUID=<UUID>/PARTNROFF=<int> 用于选择相对于具有已知唯一 ID 的分区的某个分区。
<major>:<minor> 设备的主设备号和次设备号,中间用冒号隔开。
对于 MTD 设备,它接受以下变体形式
<device name> MTD 设备名称。建议使用“pstore”。
<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_info 的 register_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 需要特别注意以下几点
不能分配任何内存。如果你需要内存,请在块驱动程序初始化时进行分配,而不是等到 panic 发生时。
必须轮询,不能由中断驱动。不再有任务调度。块驱动程序应进行延迟以确保写入成功,但不能休眠。
不能获取任何锁。没有其他任务,也没有任何共享资源;你可以安全地打破所有锁。
仅使用 CPU 进行传输。除非你确定 DMA 不会保持锁,否则不要使用 DMA 进行传输。
直接控制寄存器。请直接控制寄存器,而不是使用 Linux 内核资源。在初始化时进行 I/O 映射,而不是等到 panic 发生。
必要时重置你的块设备和控制器。如果你不确定 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 类型值)
datalendata 中的数据长度
startdata 中存储字节开始处的偏移量
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[];
};
成员
magickmsg 转储头部的魔数
timekmsg 转储触发时间
compressed是否已压缩
counterkmsg 转储计数器
reasonkmsg 转储原因(例如 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_sizebuffer->data 中的字节数
should_recover此区域是否应从存储区恢复
dirtybuffer 中的数据是否为脏数据
描述
内存中的区域结构体。
-
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;
};
成员
kpszskmsg 转储存储区域
ppszpmsg 存储区域
cpsz控制台存储区域
fpszsftrace 存储区域
kmsg_max_cntkpszs 的最大计数
kmsg_read_cnt读取的 kmsg 转储总数计数器
kmsg_write_cntkmsg 转储写入总数计数器
pmsg_read_cnt读取的 pmsg 区域总数计数器
console_read_cnt读取的控制台区域总数计数器
ftrace_max_cntfpszs 的最大计数
ftrace_read_cnt最大读取 ftrace 区域计数器
oops_counteroops 转储计数器
panic_counterpanic 转储计数器
recovered是否已完成从存储区恢复数据
on_panic是否正在发生 panic
pstore_zone_info_lock指向 pstore_zone_info 的锁
pstore_zone_info来自后端的信息
pstorepstore 的结构体
-
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 *cxtpstore/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_sizepstore/zone 可以使用的总大小(以字节为单位)。它必须大于 4096 且是 4096 的倍数。
kmsg_sizeoops/panic 区域的大小。零表示禁用,否则它必须是 SECTOR_SIZE(512 字节)的倍数。
max_reason要存储的最大 kmsg 转储原因。
pmsg_sizepmsg 区域的大小,与 kmsg_size 相同。
console_size控制台区域的大小,与 kmsg_size 相同。
ftrace_sizeftrace 区域的大小,与 kmsg_size 相同。
read通用读操作。函数参数 size 和 offset 都是相对于存储区的相对值。成功时应返回字节数,其他值表示错误。
write与 read 相同,但具有以下错误号:-EBUSY 表示稍后重试写入。-ENOMSG 表示尝试下一个区域。
erase针对具有特殊移除作业的设备的通用擦除操作。函数参数 size 和 offset 都是相对于存储区的相对值。成功返回 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_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_sizekmsg 转储的总大小
pmsg_sizepmsg 存储区域的总大小
console_size控制台存储区域的总大小
ftrace_sizeftrace 日志数据的总大小(适用于所有 CPU)
-
int pstore_blk_get_config(struct pstore_blk_config *info)¶
获取 pstore_blk 后端配置的副本
参数
struct pstore_blk_config *info要填写的 pstore_blk_config 结构体
描述
失败返回负错误码,成功返回 0。