Ramoops oops/panic 日志记录器¶
Sergiu Iordache <sergiu@chromium.org>
更新时间:2021 年 2 月 10 日
介绍¶
Ramoops 是一个 oops/panic 日志记录器,它在系统崩溃之前将其日志写入 RAM。它的工作原理是将 oops 和 panic 记录在循环缓冲区中。Ramoops 需要具有持久 RAM 的系统,以便该区域的内容在重启后仍然存在。
Ramoops 概念¶
Ramoops 使用预定义的内存区域来存储转储。内存区域的起始地址、大小和类型使用三个变量设置
mem_address
表示起始地址
mem_size
表示大小。内存大小将向下舍入为 2 的幂。
mem_type
用于指定内存类型 (默认为 pgprot_writecombine)。
mem_name
用于指定由reserve_mem
命令行参数定义的内存区域。
通常,应使用 mem_type=0
的默认值,因为这会将 pstore 映射设置为 pgprot_writecombine。设置 mem_type=1
会尝试使用 pgprot_noncached
,这仅在某些平台上有效。这是因为 pstore 依赖于原子操作。至少在 ARM 上,pgprot_noncached 会导致内存被映射为强顺序,而对强顺序内存的原子操作是实现定义的,并且在许多 ARM(如 omap)上不起作用。设置 mem_type=2
会尝试将内存区域视为普通内存,这使其能够使用完整缓存。这可以提高性能。
内存区域分为 record_size
块(也向下舍入为 2 的幂),每个 kmesg 转储写入一个 record_size
块的信息。
可以通过 max_reason
值来控制存储哪些类型的 kmsg 转储,如 include/linux/kmsg_dump.h 的 enum kmsg_dump_reason
中定义的那样。例如,要存储 Oops 和 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。
该模块使用一个计数器来记录多个转储,但计数器在重启时重置(即,重启后的新转储将覆盖旧转储)。
Ramoops 还支持持久内存区域的软件 ECC 保护。当使用硬件重置使机器恢复运行时(即触发了看门狗),这可能很有用。在这种情况下,RAM 可能有些损坏,但通常是可以恢复的。
设置参数¶
可以通过几种不同的方式设置 ramoops 参数
A. 使用模块参数(其名称与之前描述的变量相同)。对于快速调试,您还可以在启动期间保留部分内存,然后将保留的内存用于 ramoops。例如,假设一台具有 > 128 MB 内存的机器,以下内核命令行将告知内核仅使用前 128 MB 内存,并将受 ECC 保护的 ramoops 区域放置在 128 MB 边界
mem=128M ramoops.mem_address=0x8000000 ramoops.ecc=1B. 使用设备树绑定,如
Documentation/devicetree/bindings/reserved-memory/ramoops.yaml
中所述。例如reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; ramoops@8f000000 { compatible = "ramoops"; reg = <0 0x8f000000 0 0x100000>; record-size = <0x4000>; console-size = <0x4000>; }; };C. 使用平台设备并设置平台数据。然后可以通过该平台数据设置参数。执行此操作的示例是
#include <linux/pstore_ram.h> [...] static struct ramoops_platform_data ramoops_data = { .mem_size = <...>, .mem_address = <...>, .mem_type = <...>, .record_size = <...>, .max_reason = <...>, .ecc = <...>, }; static struct platform_device ramoops_dev = { .name = "ramoops", .dev = { .platform_data = &ramoops_data, }, }; [... inside a function ...] int ret; ret = platform_device_register(&ramoops_dev); if (ret) { printk(KERN_ERR "unable to register platform device\n"); return ret; }
使用通过
reserve_mem
命令行参数保留的内存区域。地址和大小将由reserve_mem
参数定义。请注意,reserve_mem
可能不会始终在同一位置分配内存,并且不能依赖它。需要进行测试,并且它可能无法在每台机器上或每个内核上工作。请将其视为“尽力而为”的方法。reserve_mem
选项将大小、对齐方式和名称作为参数。该名称用于将内存映射到 ramoops 可以检索的标签。reserve_mem=2M:4096:oops ramoops.mem_name=oops
您可以指定 RAM 内存或外围设备的内存。但是,在指定 RAM 时,请务必在架构代码中尽早通过发出 memblock_reserve() 来保留内存,例如
#include <linux/memblock.h>
memblock_reserve(ramoops_data.mem_address, ramoops_data.mem_size);
转储格式¶
数据转储以标头开始,当前定义为 ====
,后跟时间戳和换行符。然后,转储将继续使用实际数据。
读取数据¶
可以从 pstore 文件系统读取转储数据。这些文件的格式为 dmesg-ramoops-N
,其中 N 是内存中的记录号。要从 RAM 中删除存储的记录,只需取消链接相应的 pstore 文件即可。
持久函数跟踪¶
持久函数跟踪可能有助于调试软件或硬件相关的挂起。函数调用链日志存储在 ftrace-ramoops
文件中。以下是使用示例
# mount -t debugfs debugfs /sys/kernel/debug/
# echo 1 > /sys/kernel/debug/pstore/record_ftrace
# reboot -f
[...]
# mount -t pstore pstore /mnt/
# tail /mnt/ftrace-ramoops
0 ffffffff8101ea64 ffffffff8101bcda native_apic_mem_read <- disconnect_bsp_APIC+0x6a/0xc0
0 ffffffff8101ea44 ffffffff8101bcf6 native_apic_mem_write <- disconnect_bsp_APIC+0x86/0xc0
0 ffffffff81020084 ffffffff8101a4b5 hpet_disable <- native_machine_shutdown+0x75/0x90
0 ffffffff81005f94 ffffffff8101a4bb iommu_shutdown_noop <- native_machine_shutdown+0x7b/0x90
0 ffffffff8101a6a1 ffffffff8101a437 native_machine_emergency_restart <- native_machine_restart+0x37/0x40
0 ffffffff811f9876 ffffffff8101a73a acpi_reboot <- native_machine_emergency_restart+0xaa/0x1e0
0 ffffffff8101a514 ffffffff8101a772 mach_reboot_fixups <- native_machine_emergency_restart+0xe2/0x1e0
0 ffffffff811d9c54 ffffffff8101a7a0 __const_udelay <- native_machine_emergency_restart+0x110/0x1e0
0 ffffffff811d9c34 ffffffff811d9c80 __delay <- __const_udelay+0x30/0x40
0 ffffffff811d9d14 ffffffff811d9c3f delay_tsc <- __delay+0xf/0x20