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