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=1

B. 使用设备树绑定,如 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;
}
  1. 使用通过 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