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 上(例如 omaps)將無法工作。設定 mem_type=2 嘗試將記憶體區域視為普通記憶體,這會啟用其上的完全快取。這可以提高效能。

記憶體區域被劃分為 record_size 個塊(也向下舍入為 2 的冪),並且每個 kmesg 轉儲寫入一個 record_size 大小的資訊塊。

可以透過 max_reason 值來控制儲存哪種型別的 kmsg 轉儲,如 include/linux/kmsg_dump.h 的 enum kmsg_dump_reason 中所定義的那樣。例如,要同時儲存 Oops 和 Panic,應將 max_reason 設定為 2 (KMSG_DUMP_OOPS),要僅儲存 Panic,應將 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