요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
Ramoops oops/panic logger
=========================
Sergiu Iordache <[email protected]>
Updated: 10 Feb 2021
Introduction
------------
Ramoops is an oops/panic logger that writes its logs to RAM before the system
crashes. It works by logging oopses and panics in a circular buffer. Ramoops
needs a system with persistent RAM so that the content of that area can
survive after a restart.
Ramoops concepts
----------------
Ramoops uses a predefined memory area to store the dump. The start and size
and type of the memory area are set using three variables:
* ``mem_address`` for the start
* ``mem_size`` for the size. The memory size will be rounded down to a
power of two.
* ``mem_type`` to specify if the memory type (default is pgprot_writecombine).
* ``mem_name`` to specify a memory region defined by ``reserve_mem`` command
line parameter.
Typically the default value of ``mem_type=0`` should be used as that sets the pstore
mapping to pgprot_writecombine. Setting ``mem_type=1`` attempts to use
``pgprot_noncached``, which only works on some platforms. This is because pstore
depends on atomic operations. At least on ARM, pgprot_noncached causes the
memory to be mapped strongly ordered, and atomic operations on strongly ordered
memory are implementation defined, and won't work on many ARMs such as omaps.
Setting ``mem_type=2`` attempts to treat the memory region as normal memory,
which enables full cache on it. This can improve the performance.
The memory area is divided into ``record_size`` chunks (also rounded down to
power of two) and each kmesg dump writes a ``record_size`` chunk of
information.
Limiting which kinds of kmsg dumps are stored can be controlled via
the ``max_reason`` value, as defined in include/linux/kmsg_dump.h's
``enum kmsg_dump_reason``. For example, to store both Oopses and Panics,
``max_reason`` should be set to 2 (KMSG_DUMP_OOPS), to store only Panics
``max_reason`` should be set to 1 (KMSG_DUMP_PANIC). Setting this to 0
(KMSG_DUMP_UNDEF), means the reason filtering will be controlled by the
``printk.always_kmsg_dump`` boot param: if unset, it'll be KMSG_DUMP_OOPS,
otherwise KMSG_DUMP_MAX.
The module uses a counter to record multiple dumps but the counter gets reset
on restart (i.e. new dumps after the restart will overwrite old ones).
Ramoops also supports software ECC protection of persistent memory regions.
This might be useful when a hardware reset was used to bring the machine back
to life (i.e. a watchdog triggered). In such cases, RAM may be somewhat
corrupt, but usually it is restorable.
Setting the parameters
----------------------
Setting the ramoops parameters can be done in several different manners:
A. Use the module parameters (which have the names of the variables described
as before). For quick debugging, you can also reserve parts of memory during
boot and then use the reserved memory for ramoops. For example, assuming a
machine with > 128 MB of memory, the following kernel command line will tell
the kernel to use only the first 128 MB of memory, and place ECC-protected
ramoops region at 128 MB boundary::
mem=128M ramoops.mem_address=0x8000000 ramoops.ecc=1
B. Use Device Tree bindings, as described in
``Documentation/devicetree/bindings/reserved-memory/ramoops.yaml``.
For example::
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. Use a platform device and set the platform data. The parameters can then
be set through that platform data. An example of doing that is:
.. code-block:: 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;
}
D. Using a region of memory reserved via ``reserve_mem`` command line
parameter. The address and size will be defined by the ``reserve_mem``
parameter. Note, that ``reserve_mem`` may not always allocate memory
in the same location, and cannot be relied upon. Testing will need
to be done, and it may not work on every machine, nor every kernel.
Consider this a "best effort" approach. The ``reserve_mem`` option
takes a size, alignment and name as arguments. The name is used
to map the memory to a label that can be retrieved by ramoops.
reserve_mem=2M:4096:oops ramoops.mem_name=oops
You can specify either RAM memory or peripheral devices' memory. However, when
specifying RAM, be sure to reserve the memory by issuing memblock_reserve()
very early in the architecture code, e.g.::
#include <linux/memblock.h>
memblock_reserve(ramoops_data.mem_address, ramoops_data.mem_size);
Dump format
-----------
The data dump begins with a header, currently defined as ``====`` followed by a
timestamp and a new line. The dump then continues with the actual data.
Reading the data
----------------
The dump data can be read from the pstore filesystem. The format for these
files is ``dmesg-ramoops-N``, where N is the record number in memory. To delete
a stored record from RAM, simply unlink the respective pstore file.
Persistent function tracing
---------------------------
Persistent function tracing might be useful for debugging software or hardware
related hangs. The functions call chain log is stored in a ``ftrace-ramoops``
file. Here is an example of usage::
# 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
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
소개, memory 구성, ECC
1-58이 문서의 제목은 'Ramoops oops/panic logger'이며 Sergiu Iordache `<[email protected]>`가 작성했고 2021년 2월 10일에 갱신되었습니다.
Ramoops는 system이 crash하기 전에 log를 RAM에 쓰는 oops/panic logger입니다. Oops와 panic을 circular buffer에 기록하며, restart 뒤에도 해당 영역의 content가 살아남는 persistent RAM system이 필요합니다.
Ramoops는 미리 정한 memory area에 dump를 저장합니다. `mem_address`는 시작 주소, `mem_size`는 크기이며 2의 거듭제곱으로 내림합니다. `mem_type`은 memory type을 정하고 기본 mapping은 `pgprot_writecombine`입니다. `mem_name`은 `reserve_mem` command-line parameter로 정의한 memory region을 지정합니다.
일반적으로 `mem_type=0`을 사용해야 합니다. 이 값은 pstore mapping을 `pgprot_writecombine`으로 설정합니다. `mem_type=1`은 `pgprot_noncached`를 시도하지만 일부 platform에서만 동작합니다.
제한의 이유는 pstore가 atomic operation에 의존하기 때문입니다. 적어도 ARM에서 `pgprot_noncached`는 memory를 strongly ordered로 mapping하고, strongly ordered memory의 atomic operation은 implementation-defined라 OMAP을 포함한 많은 ARM에서 동작하지 않습니다.
`mem_type=2`는 memory region을 normal memory로 취급해 full cache를 사용하며 성능을 높일 수 있습니다.
Memory area는 `record_size` chunk로 나뉘며 이 크기도 2의 거듭제곱으로 내림합니다. Kmesg dump 하나는 `record_size`만큼의 정보 chunk 하나를 기록합니다.
저장할 kmsg dump 종류는 `include/linux/kmsg_dump.h`의 `enum kmsg_dump_reason`에 정의된 `max_reason`으로 제한합니다. Oops와 panic을 모두 저장하려면 `max_reason=2`, 즉 `KMSG_DUMP_OOPS`를 사용하고 panic만 저장하려면 `max_reason=1`, 즉 `KMSG_DUMP_PANIC`을 사용합니다.
`max_reason=0`, 즉 `KMSG_DUMP_UNDEF`이면 `printk.always_kmsg_dump` boot parameter가 reason filtering을 제어합니다. Parameter가 설정되지 않으면 `KMSG_DUMP_OOPS`, 설정되면 `KMSG_DUMP_MAX`가 됩니다.
Module은 counter로 여러 dump를 기록하지만 restart 때 counter가 reset되므로 restart 뒤의 새 dump가 이전 dump를 덮어씁니다.
Ramoops는 persistent memory region의 software ECC 보호도 지원합니다. Watchdog trigger 같은 hardware reset으로 machine을 되살렸을 때 RAM이 일부 손상될 수 있지만 대개 복구 가능하므로 유용합니다.
Parameter 설정 방법
59-141Ramoops parameter는 여러 방식으로 설정할 수 있습니다. 첫째, 앞에서 설명한 variable과 같은 이름의 module parameter를 사용합니다. 빠른 debugging에서는 boot 중 memory 일부를 예약하고 그 영역을 ramoops에 쓸 수도 있습니다.
Memory가 128MB보다 큰 machine에서 다음 kernel command line은 kernel이 처음 128MB만 사용하게 하고, 128MB 경계에 ECC로 보호한 ramoops region을 둡니다.
mem=128M ramoops.mem_address=0x8000000 ramoops.ecc=1
둘째, `Documentation/devicetree/bindings/reserved-memory/ramoops.yaml`에 설명된 Device Tree binding을 사용합니다. 다음 예제는 `0x8f000000`에서 1MB를 예약하고 record와 console에 각각 `0x4000` byte를 할당합니다.
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
ramoops@8f000000 {
compatible = "ramoops";
reg = <0 0x8f000000 0 0x100000>;
record-size = <0x4000>;
console-size = <0x4000>;
};
};
셋째, platform device를 만들고 platform data로 parameter를 설정합니다. 다음 C 예제는 `struct ramoops_platform_data`에 memory, record, dump reason, ECC 값을 넣고 `platform_device_register()`로 `ramoops` device를 등록합니다.
#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` command-line parameter로 예약한 memory region을 사용합니다. Address와 size는 `reserve_mem`이 정합니다. 다만 이 옵션은 항상 같은 위치에 memory를 할당하지 않아 신뢰할 수 없고 machine이나 kernel에 따라 동작하지 않을 수 있으므로 test가 필요합니다. Best-effort 방식으로 취급해야 합니다.
`reserve_mem` option은 size, alignment, name을 argument로 받습니다. Name은 memory를 ramoops가 가져올 수 있는 label에 mapping합니다.
reserve_mem=2M:4096:oops ramoops.mem_name=oops
RAM memory와 peripheral device memory를 모두 지정할 수 있습니다. RAM을 지정한다면 architecture code의 매우 이른 시점에 `memblock_reserve()`를 호출해 반드시 그 memory를 예약해야 합니다.
#include <linux/memblock.h>
memblock_reserve(ramoops_data.mem_address, ramoops_data.mem_size);
Dump 형식, 읽기, 삭제
142-154Data dump는 현재 `====`로 정의된 header로 시작하고 그 뒤에 timestamp와 newline이 옵니다. 이어서 실제 data가 기록됩니다.
Dump data는 pstore filesystem에서 읽습니다. File 형식은 `dmesg-ramoops-N`이며 `N`은 memory의 record number입니다. RAM에 저장된 record를 삭제하려면 해당 pstore file을 unlink하면 됩니다.
Persistent function tracing
155-177Persistent function tracing은 software 또는 hardware 관련 hang을 debug할 때 유용할 수 있습니다. Function call chain log는 `ftrace-ramoops` file에 저장됩니다. 다음은 사용 예제와 실제 trace 일부입니다.
# 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
Ramoops는 강제 reboot를 가로질러 function call chain을 persistent RAM에 남깁니다.
요약과 해설
ramoops.rst:1-177Ramoops는 crash 직전의 kmsg와 function trace를 persistent RAM에 기록해 reboot 뒤 pstore에서 회수합니다. 저장 영역은 고정 크기 record로 나뉘며 restart 뒤 counter가 초기화되면 새 dump가 오래된 record를 덮어쓸 수 있습니다.
안정적인 구성에서는 platform의 atomic operation 특성에 맞는 `mem_type`을 고르고 memory reservation이 다른 allocator와 충돌하지 않게 해야 합니다. `reserve_mem`은 위치가 달라질 수 있으므로 고정 Device Tree나 platform data보다 신뢰도가 낮은 best-effort 선택입니다.