MCE (Machine Check Exception)
x86 Machine Check Architecture(MCA)의 뱅크/레지스터(Register) 구조, MCE 심각도 분류(SRAR/SRAO/UCNA), 커널 처리 경로(Monarch 랑데부), CMCI 스톰, AMD SMCA, IIO(Integrated I/O) PCIe 에러 아키텍처(Completion Timeout, Poisoned TLP, Surprise Link Down, DMA/VT-d 에러), Uncore MCA 에러 종합(M2M/CHA/UPI/PCU/TOR/MDF), KVM 가상화(Virtualization) MCE 전파(VMEXIT, 가상 MCE 뱅크, HWPoison→게스트), APEI/GHES 펌웨어(Firmware) 연동, EDAC DIMM 디코드, memory_failure()/HWPoison 복구, GPU/가속기 연쇄 에러 분석(Xid↔IIO MCE 상관분석, AI/ML 클러스터 영향), ARM/RISC-V/POWER EEH 비교, DDR5 On-Die ECC, Row Hammer, MCE 디버깅(Debugging) 기법(perf/ftrace/eBPF), 로그 디코딩 실전과 운영 플레이북까지 다룹니다.
핵심 요약
- Machine Check Exception — x86 벡터 18번 예외로, CPU/메모리/버스(Bus)의 하드웨어 오류를 보고합니다. CR4.MCE=1일 때만 활성화됩니다.
- MCA 뱅크 — CPU 내부의 에러 감지 유닛(L1/L2/L3 캐시(Cache), 메모리 컨트롤러, 인터커넥트 등)이 각각 독립 레지스터 세트를 가집니다.
- 심각도 3단계 — SRAR(즉시 복구 필요), SRAO(백그라운드 처리), UCNA(정보 전달만): 커널은 STATUS 비트로 자동 판별합니다.
- Monarch 메커니즘 — MCE 발생 시 모든 CPU를 동기화하고, 한 CPU(Monarch)가 최종 의사결정(패닉/복구/무시)을 내립니다.
- memory_failure() — HWPoison으로 물리 페이지(Page)를 오프라인 처리하고, 해당 메모리를 사용하던 프로세스에 SIGBUS를 전달합니다.
단계별 이해
- 아키텍처와 레지스터
MCE 예외 위치, MCA 뱅크·MCi_STATUS 비트 필드·에러 코드 체계를 한 번에 정리합니다. - 커널 처리 경로
하드웨어 예외 → 커널 핸들러(Handler) → Monarch 선출의 흐름을 따라가며 의사결정 지점을 파악합니다. - 복구 메커니즘
memory_failure()·HWPoison·CMCI로 에러 복구와 교정 인터럽트를 연결합니다. - IIO / Uncore / 가상화
PCIe IIO 에러, M2M/CHA/UPI 계열 Uncore 에러, KVM·GPU 전파 사례를 비교합니다. - 플랫폼 통합과 운영
APEI/GHES, EDAC, AMD SMCA 경로를 익히고 로그 디코딩·perf/eBPF 진단·플레이북으로 운영을 마무리합니다.
MCE 아키텍처 개요
Machine Check Exception(MCE)은 x86 아키텍처에서 벡터 18(#MC)로 정의된 abort 클래스 예외입니다. CPU 내부 또는 외부에서 발생하는 하드웨어 오류 — ECC 메모리 오류, 캐시 패리티 오류, 버스 타임아웃, TLB 오류 등 — 를 소프트웨어에 알려주는 유일한 표준 메커니즘입니다.
예외 특성
| 속성 | 값 | 의미 |
|---|---|---|
| 벡터 번호 | 18 (#MC) | IDT 엔트리 18 |
| 클래스 | Abort | 재시작(Reboot) 불가능할 수 있음 |
| 에러 코드 | 없음 | 정보는 MSR(MCA 뱅크)에 저장 |
| IST | 일반적으로 IST 3 | 전용 스택에서 실행 |
| 활성화 조건 | CR4.MCE = 1 | 부팅 시 커널이 설정 |
| 마스킹 | 불가 | cli/sti로 차단 불가 |
MCE의 위치: x86 예외 계층
MCE는 동기(synchronous)와 비동기(asynchronous) 모두 가능합니다. 명령어 실행 중 발견된 메모리 오류(SRAR)는 동기적이고, 백그라운드 스크러빙이 발견한 오류(SRAO)는 비동기적입니다. 커널은 동기 MCE를 더 긴급하게 처리합니다.
/* arch/x86/kernel/cpu/mce/core.c — MCE 활성화 */
void mcheck_cpu_init(struct cpuinfo_x86 *c)
{
/* CR4.MCE 비트 설정 — 이것이 없으면 MCE 시 triple fault → 리셋 */
cr4_set_bits(X86_CR4_MCE);
/* 각 MCA 뱅크의 CTL 레지스터를 0xFFFFFFFFFFFFFFFF로 설정 (모든 에러 활성화) */
for (i = 0; i < num_banks; i++)
wrmsrl(MSR_IA32_MCx_CTL(i), 0xFFFFFFFFFFFFFFFFULL);
}
MCA 뱅크와 레지스터
MCA(Machine Check Architecture)는 CPU 내부의 에러 감지 유닛을 뱅크(bank) 단위로 구성합니다. 각 뱅크는 특정 하드웨어 컴포넌트(L1 캐시, L2 캐시, 메모리 컨트롤러, QPI/UPI 등)에 매핑(Mapping)되며, 독립적인 레지스터 세트를 가집니다.
글로벌 레지스터
| MSR | 주소 | 역할 |
|---|---|---|
| MCG_CAP | 0x179 | 뱅크 수(Count), 확장 기능 플래그(MCG_EXT_P, MCG_SER_P, MCG_ELOG_P, MCG_LMCE_P) |
| MCG_STATUS | 0x17A | RIPV(Restart IP Valid), EIPV(Error IP Valid), MCIP(MC In Progress), LMCE_S |
| MCG_CTL | 0x17B | MCG_CTL_P=1일 때만 존재. 모든 뱅크의 글로벌 활성화/비활성화 |
| MCG_EXT_CTL | 0x4D0 | LMCE_EN 비트 — Local MCE 활성화 |
뱅크별 레지스터 (i = 0..N-1)
| MSR | 주소 공식 | 역할 |
|---|---|---|
| MCi_CTL | 0x400 + 4*i | 에러 유형별 활성화/비활성화 비트마스크 |
| MCi_STATUS | 0x401 + 4*i | 에러 정보 — 가장 핵심적인 레지스터 |
| MCi_ADDR | 0x402 + 4*i | 에러 발생 주소 (ADDRV=1일 때 유효) |
| MCi_MISC | 0x403 + 4*i | 추가 정보 — 주소 모드, LSB 등 (MISCV=1일 때 유효) |
| MCi_CTL2 | 0x280 + i | CMCI 임계값 설정, CMCI_EN 비트 |
/* arch/x86/kernel/cpu/mce/core.c — 뱅크 스캔 */
static void __mcheck_cpu_mce_banks_init(void)
{
int i;
u64 cap;
rdmsrl(MSR_IA32_MCG_CAP, cap);
num_banks = cap & MCG_BANKCNT_MASK; /* bits [7:0] */
for (i = 0; i < num_banks; i++) {
struct mce_bank *b = &mce_banks[i];
b->ctl = ~(u64)0; /* 모든 에러 유형 활성화 */
wrmsrl(MSR_IA32_MCx_CTL(i), b->ctl);
}
}
cat /sys/devices/system/machinecheck/machinecheck0/bank*로
각 뱅크의 CTL 값을 확인하고, mcelog --client 또는 rasdaemon으로
뱅크별 에러 발생 이력을 추적할 수 있습니다.
MCA 뱅크 레지스터 상세 레이아웃
각 MCA 뱅크의 4개 핵심 레지스터(CTL/STATUS/ADDR/MISC)는 서로 다른 역할을 수행하며, 에러 처리 과정에서 순차적으로 참조됩니다. 아래 다이어그램은 단일 뱅크의 레지스터 간 데이터 흐름과 비트 필드 구조를 상세하게 보여줍니다.
커널 모듈(Kernel Module)에서 MCA 뱅크 MSR 읽기
다음은 커널 모듈에서 직접 MCA 뱅크 레지스터를 읽어 에러 상태를 진단하는 예제입니다.
프로덕션 환경에서는 rasdaemon이나 perf를 사용하는 것이 권장되지만,
디버깅이나 커스텀 RAS 모니터링 도구 개발 시 직접 MSR을 읽어야 할 때가 있습니다.
/* mce_reader.c — MCA 뱅크 MSR을 직접 읽는 커널 모듈 예제 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/msr.h>
#include <asm/mce.h>
#define MSR_IA32_MCG_CAP 0x179
#define MSR_IA32_MCG_STATUS 0x17A
#define MCG_BANKCNT_MASK 0xFF
/* MCi_STATUS 비트 매크로 */
#define MCI_STATUS_VAL (1ULL << 63)
#define MCI_STATUS_OVER (1ULL << 62)
#define MCI_STATUS_UC (1ULL << 61)
#define MCI_STATUS_EN (1ULL << 60)
#define MCI_STATUS_MISCV (1ULL << 59)
#define MCI_STATUS_ADDRV (1ULL << 58)
#define MCI_STATUS_PCC (1ULL << 57)
#define MCI_STATUS_S (1ULL << 56)
#define MCI_STATUS_AR (1ULL << 55)
static void decode_mci_status(int bank, u64 status)
{
if (!(status & MCI_STATUS_VAL)) {
pr_info(" Bank %d: 에러 없음 (VAL=0)\n", bank);
return;
}
pr_info(" Bank %d: STATUS=0x%016llx\n", bank, status);
pr_info(" VAL=%d OVER=%d UC=%d EN=%d MISCV=%d ADDRV=%d PCC=%d S=%d AR=%d\n",
!!(status & MCI_STATUS_VAL),
!!(status & MCI_STATUS_OVER),
!!(status & MCI_STATUS_UC),
!!(status & MCI_STATUS_EN),
!!(status & MCI_STATUS_MISCV),
!!(status & MCI_STATUS_ADDRV),
!!(status & MCI_STATUS_PCC),
!!(status & MCI_STATUS_S),
!!(status & MCI_STATUS_AR));
/* MCA 에러 코드 (하위 16비트) */
pr_info(" MCA Error Code: 0x%04x, MSCOD: 0x%04x\n",
(u16)(status & 0xFFFF),
(u16)((status >> 16) & 0xFFFF));
/* 심각도 판별 */
if (status & MCI_STATUS_UC) {
if (status & MCI_STATUS_PCC)
pr_err(" 심각도: UC+PCC → 프로세서 컨텍스트 손상 (PANIC 대상)\n");
else if ((status & MCI_STATUS_S) && (status & MCI_STATUS_AR))
pr_warn(" 심각도: SRAR → 즉시 복구 필요\n");
else if (status & MCI_STATUS_S)
pr_warn(" 심각도: SRAO → 백그라운드 처리\n");
else
pr_info(" 심각도: UCNA → 정보성 보고\n");
} else {
pr_info(" 심각도: CE → 하드웨어 자동 교정\n");
}
}
static int __init mce_reader_init(void)
{
u64 cap, status, addr, misc;
int num_banks, i;
/* 1. MCG_CAP에서 뱅크 수 읽기 */
rdmsrl(MSR_IA32_MCG_CAP, cap);
num_banks = cap & MCG_BANKCNT_MASK;
pr_info("MCE Reader: MCG_CAP=0x%016llx, 뱅크 수=%d\n", cap, num_banks);
pr_info(" MCG_EXT_P=%d MCG_SER_P=%d MCG_ELOG_P=%d MCG_LMCE_P=%d\n",
!!(cap & (1ULL << 9)), /* MCG_EXT_P */
!!(cap & (1ULL << 24)), /* MCG_SER_P: Software Error Recovery */
!!(cap & (1ULL << 26)), /* MCG_ELOG_P: Extended Error Logging */
!!(cap & (1ULL << 27))); /* MCG_LMCE_P: Local MCE */
/* 2. 각 뱅크의 STATUS/ADDR/MISC 읽기 */
for (i = 0; i < num_banks; i++) {
rdmsrl(MSR_IA32_MCx_STATUS(i), status);
decode_mci_status(i, status);
if ((status & MCI_STATUS_VAL) && (status & MCI_STATUS_ADDRV)) {
rdmsrl(MSR_IA32_MCx_ADDR(i), addr);
pr_info(" ADDR=0x%016llx (PFN=0x%lx)\n",
addr, (unsigned long)(addr >> PAGE_SHIFT));
}
if ((status & MCI_STATUS_VAL) && (status & MCI_STATUS_MISCV)) {
rdmsrl(MSR_IA32_MCx_MISC(i), misc);
pr_info(" MISC=0x%016llx (AddrLSB=%d, AddrMode=%d)\n",
misc, (int)(misc & 0x3F), (int)((misc >> 6) & 7));
}
}
return 0;
}
static void __exit mce_reader_exit(void)
{
pr_info("MCE Reader: 모듈 언로드\n");
}
module_init(mce_reader_init);
module_exit(mce_reader_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MCA Bank Register Reader for Diagnostics");
IA32_MCi_STATUS 비트 필드 상세
MCi_STATUS는 MCA의 핵심 레지스터로, 64비트 전체가 에러 정보를 담고 있습니다. 커널의 MCE 핸들러는 이 레지스터의 비트 조합으로 에러의 종류, 심각도, 복구 가능성을 판단합니다.
| 비트 | 이름 | 의미 | 1일 때 동작 |
|---|---|---|---|
| 63 | VAL | STATUS 유효성 | 이 레지스터에 유효한 에러 정보가 있음 |
| 62 | OVER | 오버플로 | 이전 에러를 읽기 전에 새 에러 발생 — 정보 손실 가능 |
| 61 | UC | Uncorrected | 하드웨어가 자동 교정하지 못한 에러 |
| 60 | EN | Error Enabled | MCi_CTL에서 이 에러 유형이 활성화된 상태 |
| 59 | MISCV | MISC Valid | MCi_MISC 레지스터에 추가 정보 있음 |
| 58 | ADDRV | Address Valid | MCi_ADDR에 에러 주소가 유효 |
| 57 | PCC | Processor Context Corrupt | CPU 내부 상태 손상 — 안전한 실행 계속 불가 |
| 56 | S | Signaling (SER_P) | 소프트웨어 복구 가능성 표시 (MCG_SER_P 필요) |
| 55 | AR | Action Required | 즉시 조치 필요 (S=1이어야 의미 있음) |
| 15:0 | MCA Error Code | 표준 에러 코드 | 에러 유형 분류 (Simple/Compound) |
MCA 에러 코드 분류
MCi_STATUS의 하위 16비트(bits 15:0)는 MCA 에러 코드로, Intel SDM에서 정의한 표준 포맷을 따릅니다. 크게 Simple Error Code와 Compound Error Code로 나뉩니다.
Simple Error Code (단순 에러 코드)
| 코드 | 이름 | 설명 |
|---|---|---|
| 0x0000 | No Error | 에러 없음 |
| 0x0001 | Unclassified | 분류 불가 에러 |
| 0x0002 | Microcode ROM Parity | 마이크로코드 ROM 패리티 에러 |
| 0x0003 | External | 외부 소스 에러 |
| 0x0004 | FRC | FRC(Functional Redundancy Check) 에러 |
| 0x0005 | Internal Parity | 내부 패리티 에러 |
| 0x0400 | Internal Timer | 내부 타이머(Timer) 에러 |
Compound Error Code (복합 에러 코드)
복합 에러 코드는 000F 0FTT LLLL 형식의 비트 패턴으로 인코딩됩니다.
| 필드 | 비트 | 값 | 의미 |
|---|---|---|---|
| LLLL (Level) | 3:0 | 0000 | Level 0 |
| 0001 | Level 1 | ||
| 0010 | Level 2 | ||
| 0011 | Generic / Level 3 | ||
| 0100~1111 | 예약 | ||
| TT (Transaction) | 5:4 | 00 | Instruction |
| 01 | Data | ||
| 10 | Generic | ||
| 11 | 예약 | ||
| RRRR (Request) | 11:8 | 0000 | Generic Error (ERR) |
| 0001 | Generic Read (RD) | ||
| 0010 | Generic Write (WR) | ||
| 0011 | Data Read (DRD) | ||
| 0100 | Data Write (DWR) | ||
| CCCC (Channel) | Varies | memory hierarchy | 캐시/메모리 계층 에러 |
| bus/interconnect | 버스/인터커넥트 에러 | ||
| timeout | 타임아웃 에러 |
0x0011— L1 캐시 데이터 읽기 에러0x0019— L1 캐시 명령어 프리페치 에러0x00C0— L2 캐시 에러0x0150— 메모리 컨트롤러 에러 (DIMM ECC)0x0800— 버스/인터커넥트 에러
/* arch/x86/kernel/cpu/mce/severity.c — 에러 코드 디코딩 */
#define MCESEV(s, m, c...) { .sev = MCE_##s##_SEVERITY, .msg = m, ##c }
static struct severity {
u64 mask;
u64 result;
unsigned char sev;
unsigned char mcgmask;
unsigned char mcgres;
char *msg;
} severities[] = {
MCESEV(NO, "Invalid",
SER, BITCLR(MCI_STATUS_VAL)),
MCESEV(NO, "Not enabled",
SER, BITCLR(MCI_STATUS_EN)),
MCESEV(PANIC, "Processor context corrupt",
BITSET(MCI_STATUS_PCC)),
/* ... */
};
메모리 관련 에러 코드 상세
메모리 컨트롤러에서 발생하는 에러는 가장 흔한 MCE 원인입니다. 다음은 Intel/AMD 메모리 컨트롤러 뱅크에서 자주 보이는 에러 코드 패턴입니다.
| STATUS 하위 16비트 | 에러 분류 | 일반적 원인 |
|---|---|---|
0x0001 | Unclassified | 분류 불가 내부 에러 |
0x0010 | Generic TLB Error | TLB 패리티/ECC 에러 |
0x0011 | L1 Data TLB Read | L1 DTLB 읽기 에러 |
0x0012 | L2 Data TLB Read | L2 DTLB 읽기 에러 |
0x0100 | Generic Cache L0 | L0 마이크로-op 캐시 에러 (AMD) |
0x0110 | L1 Cache Generic | L1 캐시 일반 에러 |
0x0111 | L1 Cache Data Read | L1D 읽기 에러 |
0x0120 | L2 Cache Generic | L2 캐시 일반 에러 |
0x0150 | Memory Controller | DRAM ECC 에러 (가장 흔함) |
0x0151 | Memory Read | DRAM 읽기 ECC 에러 |
0x0152 | Memory Write | DRAM 쓰기 에러 |
0x0800 | Bus/Interconnect | QPI/UPI 링크 에러 |
0x0C0F | L3 Cache Generic | LLC 에러 |
mcelog --ascii나 edac-util이 자동으로 에러 코드를 해석해줍니다.
수동 해석이 필요하면 Intel SDM Vol. 3B, Chapter 16 "Machine-Check Architecture"를 참조하세요.
MCE 심각도 분류
커널 함수 mce_severity()는 MCi_STATUS 비트를 분석하여 에러의 심각도를 결정합니다.
Intel의 Software Developer's Manual에서 정의한 4가지 등급과 커널의 내부 분류가 있습니다.
Intel MCA Recovery 분류
| 분류 | UC | S | AR | PCC | 의미 | 커널 동작 |
|---|---|---|---|---|---|---|
| SRAR | 1 | 1 | 1 | 0 | Software Recoverable Action Required — 즉시 복구 필요 | memory_failure() 호출, 프로세스에 SIGBUS |
| SRAO | 1 | 1 | 0 | 0 | Software Recoverable Action Optional — 백그라운드 처리 | memory_failure() 오프라인, 워크큐에서 처리 |
| UCNA | 1 | 0 | 0 | 0 | Uncorrected No Action — 정보만 전달 | 로그 기록, CMCI로 전달 |
| CE | 0 | - | - | - | Corrected Error — 하드웨어가 자동 교정 | 로그 기록, CMCI/폴링(Polling)으로 수집 |
| PCC=1 | 1 | - | - | 1 | Processor Context Corrupt | 무조건 패닉 (tolerant=3 제외) |
/* arch/x86/kernel/cpu/mce/severity.c */
static int mce_severity_intel(struct mce *m, struct pt_regs *regs,
char **msg, bool is_excp)
{
/* severities[] 테이블을 순회하며 mask/result 매칭 */
for (struct severity *s = severities;; s++) {
if ((m->status & s->mask) != s->result)
continue;
if ((m->mcgstatus & s->mcgmask) != s->mcgres)
continue;
*msg = s->msg;
return s->sev;
}
}
severities[] 테이블 상세 — 매칭 순서와 우선순위(Priority)
커널의 severities[] 배열은 위에서 아래로 순차 검사되며, 첫 번째 매칭이
최종 심각도를 결정합니다. 따라서 배열 순서가 곧 우선순위입니다.
아래는 최근 6.x 계열의 주요 엔트리와 매칭 조건입니다.
/* arch/x86/kernel/cpu/mce/severity.c — severities[] 테이블 (핵심 엔트리) */
static struct severity severities[] = {
/* --- 무효/비활성 에러 → 무시 --- */
MCESEV(NO, "Invalid",
SER, BITCLR(MCI_STATUS_VAL)), /* VAL=0: 유효하지 않음 */
MCESEV(NO, "Not enabled",
SER, BITCLR(MCI_STATUS_EN)), /* EN=0: 보고 비활성 */
/* --- 무조건 패닉 --- */
MCESEV(PANIC, "Processor context corrupt",
BITSET(MCI_STATUS_PCC)), /* PCC=1: 어떤 조건이든 패닉 */
/* --- 오버플로 + UC → 패닉 --- */
MCESEV(PANIC, "Overflowed uncorrected",
EXCP, BITSET(MCI_STATUS_OVER | MCI_STATUS_UC)),
/* --- SRAR (Action Required) --- */
MCESEV(AR, "Action required: data load error in user process",
SER, BITSET(MCI_STATUS_UC | MCI_STATUS_S | MCI_STATUS_AR),
USER), /* 사용자 모드에서 데이터 소비 */
MCESEV(AR, "Action required with lost events",
SER, BITSET(MCI_STATUS_UC | MCI_STATUS_S | MCI_STATUS_AR | MCI_STATUS_OVER)),
/* --- SRAO (Action Optional) --- */
MCESEV(UC, "Action optional: memory scrubbing error",
SER, BITSET(MCI_STATUS_UC | MCI_STATUS_S), BITCLR(MCI_STATUS_AR),
MASK(MCI_UC_SAO, MCI_STATUS_MCA)), /* 패트롤 스크러빙 에러 */
/* --- UCNA (No Action) --- */
MCESEV(UCNA, "Uncorrected no action required",
SER, BITSET(MCI_STATUS_UC), BITCLR(MCI_STATUS_S)),
/* --- CE (Corrected) --- */
MCESEV(KEEP, "Corrected error",
BITCLR(MCI_STATUS_UC)), /* UC=0: 하드웨어 자동 교정 */
/* --- 기본값 (매칭 실패 시) --- */
MCESEV(SOME, "Machine check"),
};
커널 MCE 진입 경로
MCE가 발생하면 CPU는 IDT의 벡터 18 핸들러로 점프합니다. 리눅스 커널 6.x에서의 진입 경로는 어셈블리(Assembly) 엔트리 포인트에서 시작하여 C 핸들러까지 이어집니다.
/* arch/x86/kernel/cpu/mce/core.c */
DEFINE_IDTENTRY_MCE(exc_machine_check)
{
/* MCE 핸들러는 NMI-like 컨텍스트에서 실행 */
if (user_mode(regs))
nmi_enter();
do_machine_check(regs);
if (user_mode(regs))
nmi_exit();
}
/* IST 3 설정: arch/x86/kernel/idt.c */
static const __initconst struct idt_data def_idts[] = {
/* ... */
ISTG(X86_TRAP_MC, asm_exc_machine_check, IST_INDEX_MCE), /* IST 3 */
};
printk()도 안전하지 않을 수 있습니다.
CPU 상태가 손상되었을 수 있으므로, 최소한의 작업(MSR 읽기, 구조체(Struct) 기록)만 수행하고,
상세 처리는 뒤로 미룹니다.
do_machine_check() 처리 흐름
do_machine_check()는 MCE의 핵심 처리 함수입니다.
멀티 CPU 환경에서 여러 코어가 동시에 MCE를 받을 수 있으므로,
Monarch 랑데부 프로토콜로 동기화한 뒤 한 CPU가 최종 결정을 내립니다.
/* arch/x86/kernel/cpu/mce/core.c — 핵심 흐름 */
noinstr void do_machine_check(struct pt_regs *regs)
{
int worst = MCE_NO_SEVERITY;
int order, no_way_out;
/* Phase 1: Monarch 선출 및 랑데부 */
order = mce_start(&no_way_out);
/* Phase 2: 자신의 MCA 뱅크 스캔 */
for (i = 0; i < num_banks; i++) {
rdmsrl(MSR_IA32_MCx_STATUS(i), m.status);
if (!(m.status & MCI_STATUS_VAL))
continue;
severity = mce_severity(&m, regs, &msg, true);
if (severity > worst)
worst = severity;
mce_log(&m); /* 링 버퍼에 기록 */
}
/* Phase 3: Monarch가 최종 결정 */
if (order == 1) /* Monarch */
mce_reign(); /* worst severity 수집 → panic/recover */
/* Phase 4: 랑데부 완료 */
mce_end(order);
/* Phase 5: 에러 클리어 */
for (i = 0; i < num_banks; i++)
wrmsrl(MSR_IA32_MCx_STATUS(i), 0);
}
do_machine_check() 소스 코드 상세 분석
do_machine_check()의 내부 로직을 단계별로 더 깊이 분석합니다.
이 함수는 noinstr 속성으로 선언되어 계측 코드(ftrace, kprobes)가 삽입되지 않으며,
NMI와 유사한 제약 환경에서 실행됩니다. 메모리 할당, 슬립(Sleep), printk 등 일반적인 커널 기능을
사용할 수 없으므로, 미리 할당된 per-CPU 구조체와 원자적 연산(Atomic Operation)만 사용합니다.
/* do_machine_check() 상세 흐름 (최근 6.x 계열, 핵심 로직 발췌) */
noinstr void do_machine_check(struct pt_regs *regs)
{
struct mce m, *final;
int worst = MCE_NO_SEVERITY;
int order, no_way_out, kill_current_task = 0;
int i, lmce, cfg_monarch_timeout;
struct mca_config *cfg = &mca_cfg;
/*
* Phase 0: 초기화 및 컨텍스트 설정
* - per-CPU mce 구조체 초기화
* - 인터럽트/프리엠션 비활성 확인
* - MCG_STATUS 읽기 (MCIP 비트 확인)
*/
mce_gather_info(&m, regs);
final = this_cpu_ptr(&mces_seen);
*final = m;
/* MCG_STATUS.MCIP 확인 — 이미 MCE 처리 중이면 이중 MCE */
if (m.mcgstatus & MCG_STATUS_MCIP) {
/* 이미 MCE 핸들러 안에서 새 MCE → 복구 불가 */
mce_panic("MCE while already in MCE handler", &m, NULL);
}
/* LMCE(Local MCE) 여부 확인 — 다른 CPU에 브로드캐스트 안 됨 */
lmce = m.mcgstatus & MCG_STATUS_LMCE_S;
/*
* Phase 1: Monarch 랑데부 (LMCE가 아닌 경우)
* - LMCE면 이 CPU만 처리, Monarch 건너뜀
* - 브로드캐스트 MCE면 모든 CPU 동기화
*/
if (!lmce) {
order = mce_start(&no_way_out);
} else {
order = -1; /* LMCE: Monarch 없이 독립 처리 */
no_way_out = 0;
}
/*
* Phase 2: 자신의 MCA 뱅크 전체 스캔
* - 모든 뱅크의 MCi_STATUS 읽기
* - 각 에러의 심각도(severity) 판별
* - 최악 심각도(worst) 추적
* - mce_log()로 링 버퍼에 기록
*/
for (i = 0; i < this_cpu_read(mce_num_banks); i++) {
__clear_bit(i, this_cpu_ptr(mce_poll_banks));
m.bank = i;
m.status = mce_rdmsrl(MSR_IA32_MCx_STATUS(i));
if (!(m.status & MCI_STATUS_VAL))
continue; /* 이 뱅크에 에러 없음 */
/* ADDR/MISC 조건부 읽기 */
if (m.status & MCI_STATUS_ADDRV)
m.addr = mce_rdmsrl(MSR_IA32_MCx_ADDR(i));
if (m.status & MCI_STATUS_MISCV)
m.misc = mce_rdmsrl(MSR_IA32_MCx_MISC(i));
/* 심각도 판별 — severity.c의 severities[] 테이블 매칭 */
int severity = mce_severity(&m, regs, NULL, true);
if (severity == MCE_PANIC_SEVERITY)
no_way_out = 1; /* 패닉 불가피 */
if (severity > worst) {
worst = severity;
*final = m; /* 최악 에러 정보 보존 */
}
/* 링 버퍼에 기록 + tracepoint 발생 */
mce_log(&m);
}
/*
* Phase 3: Monarch 결정 (브로드캐스트 MCE인 경우)
* - Monarch CPU(order==1)가 모든 CPU의 worst severity 수집
* - global_worst 기반으로 패닉/복구/로그 결정
*/
if (!lmce) {
if (order == 1)
mce_reign(); /* Monarch: 최종 결정 */
mce_end(order);
}
/*
* Phase 4: 복구 동작 실행
* - SRAR → kill_current_task = 1, memory_failure() 큐잉
* - SRAO → memory_failure_queue() (워크큐 비동기 처리)
* - CE/UCNA → 로깅만
*/
if (worst >= MCE_AR_SEVERITY && mce_usable_address(final)) {
/* AR(Action Required): 동기 MCE, 데이터 소비 에러 */
if (!(final->mcgstatus & MCG_STATUS_RIPV))
kill_current_task = 1;
/* memory_failure를 큐에 넣음 (MCE 핸들러 내에서 직접 호출 불가) */
memory_failure_queue(final->addr >> PAGE_SHIFT,
MF_ACTION_REQUIRED);
} else if (worst >= MCE_UC_SEVERITY && mce_usable_address(final)) {
/* UC + !PCC: 비동기 복구 가능 */
memory_failure_queue(final->addr >> PAGE_SHIFT, 0);
}
/*
* Phase 5: 에러 클리어 및 상태 복원
* - MCi_STATUS = 0 쓰기 (에러 ACK)
* - MCG_STATUS.MCIP 클리어 (다음 MCE 수신 허용)
*/
for (i = 0; i < this_cpu_read(mce_num_banks); i++)
mce_wrmsrl(MSR_IA32_MCx_STATUS(i), 0);
mce_wrmsrl(MSR_IA32_MCG_STATUS, 0);
/* 현재 태스크 kill이 필요한 경우 */
if (kill_current_task)
force_sig(SIGBUS);
}
noinstr 섹션에서는 ftrace/kprobes 계측이 불가능합니다.
이 함수 내부를 추적하려면 mce:mce_record tracepoint를 활용하거나,
mce_log() 이후의 notifier chain에서 관찰해야 합니다.
do_machine_check() 자체에 kprobe를 설치하면 MCE 처리 중 또 다른 예외가 발생할 수 있어 위험합니다.
mce_gather_info() — 초기 컨텍스트 수집
MCE 핸들러 진입 직후 호출되는 mce_gather_info()는 MCG_STATUS 및 실행 컨텍스트를
수집하여 struct mce에 기록합니다. 이 정보는 이후 모든 단계에서 참조됩니다.
/* arch/x86/kernel/cpu/mce/core.c — 초기 정보 수집 */
static noinstr void mce_gather_info(struct mce *m, struct pt_regs *regs)
{
/* 구조체 초기화 */
mce_setup(m);
/* MCG_STATUS: MCIP, RIPV, EIPV, LMCE_S 비트 */
m->mcgstatus = mce_rdmsrl(MSR_IA32_MCG_STATUS);
/* TSC(Time Stamp Counter) — 에러 발생 시각의 정밀 타임스탬프 */
m->tsc = rdtsc();
/* 현재 실행 컨텍스트 기록 */
if (regs) {
m->ip = regs->ip; /* 에러 발생 시 명령 포인터 */
m->cs = regs->cs; /* 코드 세그먼트 → ring 레벨 판별 */
}
/* CPU 식별 정보 */
m->cpuvendor = boot_cpu_data.x86_vendor;
m->cpuid = cpuid_eax(1);
m->socketid = cpu_data(m->extcpu).phys_proc_id;
m->apicid = cpu_data(m->extcpu).initial_apicid;
m->microcode = boot_cpu_data.microcode;
/* Intel PPIN(Protected Processor Inventory Number) — CPU 고유 식별자 */
if (cpu_has(c, X86_FEATURE_INTEL_PPIN))
rdmsrl(MSR_PPIN, m->ppin);
}
Monarch 메커니즘
MCE는 일반적으로 모든 CPU에 동시에 브로드캐스트됩니다(LMCE 제외). 여러 CPU가 동시에 핸들러를 실행하면 의사결정이 분산되어 일관성을 잃을 수 있으므로, Monarch(군주) 프로토콜로 하나의 CPU가 최종 결정권을 갖습니다.
Monarch 선출 규칙
mce_start()에서atomic_inc_return(&mce_callin)을 호출- 리턴 값이 1인 CPU가 Monarch (가장 먼저 도착한 CPU)
- 나머지 CPU(Subject)는 Monarch의 결정을 대기
- Monarch는 모든 Subject가
mce_callin에 도달하거나 타임아웃까지 대기
tolerant 레벨
/sys/devices/system/machinecheck/machinecheck0/tolerant 설정으로
MCE 대응 수준을 조절합니다.
| 레벨 | 동작 | 용도 |
|---|---|---|
| 0 | 모든 MCE에서 즉시 패닉 | 안전 최우선 환경 (의료/항공) |
| 1 (기본값) | PCC가 아니면 복구 시도, PCC → 패닉 | 일반 서버/데스크톱 |
| 2 | RIPV가 없어도 복구 시도 | 고가용성 서버 |
| 3 | MCE를 최대한 무시 (디버그용) | 테스트/개발 전용 |
mce_panic()의 결정 시점
/* arch/x86/kernel/cpu/mce/core.c */
static void mce_reign(void)
{
int global_worst = MCE_NO_SEVERITY;
/* 모든 CPU의 worst severity 수집 */
for_each_possible_cpu(cpu) {
int severity = per_cpu(mce_worst_severity, cpu);
if (severity > global_worst)
global_worst = severity;
}
if (global_worst >= MCE_PANIC_SEVERITY) {
mce_panic("Fatal machine check", &m, msg);
/* 여기서 리턴하지 않음 */
}
/* PANIC이 아니면 복구 경로로 진행 */
}
Monarch 타임아웃
Monarch는 Subject CPU의 도착을 monarch_timeout(기본 ~500us, CPU마다 조정)까지 대기합니다.
타임아웃이 발생하면 Monarch는 도착한 CPU 정보만으로 결정을 내립니다.
이는 MCE 자체가 일부 CPU를 무응답 상태로 만들었을 수 있기 때문입니다.
MCG_STATUS.MCIP 비트
MCG_STATUS의 MCIP(Machine Check In Progress) 비트는 MCE 핸들러가 실행 중임을 나타냅니다.
이 비트가 1인 상태에서 새로운 MCE가 발생하면 CPU는 shutdown 상태에 들어갑니다(triple fault).
따라서 커널은 MCE 핸들러 완료 시 반드시 MCG_STATUS.MCIP = 0으로 클리어해야 합니다.
/* MCE 핸들러 완료 시 MCIP 클리어 */
static void mce_wrmsrl(u32 msr, u64 v)
{
if (msr == MSR_IA32_MCG_STATUS) {
v &= ~MCG_STATUS_MCIP; /* MCIP 클리어 → 다음 MCE 수신 가능 */
}
wrmsrl(msr, v);
}
Monarch와 가상화(Virtualization)
KVM 가상화 환경에서 MCE가 발생하면, 하이퍼바이저(Hypervisor)가 먼저 MCE를 가로챕니다(VMEXIT). 호스트 커널의 Monarch가 에러를 분석한 후, 게스트에 영향이 있으면 게스트에 가상 MCE를 주입합니다. LMCE 지원 시 게스트 vCPU에만 MCE를 전달하여 다른 vCPU의 VMEXIT를 방지합니다.
Monarch 상태 머신
Monarch 프로토콜은 명확한 상태 전이를 통해 진행됩니다. 모든 CPU가 MCE를 수신하면
mce_start()에서 동기화 배리어를 형성하고, Monarch CPU(가장 낮은 번호)가
최종 결정을 내립니다.
/* arch/x86/kernel/cpu/mce/core.c — mce_start() 내부 로직 */
static int mce_start(int *no_way_out)
{
int order;
int cpus = num_online_cpus();
u64 timeout = (u64)mca_cfg.monarch_timeout * NSEC_PER_USEC;
if (!timeout)
return -1; /* Monarch 비활성화 → 독립 처리 */
/* 1단계: CALLIN — 모든 CPU가 도착을 알림 */
atomic_add(*no_way_out, &global_nwo);
order = atomic_inc_return(&mce_callin); /* 도착 순서 기록 */
/* 2단계: CALLIN 배리어 — 모든 CPU 도착 대기 */
mce_barrier_enter(&mce_callin_barrier, timeout);
/* order == 1인 CPU가 Monarch (첫 번째 도착) */
if (order == 1) {
/* Monarch: 글로벌 상태 초기화 */
atomic_set(&global_nwo, 0);
atomic_set(&mce_executing, 0);
}
/* 타임아웃 처리: 일부 CPU 미도착 시 도착한 CPU만으로 진행 */
if (atomic_read(&mce_callin) != cpus) {
mce_timed_out(&timeout,
"Not all CPUs arrived at MCE rendezvous\n");
}
return order;
}
Monarch 결정 매트릭스
Monarch CPU는 mce_reign()에서 수집된 모든 MCE 정보를 종합하여
global_worst 심각도와 시스템 설정에 따라 최종 조치를 결정합니다.
| global_worst 심각도 | RIPV 비트 | tolerant 레벨 | 최종 조치 |
|---|---|---|---|
MCE_PANIC_SEVERITY |
무관 | 0, 1, 2 | PANIC — mce_panic() 호출, 시스템 즉시 정지 |
MCE_PANIC_SEVERITY |
무관 | 3 | LOG — tolerant=3이면 패닉 억제, 로그만 기록 |
MCE_AR_SEVERITY |
1 (유효) | 0, 1 | RECOVER — memory_failure()로 페이지 오프라인, 프로세스에 SIGBUS |
MCE_AR_SEVERITY |
0 (무효) | 0 | PANIC — 복귀 지점 없음, 복구 불가 |
MCE_AR_SEVERITY |
0 (무효) | 1, 2 | RECOVER — 최선 노력(best-effort) 복구 시도 |
MCE_UC_SEVERITY |
1 | 0, 1, 2 | RECOVER — 비동기 UCE, 페이지 오프라인 후 계속 |
MCE_UC_SEVERITY |
0 | 0 | PANIC — 복귀 불가 + 엄격 모드 |
MCE_DEFERRED_SEVERITY |
무관 | 무관 | LOG — 지연(Latency) 처리, 나중에 소프트웨어 접근 시 처리 |
MCE_KEEP_SEVERITY |
무관 | 무관 | LOG — 정보성 기록, 조치 불필요 |
MCE_NO_SEVERITY |
무관 | 무관 | 무시 — 오류 아님 (spurious MCE) |
/sys/devices/system/machinecheck/machinecheck0/tolerant 값에 따라
0 = 최대한 엄격(패닉 우선), 1 = 기본값(복구 가능하면 시도),
2 = 관대(패닉 회피 노력), 3 = 패닉 완전 억제(디버깅(Debugging) 전용)로 동작합니다.
MCE와 CPU 핫플러그(Hotplug)
CPU 핫플러그 도중 MCE가 발생하면 특수한 상황이 발생합니다. 오프라인 상태이거나 핫플러그 진행 중인 CPU는 Monarch 랑데부에서 제외되어야 합니다.
커널은 다음과 같은 메커니즘으로 이 문제를 처리합니다:
- 오프라인 CPU 제외:
mce_start()에서num_online_cpus()를 사용하여 현재 온라인 CPU 수만 카운트합니다. 오프라인 CPU는 배리어 도착 대상에 포함되지 않습니다. - 핫플러그 진행 중: CPU가
CPU_DEAD상태로 전환되는 과정에서 MCE가 발생하면, 해당 CPU의 MCE 핸들러는 이미 해제(mce_cpu_dead())되어 #MC 인터럽트를 받지 못합니다. 따라서 자연스럽게 랑데부에서 제외됩니다. - CPU 온라인 시:
mce_cpu_online()에서 MCE 뱅크를 재초기화하고, 타이머를 재설정하며, CMCI를 재활성화합니다. 이전에 누적된 CE는 새로 폴링됩니다. - 타임아웃 보호: 핫플러그 경합(Contention)으로 CPU 수가 일시적으로 불일치하는 경우,
monarch_timeout이 데드락을 방지합니다. 타임아웃 만료 시 도착한 CPU만으로 진행합니다.
/* arch/x86/kernel/cpu/mce/core.c — CPU 핫플러그 콜백 */
static int mce_cpu_online(unsigned int cpu)
{
struct timer_list *t = this_cpu_ptr(&mce_timer);
mce_device_create(cpu); /* sysfs 노드 생성 */
mce_threshold_create_device(cpu);
mce_reenable_cpu(); /* MCE 뱅크 재활성화 */
mce_start_timer(t); /* 폴링 타이머 시작 */
return 0;
}
static int mce_cpu_dead(unsigned int cpu)
{
mce_threshold_remove_device(cpu);
mce_device_remove(cpu); /* sysfs 노드 제거 */
return 0;
/* 이후 이 CPU는 #MC를 수신하지 않음 → Monarch 랑데부 제외 */
}
MCE Notifier Chain과 복구
Notifier Chain 등록 우선순위(Priority)
| 우선순위 | 등록자 | 역할 |
|---|---|---|
| MCE_PRIO_FIRST | mce_default_notifier | dmesg 출력 (기본 디코더) |
| MCE_PRIO_MCELOG | dev_mcelog | /dev/mcelog 인터페이스 |
| MCE_PRIO_EDAC | EDAC 드라이버 | DIMM 위치 디코딩 |
| MCE_PRIO_NFIT | NVDIMM/NFIT | NVDIMM ARS |
| MCE_PRIO_CEC | Corrected Error Collector | CE 누적 추적 |
kill_me_now vs kill_me_later
SRAR 에러에서 커널은 두 가지 킬 전략을 사용합니다.
- kill_me_now — MCE 핸들러 내에서 즉시
force_sig(SIGBUS)를 전송. 현재 태스크(Task)가 에러 데이터를 소비한 경우에 사용. - kill_me_later — 핸들러 리턴 후 태스크가 다시 스케줄될 때
SIGBUS를 전송. 현재 태스크와 무관한 에러일 때 사용.
/* memory_failure_queue() — 비동기 처리 큐 */
void memory_failure_queue(unsigned long pfn, int flags)
{
struct memory_failure_entry entry = {
.pfn = pfn,
.flags = flags,
};
kfifo_put(&mf_cpu->fifo, entry);
schedule_work_on(smp_processor_id(), &mf_cpu->work);
}
MCE 링 버퍼(Ring Buffer) 구조
커널은 MCE 이벤트를 순환(circular) 링 버퍼에 기록합니다. 이 버퍼(Buffer)는 프로듀서-컨슈머 모델로 동작하며, NMI 컨텍스트에서도 안전하게 쓸 수 있도록 설계되어 있습니다.
MCE 이벤트 필터링
perf를 사용하면 MCE 이벤트를 실시간(Real-time)으로 캡처하고 디코딩할 수 있습니다.
mce:mce_record 트레이스포인트는 모든 MCE 로깅 경로에서 호출됩니다.
# MCE 트레이스 이벤트 캡처
$ perf record -e mce:mce_record -a --overwrite # 시스템 전체 기록
# 기록된 MCE 이벤트 디코딩
$ perf script
mcelog 3421 [002] 1842.553291: mce:mce_record: \
CPU: 2, MCGc: 0x0, MCGi: 0xf10, \
Bank: 9, MCi: 0xbd80000000100134, \
Addr: 0x3a7f8c000, MISC: 0x8c, severity: CE
# 특정 뱅크만 필터링 (Bank 9 = Memory Controller)
$ perf record -e mce:mce_record --filter 'bank == 9' -a
# ftrace로 직접 캡처 (perf 없이)
$ echo 1 > /sys/kernel/debug/tracing/events/mce/mce_record/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
rasdaemon은 이러한 트레이스 이벤트를 SQLite DB에 저장하여
장기간 CE 추적과 페이지별 오류 통계를 제공합니다.
레거시 mcelog 데몬은 /dev/mcelog에서 직접 읽는 방식이며,
최신 커널에서는 rasdaemon 사용이 권장됩니다.
# rasdaemon으로 MCE 이력 조회
$ ras-mc-ctl --errors
# Label CE count UE count Last error
# DIMM0 47 0 2026-03-15 14:23:01 Bank 9 CE
# DIMM1 0 0 —
# DIMM2 3 1 2026-03-14 09:11:44 Bank 9 UCE
# 페이지별 CE 누적 카운트 (메모리 교체 판단 기준)
$ ras-mc-ctl --error-count
# mc0: csrow0: ch0: 47 CEs, ch1: 0 CEs
# SQLite DB 직접 쿼리
$ sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT timestamp, err_type, mc_location FROM mce_record \
WHERE mcgstatus LIKE '%MCIP%' ORDER BY id DESC LIMIT 10;"
memory_failure()와 HWPoison
memory_failure()는 물리 메모리(Physical Memory) 페이지의 하드웨어 오류를 처리하는 핵심 함수입니다.
커널은 해당 페이지를 HWPoison으로 마킹하고, 페이지 유형에 따라 적절한 복구 조치를 수행합니다.
/* mm/memory-failure.c — 핵심 함수 */
int memory_failure(unsigned long pfn, int flags)
{
struct page *p = pfn_to_page(pfn);
/* 1. HWPoison 마킹 */
SetPageHWPoison(p);
/* 2. 페이지 잠금 및 참조 획득 */
shake_page(p); /* LRU에서 제거 시도 */
/* 3. 페이지 유형에 따른 처리 */
if (PageHuge(p))
return memory_failure_hugetlb(pfn, p, flags);
if (!PageLRU(p))
return identify_page_state(pfn, p, flags); /* 커널 페이지 등 */
/* 4. 프로세스에 SIGBUS 전송 */
collect_procs(p, &tokill); /* 매핑한 프로세스 목록 */
kill_procs(&tokill, flags & MF_ACTION_REQUIRED, pfn, flags);
/* 5. 페이지 오프라인 */
page_action(ps, p, pfn);
return 0;
}
/proc/kpageflags의 bit 19(HWPOISON)로 특정 페이지의
HWPoison 상태를 확인할 수 있습니다. page-types -l -b hwpoison 도구도 유용합니다.
HWPoison 페이지 격리(Isolation) 상세 흐름
memory_failure()가 페이지를 HWPoison으로 마킹한 후, 해당 페이지가
시스템에서 완전히 격리되기까지의 상세한 내부 흐름입니다.
이 과정은 페이지 유형에 따라 크게 달라지며, 각 단계에서 실패할 수 있는 지점이 있습니다.
/* mm/memory-failure.c — HWPoison 격리 상세 흐름 (커널 6.x) */
int memory_failure(unsigned long pfn, int flags)
{
struct page *p = pfn_to_page(pfn);
int result = MF_FAILED;
/* ========== 1단계: 기본 검증 ========== */
if (!pfn_valid(pfn))
return -ENXIO; /* 유효하지 않은 PFN */
if (TestSetPageHWPoison(p)) {
pr_err("Memory failure: 0x%lx: already hardware poisoned\n", pfn);
return 0; /* 이미 HWPoison 상태 */
}
num_poisoned_pages_inc(); /* /proc/meminfo HardwareCorrupted 증가 */
/* ========== 2단계: 페이지 참조 확인 ========== */
/* shake_page: LRU에서 제거 시도 (pagevec flush 등) */
shake_page(p);
lock_page(p);
/* ========== 3단계: 특수 페이지 유형 처리 ========== */
if (PageHuge(p)) {
/* Huge page: 4KB 서브페이지로 분리 후 해당 페이지만 재처리 */
unlock_page(p);
return memory_failure_hugetlb(pfn, p, flags);
}
if (PageTransHuge(p)) {
/* THP(Transparent Huge Page): split 후 재처리 */
if (try_to_split_thp_page(p) < 0) {
pr_err("Memory failure: THP split 실패 0x%lx\n", pfn);
return -EBUSY;
}
}
/* ========== 4단계: 프로세스 알림 ========== */
/* rmap(reverse mapping)으로 이 페이지를 매핑한 모든 프로세스 찾기 */
collect_procs(p, &tokill, flags & MF_ACTION_REQUIRED);
/* Early Kill: vm.memory_failure_early_kill=1이면 즉시 SIGBUS */
/* Late Kill: 다음 접근 시점에 SIGBUS (기본값) */
kill_procs(&tokill, flags & MF_ACTION_REQUIRED, pfn, flags);
/* ========== 5단계: 페이지 격리 (유형별) ========== */
struct page_state *ps = error_page_state(p);
result = ps->action(p, pfn);
/* ========== 6단계: buddy allocator에서 영구 제거 ========== */
/* 페이지가 free list에 있으면 dissolve하여 재할당 방지 */
if (PageBuddy(p))
take_page_off_buddy(p);
unlock_page(p);
return result == MF_RECOVERED ? 0 : -EBUSY;
}
| 격리 결과 | 의미 | 영향 |
|---|---|---|
MF_RECOVERED |
페이지 격리 성공, 데이터 복구 또는 안전 제거 | 시스템 정상 운영 계속. 오프라인 페이지만큼 가용 메모리 감소. |
MF_DELAYED |
격리는 완료되었으나 프로세스 알림이 지연됨 | Late Kill 모드에서 접근 시점에 SIGBUS 전달. |
MF_IGNORED |
격리 불필요 또는 이미 안전한 상태 | free buddy 페이지 등 프로세스 영향 없음. |
MF_FAILED |
격리 실패 — 커널 페이지, 잠금(Lock) 실패 등 | 위험: 오염 페이지가 활성 상태로 남음. panic 가능. |
memory_failure()가 MF_FAILED를 반환하면
커널이 오염 페이지를 격리하지 못한 것입니다. 이 경우 해당 페이지 데이터를 사용하는
프로세스나 커널 자체가 손상된 데이터로 동작할 위험이 있습니다.
tolerant=0 설정에서는 이 상황에서 즉시 패닉합니다.
page_action[] 타입별 처리 상세
memory_failure()는 오류 페이지의 유형을 판별한 뒤 page_action[] 배열에서
해당 타입의 핸들러를 호출합니다. 커널 소스(mm/memory-failure.c)의
error_states[] 배열이 이 매핑을 정의하며, 각 페이지 상태에 따라 복구 가능 여부와
처리 방식이 크게 달라집니다.
| 타입 (enum) | 대상 페이지 | 핸들러 함수 | 동작 | 결과 |
|---|---|---|---|---|
me_kernel |
커널이 직접 사용 중인 페이지 (slab, page table 등) | me_kernel() |
복구 불가 — 즉시 panic() 또는 프로세스 kill |
MF_FAILED |
me_unknown |
유형 판별 불가 페이지 | me_unknown() |
보수적 처리: 오프라인 시도, 실패 시 무시 | MF_IGNORED / MF_FAILED |
me_pagecache_clean |
깨끗한 페이지 캐시(Page Cache) (디스크와 동기화됨) | me_pagecache_clean() |
truncate_error_page()로 페이지 무효화(Invalidation) — 다음 접근 시 디스크에서 재로드 |
MF_RECOVERED |
me_pagecache_dirty |
더티 페이지 캐시 (아직 디스크에 미기록) | me_pagecache_dirty() |
매핑 프로세스에 SIGBUS, 페이지 무효화 — 데이터 손실 가능 | MF_RECOVERED (일부 데이터 유실) |
me_swapcache_clean |
스왑(Swap) 캐시 (스왑 영역(Swap Area)과 동기화됨) | me_swapcache_clean() |
스왑 슬롯 해제, 페이지 삭제 — 스왑에서 재로드 가능 | MF_RECOVERED |
me_swapcache_dirty |
더티 스왑 캐시 (수정 후 미기록) | me_swapcache_dirty() |
스왑 슬롯 무효화, 매핑 프로세스에 SIGBUS — 복구 불가능한 데이터 | MF_DELAYED (접근 시 kill) |
me_huge_page |
Huge page (2 MB / 1 GB) | me_huge_page() |
split_huge_page() 호출 → 4 KB 서브페이지로 분리 후 해당 페이지만 재처리 |
재귀 호출 |
me_free |
buddy allocator의 프리 페이지 | me_free() |
dissolve_free_huge_page() + buddy에서 영구 제거 — 프로세스 영향 없음 |
MF_RECOVERED |
/* mm/memory-failure.c — error_states[] 배열 (최근 6.x 계열 간략화) */
static struct page_state error_states[] = {
{ 0, 0, "free buddy", me_free },
{ slab, slab, "kernel slab", me_kernel },
{ head, head, "huge page", me_huge_page },
{ sc|dirty, sc|dirty, "swapcache dirty", me_swapcache_dirty },
{ sc, sc, "swapcache clean", me_swapcache_clean },
{ mlock|dirty, dirty, "dirty mlocked", me_pagecache_dirty },
{ mlock, mlock, "clean mlocked", me_pagecache_clean },
{ lru|dirty, dirty, "dirty LRU", me_pagecache_dirty },
{ lru, lru, "clean LRU", me_pagecache_clean },
{ 0, 0, "unknown page", me_unknown },
};
error_states[] 배열은 위에서 아래로 순서대로 검사됩니다.
첫 번째 매칭되는 엔트리의 핸들러가 호출되므로, 커널 슬랩 페이지는 항상 LRU 페이지보다 먼저
검사되어 적절한 처리(panic 또는 slab 격리(Isolation))를 보장합니다.
ECC 유형과 MCE 관계
서버 메모리에서 사용되는 ECC(Error Correcting Code) 수준에 따라 MCE의 CE(Corrected Error)와 UC(Uncorrected Error) 발생 패턴이 달라집니다. ECC가 강력할수록 더 많은 비트 오류를 CE로 교정하여 UC 발생을 줄입니다.
edac-util -s 또는 /sys/devices/system/edac/mc/mc*/에서
메모리 컨트롤러의 ECC 모드를 확인할 수 있습니다. edac_mode 파일에
S4ECD4ED(Chipkill), S8ECD8ED, SECDED 등이 표시됩니다.
MCi_MISC 비트 필드 상세
MCi_MISC 레지스터는 오류 주소의 해석 방법과 복구 가능 주소의 정밀도를 알려줍니다.
특히 UCR(Uncorrected Recoverable) 에러에서 memory_failure()에 전달할 물리 주소(Physical Address)의
유효 범위를 결정하는 데 핵심적인 역할을 합니다.
| 비트 범위 | 필드 이름 | 설명 |
|---|---|---|
[63:57] |
예약 | 미사용 (0) |
[56] |
Overflow (OVF) | 추가 에러 정보 오버플로 여부 |
[55:44] |
모델 의존 | CPU 모델별 추가 정보 (ECC 신드롬, 칩 번호 등) |
[43:40] |
예약 | 미사용 (0) |
[39:32] |
모델 의존 | CPU별 추가 에러 정보 |
[8:6] |
Address Mode | MCi_ADDR의 주소 해석 방식:
|
[5:0] |
Recoverable Addr LSB | 주소의 유효 최하위 비트 위치. 예: 값이 12이면 4 KB(페이지) 단위 정밀도,
값이 6이면 64 B(캐시라인) 단위 정밀도 |
/* arch/x86/kernel/cpu/mce/severity.c — MISC 비트 해석 */
#define MCI_MISC_ADDR_LSB(m) ((m) & 0x3f) /* bits [5:0] */
#define MCI_MISC_ADDR_MODE(m) (((m) >> 6) & 7) /* bits [8:6] */
#define MCI_MISC_ADDR_PHYS 2 /* 물리 주소 모드 */
/* UCR 에러에서 PFN 추출 시 LSB를 확인 */
if (MCI_MISC_ADDR_MODE(m->misc) != MCI_MISC_ADDR_PHYS)
return; /* 물리 주소가 아니면 memory_failure 호출 불가 */
int lsb = MCI_MISC_ADDR_LSB(m->misc);
if (lsb > PAGE_SHIFT)
lsb = PAGE_SHIFT; /* 페이지 단위로 클램프 */
MCi_ADDR 주소 해석
MCi_ADDR 레지스터에 기록된 주소는 MCi_MISC의 Address Mode에 따라
해석 방법이 다릅니다. 대부분의 메모리 에러에서는 물리 주소 모드(010)가 사용되며,
커널은 이 주소에서 PFN(Page Frame Number)을 추출하여 memory_failure()에 전달합니다.
| 단계 | 연산 | 설명 |
|---|---|---|
| 1. Address Mode 확인 | MCi_MISC[8:6] == 010 |
물리 주소 모드인지 검증 — 다른 모드면 PFN 추출 불가 |
| 2. LSB 마스킹 | addr &= ~((1UL << lsb) - 1) |
MCi_MISC[5:0]이 가리키는 LSB 이하 비트를 0으로 클리어 |
| 3. PFN 추출 | pfn = addr >> PAGE_SHIFT |
물리 주소를 12비트 시프트하여 페이지 프레임(Page Frame) 번호 산출 |
| 4. memory_failure 호출 | memory_failure(pfn, flags) |
해당 PFN의 페이지를 HWPoison으로 마킹하고 복구 처리 |
| 5. DIMM 매핑 (EDAC) | edac_mc_handle_error() |
물리 주소 → DIMM 슬롯/채널/랭크/뱅크/로우 매핑 (address decoder) |
/* arch/x86/kernel/cpu/mce/core.c — MCi_ADDR에서 PFN 추출 */
static void mce_log_and_process(struct mce *m)
{
unsigned long pfn;
int lsb;
/* 물리 주소 모드가 아니면 복구 불가 */
if (MCI_MISC_ADDR_MODE(m->misc) != MCI_MISC_ADDR_PHYS)
return;
/* 유효 비트 하한 */
lsb = MCI_MISC_ADDR_LSB(m->misc);
if (lsb > PAGE_SHIFT)
lsb = PAGE_SHIFT;
/* PFN 추출: 하위 비트 마스크 후 페이지 시프트 */
pfn = (m->addr >> PAGE_SHIFT) &
(((1UL << (46 - PAGE_SHIFT)) - 1));
/* memory_failure 호출 (MF_ACTION_REQUIRED는 SRAR 에러에서 설정) */
memory_failure(pfn, m->mcgstatus & MCG_STATUS_RIPV ?
MF_ACTION_REQUIRED : 0);
}
/* EDAC에서 물리 주소 → DIMM 위치 디코딩 (drivers/edac/skx_common.c) */
static int skx_decode_addr(u64 addr, struct decoded_addr *res)
{
/* System Address → Socket → iMC → Channel → DIMM → Rank → Bank → Row/Col */
res->socket = skx_sad_decode(addr);
res->imc = skx_tad_decode(addr);
res->channel = skx_channel_decode(addr);
res->dimm = skx_dimm_decode(addr);
return 0;
}
/sys/devices/system/edac/mc/mc*/dimm*/dimm_label에서
DIMM 물리 슬롯 이름을 확인하고, edac-util -l로 전체 매핑을 조회합니다.
mcelog --dmi는 DMI/SMBIOS 테이블을 사용하여 주소를 DIMM 슬롯으로 변환합니다.
CMCI (Corrected MCE Interrupt)
CMCI(Corrected Machine Check Error Interrupt)는 교정된 에러(CE)를 효율적으로 수집하는 메커니즘입니다. 전통적인 MCE 폴링은 주기적으로 MSR을 읽어야 했지만, CMCI는 CE가 임계값에 도달하면 인터럽트를 발생시켜 즉시 알려줍니다.
CMCI vs 폴링 비교
| 특성 | CMCI | 폴링 |
|---|---|---|
| 지연 시간 | 즉시 (인터럽트) | check_interval 주기 (기본 5분) |
| CPU 오버헤드(Overhead) | 낮음 (이벤트 기반) | 주기적 MSR 읽기 |
| 스톰 대응 | 자동 폴링 전환 | 해당 없음 |
| 지원 CPU | Intel Xeon 5500+ / AMD Zen+ | 모든 MCA 지원 CPU |
/* arch/x86/kernel/cpu/mce/intel.c */
static void cmci_storm_begin(int bank)
{
__clear_bit(bank, this_cpu_ptr(mce_poll_banks));
cmci_toggle_interrupt_mode(false); /* CMCI_EN = 0 */
/* 폴링 타이머 활성화 */
mce_timer_kick(CMCI_STORM_INTERVAL);
}
static void cmci_storm_end(int bank)
{
cmci_toggle_interrupt_mode(true); /* CMCI_EN = 1 복원 */
mce_timer_kick(CMCI_POLL_INTERVAL);
}
CMCI 임계값 설정
MCi_CTL2 레지스터의 bits [14:0]이 CMCI 임계값을 결정합니다. CE가 이 값에 도달하면 CMCI 인터럽트가 발생합니다. 커널은 기본적으로 임계값을 1로 설정하여 첫 번째 CE부터 즉시 인터럽트를 받습니다.
/* arch/x86/kernel/cpu/mce/intel.c — CMCI 임계값 설정 */
static void cmci_set_threshold(int bank, int thresh)
{
u64 val;
rdmsrl(MSR_IA32_MCx_CTL2(bank), val);
val &= ~MCI_CTL2_CMCI_THRESHOLD_MASK;
val |= (u64)thresh & MCI_CTL2_CMCI_THRESHOLD_MASK;
val |= MCI_CTL2_CMCI_EN; /* bit 30: CMCI 활성화 */
wrmsrl(MSR_IA32_MCx_CTL2(bank), val);
}
Corrected Error Collector (CEC)
Intel CPU에서 커널은 CEC(Corrected Error Collector)를 사용하여
CE가 반복되는 물리 페이지를 추적합니다. 동일 페이지에서 CE가 임계값을 초과하면
soft_offline_page()를 호출하여 예방적으로 오프라인합니다.
/* arch/x86/kernel/cpu/mce/intel.c — CEC */
static int cec_add_elem(u64 pfn)
{
/* pfn을 decay table에 삽입 */
/* 동일 pfn의 카운트가 action_threshold 초과 시: */
if (count >= action_threshold) {
/* 페이지 예방 오프라인 */
soft_offline_page(pfn_to_page(pfn), MF_SOFT_OFFLINE);
return 1;
}
return 0;
}
/sys/kernel/debug/cec/decay_interval로 감쇠 주기를 조정할 수 있습니다.
CMCI 임계값 메커니즘 상세
CMCI의 임계값 메커니즘은 CE 빈도에 따라 인터럽트와 폴링 사이를 자동으로 전환하는 적응형(adaptive) 시스템입니다. 커널은 이 메커니즘을 통해 CE 폭주(storm) 상황에서도 시스템 안정성을 유지하면서 에러 정보를 놓치지 않습니다.
/* arch/x86/kernel/cpu/mce/intel.c — CMCI 스톰 감지 전체 흐름 */
/* 1. CMCI 인터럽트 핸들러 */
void intel_threshold_interrupt(void)
{
/* CMCI 인터럽트 수신 — 뱅크별 CE 확인 */
machine_check_poll(MCP_TIMESTAMP, this_cpu_ptr(mce_poll_banks));
/* 스톰 감지: 짧은 시간에 CMCI가 반복되는지 확인 */
if (cmci_storm_detect()) {
/* 스톰 → 해당 뱅크의 CMCI 비활성화 + 폴링 전환 */
cmci_storm_begin(bank);
}
}
/* 2. 스톰 감지 알고리즘 */
static bool cmci_storm_detect(void)
{
unsigned long now = jiffies;
unsigned long last = this_cpu_read(cmci_time_stamp);
/* CMCI 간격이 CMCI_STORM_INTERVAL보다 짧으면 스톰 */
if (now - last < CMCI_STORM_INTERVAL) {
this_cpu_inc(cmci_storm_cnt);
if (this_cpu_read(cmci_storm_cnt) > CMCI_STORM_THRESHOLD)
return true; /* 스톰 확정 */
} else {
this_cpu_write(cmci_storm_cnt, 0); /* 카운터 리셋 */
}
this_cpu_write(cmci_time_stamp, now);
return false;
}
/* 3. 폴링 타이머 콜백 — 스톰 중에도 CE 수집 */
static void mce_timer_fn(struct timer_list *t)
{
/* 폴링 모드에서 주기적으로 MCA 뱅크 스캔 */
machine_check_poll(MCP_TIMESTAMP, this_cpu_ptr(mce_poll_banks));
/* CE 빈도가 감소했는지 확인 */
if (cmci_storm_subsided()) {
cmci_storm_end(bank); /* CMCI 재활성화 */
} else {
/* 타이머 재등록 (폴링 계속) */
mod_timer(t, jiffies + CMCI_POLL_INTERVAL);
}
}
CMCI 성능 영향과 최적화
CMCI는 정상 상태에서 오버헤드가 거의 없지만, CE가 빈번한 환경에서는 CMCI 인터럽트 자체가 CPU 사이클을 소비합니다. 다음은 환경별 최적 설정입니다.
| 환경 | CMCI 설정 | Threshold | 이유 |
|---|---|---|---|
| 일반 서버 (CE 드물음) | CMCI_EN=1 (기본) | 1 | 즉각적인 CE 감지로 조기 경고 확보 |
| CE 빈번한 환경 (열화 DIMM) | CMCI_EN=1, 스톰 보호 신뢰 | 1 | 커널 스톰 감지가 자동으로 폴링 전환 |
| 지연 시간 민감 워크로드 (HFT 등) | mce=no_cmci (폴링만) |
- | CMCI 인터럽트에 의한 지터 제거 |
| 대규모 클러스터 (수천 노드) | CMCI_EN=1 + rasdaemon | 1 | 중앙 집중 CE 통계 수집, 교체 자동화 |
AMD Scalable MCA (SMCA)
AMD는 Zen 아키텍처부터 Scalable MCA(SMCA)를 도입하여 기존 MCA의 한계를 극복했습니다. SMCA는 뱅크별로 하드웨어 유닛을 동적으로 식별할 수 있어, 마이크로아키텍처 변경에 유연하게 대응합니다.
SMCA vs 기존 MCA
| 특성 | 기존 MCA (Intel/레거시 AMD) | AMD SMCA |
|---|---|---|
| 뱅크 식별 | 모델별 고정 매핑 (매뉴얼 참조) | IPID 레지스터로 동적 식별 |
| 에러 코드 | 표준 MCA 코드 | 확장 코드 + SYND (Syndrome) |
| 추가 레지스터 | CTL/STATUS/ADDR/MISC | + IPID, SYND, DESTAT, DEADDR |
| 인스턴스 | 뱅크 번호 = 유닛 | HWID/MCATYPE으로 유닛+인스턴스 |
SMCA 추가 레지스터
| 레지스터 | MSR 오프셋 | 내용 |
|---|---|---|
| MCi_IPID | +5 | Hardware ID(HWID) + MCA Type → 유닛 식별 |
| MCi_SYND | +6 | Syndrome — ECC 신드롬, 에러 비트 위치 |
| MCi_DESTAT | +8 | Deferred Error Status |
| MCi_DEADDR | +9 | Deferred Error Address |
/* arch/x86/kernel/cpu/mce/amd.c — SMCA 뱅크 타입 결정 */
enum smca_bank_types smca_get_bank_type(unsigned int cpu, unsigned int bank)
{
struct smca_bank *b;
if (bank >= MAX_NR_BANKS)
return N_SMCA_BANK_TYPES;
b = &smca_banks[bank];
if (!b->hwid)
return N_SMCA_BANK_TYPES;
return b->hwid->bank_type; /* SMCA_LS, SMCA_IF, SMCA_L2, SMCA_DE, ... */
}
/* SMCA 하드웨어 유닛 목록 */
static const struct smca_hwid smca_hwid_mcatypes[] = {
{ SMCA_LS, HWID_MCATYPE(0xB0, 0x0) }, /* Load Store */
{ SMCA_LS_V2, HWID_MCATYPE(0xB0, 0x10) }, /* Load Store V2 */
{ SMCA_IF, HWID_MCATYPE(0xB1, 0x0) }, /* Instruction Fetch */
{ SMCA_L2_CACHE, HWID_MCATYPE(0xB2, 0x0) }, /* L2 Cache */
{ SMCA_DE, HWID_MCATYPE(0xB3, 0x0) }, /* Decode Unit */
{ SMCA_EX, HWID_MCATYPE(0xB6, 0x0) }, /* Execution Unit */
{ SMCA_FP, HWID_MCATYPE(0xB7, 0x0) }, /* Floating Point */
{ SMCA_UMC, HWID_MCATYPE(0x96, 0x0) }, /* Unified Memory Controller */
/* ... */
};
rasdaemon은 SMCA의 IPID를 자동으로 디코딩하여
"Load Store Unit" 같은 사람이 읽을 수 있는 이름을 출력합니다.
커널 로그에서는 Scalable MCA: ... IPID: ... 형태로 출력됩니다.
Intel CPU 뱅크 매핑 (Skylake-SP 이후)
Intel 전통 MCA에서는 뱅크 번호가 CPU 마이크로아키텍처마다 다르게 매핑됩니다. 아래는 Skylake-SP(Xeon Scalable) 이후 서버 CPU의 일반적인 뱅크 할당입니다. Ice Lake-SP, Sapphire Rapids에서는 일부 뱅크가 추가/변경될 수 있습니다.
| 뱅크 번호 | 유닛 | 설명 | 일반적인 에러 |
|---|---|---|---|
| Bank 0 | IFU (Instruction Fetch Unit) | 명령어 페치 파이프라인(Pipeline) | I-Cache 패리티, uop 캐시 에러 |
| Bank 1 | DCU (Data Cache Unit) | L1 데이터 캐시 | D-Cache ECC, 로드/스토어 에러 |
| Bank 2 | DTLB | 데이터 TLB | TLB 패리티 에러 |
| Bank 3 | MLC (Mid-Level Cache) | L2 캐시 | L2 ECC CE/UC |
| Bank 4 | PCU (Power Control Unit) | 전력 관리 유닛 | 전압/클럭 관련 내부 에러 |
| Bank 5-6 | Intel UPI | 소켓(Socket) 간 인터커넥트 | 링크 CRC, 프로토콜 에러 |
| Bank 7-8 | IIO (Integrated I/O) | PCIe 루트 포트, CBDMA | PCIe CE/UC, DMA 에러 |
| Bank 9-12 | M2M (Mesh to Memory) | 메시 → 메모리 인터페이스 | 주소 디코드 에러, patrol scrub CE |
| Bank 13-20 | iMC (integrated Memory Controller) | 메모리 채널 컨트롤러 | DRAM ECC CE/UC — 가장 빈번 |
| Bank 21-24 | CHA (Caching Home Agent) | LLC 슬라이스 + 디렉토리 | LLC ECC, 스누프 프로토콜 에러 |
mcelog의 --dmi 옵션이나
커널 로그의 CPU N Bank M에서 확인할 수 있습니다.
AMD Zen SMCA 뱅크 타입 전체 목록
SMCA에서는 MCi_IPID 레지스터의 HWID(bits [43:32])와
MCATYPE(bits [63:48])으로 하드웨어 유닛을 식별합니다.
아래는 커널 소스(arch/x86/kernel/cpu/mce/amd.c)에 정의된 전체 SMCA 뱅크 타입입니다.
| enum 이름 | HWID | MCATYPE | 유닛 설명 | 주요 에러 |
|---|---|---|---|---|
SMCA_LS | 0xB0 | 0x0 | Load Store Unit | 로드/스토어 데이터 에러 |
SMCA_LS_V2 | 0xB0 | 0x10 | Load Store Unit V2 (Zen 3+) | 확장 LS 에러 코드 |
SMCA_IF | 0xB1 | 0x0 | Instruction Fetch | I-Cache, ITB(ITLB) 에러 |
SMCA_L2_CACHE | 0xB2 | 0x0 | L2 Cache | L2 ECC CE/UC |
SMCA_DE | 0xB3 | 0x0 | Decode Unit | uop 캐시, 디코더 에러 |
SMCA_RESERVED | 0xB4 | 0x0 | 예약 | 미사용 |
SMCA_EX | 0xB6 | 0x0 | Execution Unit | 정수/분기 실행 에러 |
SMCA_FP | 0xB7 | 0x0 | Floating Point | FPU, SIMD 연산 에러 |
SMCA_L3_CACHE | 0xB8 | 0x0 | L3 Cache | L3 ECC CE/UC, 태그 에러 |
SMCA_CS | 0x2B | 0x0 | Coherent Slave (xGMI/IF) | 인터커넥트 프로토콜 에러 |
SMCA_CS_V2 | 0x2B | 0x2 | Coherent Slave V2 (Zen 3+) | 확장 IF 에러 |
SMCA_PIE | 0x2E | 0x0 | Power, Interrupts, etc. | APIC, 전력 관리 에러 |
SMCA_UMC | 0x96 | 0x0 | Unified Memory Controller | DRAM ECC CE/UC — 가장 빈번 |
SMCA_UMC_V2 | 0x96 | 0x10 | UMC V2 (Zen 4+ DDR5) | DDR5 ECC, On-die ECC 이벤트 |
SMCA_PB | 0x05 | 0x0 | Parameter Block | 퓨즈/파라미터 에러 |
SMCA_PSP | 0xFF | 0x0 | Platform Security Processor | PSP 펌웨어 에러 |
SMCA_PSP_V2 | 0xFF | 0x1 | PSP V2 | 확장 PSP 에러 |
SMCA_SMU | 0x01 | 0x0 | System Management Unit | SMU 펌웨어 에러 |
SMCA_SMU_V2 | 0x01 | 0x10 | SMU V2 | 확장 SMU 에러 |
SMCA_MP5 | 0x01 | 0x2 | Microprocessor 5 Unit | MP5 내부 에러 |
SMCA_MPDMA | 0x01 | 0x3 | MPDMA | DMA 엔진 에러 |
SMCA_NBIO | 0x2E | 0x1 | Northbridge I/O Unit | PCIe, IOMMU 에러 |
SMCA_PCIE | 0x2E | 0x2 | PCIe Root Port | PCIe AER CE/UC |
SMCA_PCIE_V2 | 0x2E | 0x3 | PCIe V2 (Zen 4+) | 확장 PCIe 에러 |
SMCA_XGMI_PCS | 0x50 | 0x0 | xGMI PCS (Physical Coding) | 소켓 간 링크 물리 계층 에러 |
SMCA_NBIF | 0x2E | 0x4 | NB-IF (Northbridge Interface) | 데이터 패브릭 인터페이스 에러 |
SMCA_SHUB | 0x80 | 0x0 | System Hub | 사우스브릿지 연결 에러 |
SMCA_SATA | 0xA8 | 0x0 | SATA Controller | SATA PHY/프로토콜 에러 |
SMCA_USB | 0xAA | 0x0 | USB Controller | USB 내부 에러 |
SMCA_GMI_PCS | 0x50 | 0x1 | GMI PCS (Global Memory Interconnect) | 다이 간 링크 에러 |
SMCA_XGMI_PHY | 0x59 | 0x0 | xGMI PHY | xGMI 물리 계층 트레이닝 에러 |
SMCA_WAFL_PHY | 0x59 | 0x1 | WAFL PHY | WAFL 링크 에러 |
SMCA_GMI_PHY | 0x59 | 0x2 | GMI PHY | GMI 물리 계층 에러 |
MCi_IPID의 bits [31:0]인 InstanceId로
같은 타입의 서로 다른 인스턴스를 구별합니다. rasdaemon 로그에서
instance: N으로 확인됩니다.
Deferred Error (SMCA 고유)
AMD SMCA는 Deferred Error라는 고유한 에러 유형을 지원합니다. Deferred Error는 즉각적인 데이터 손상을 일으키지는 않지만, 나중에 소비되면 문제가 될 수 있는 "잠재적 오류"입니다. 주로 배경 패트롤 스크러빙(background patrol scrubbing)에서 발견된 에러를 보고하는 데 사용됩니다.
| 레지스터 | MSR | 설명 |
|---|---|---|
MCi_DESTAT |
MCi_BASE + 8 | Deferred Error Status — MCi_STATUS와 동일한 형식이지만,
지연된(deferred) 에러 전용. MCi_STATUS에 새 에러가 기록되어도
DESTAT의 내용은 보존됨 |
MCi_DEADDR |
MCi_BASE + 9 | Deferred Error Address — 지연 에러가 발생한 물리 주소.
memory_failure()에 전달하여 해당 페이지를 오프라인할 수 있음 |
일반적인 UC(Uncorrected) 에러와 Deferred Error의 핵심적인 차이점은 다음과 같습니다:
| 특성 | UC Error (일반) | Deferred Error |
|---|---|---|
| 발생 시점 | 데이터가 실제 소비될 때 (load, execution) | 패트롤 스크러빙 등 백그라운드 검사에서 발견 |
| 즉각 영향 | 프로세스 kill 또는 panic (SRAR/SRAO) | 즉각적 영향 없음 — 아직 소비되지 않은 데이터 |
| STATUS 기록 | MCi_STATUS.UC=1, MCi_STATUS.Deferred=0 |
MCi_STATUS.Deferred=1 (bit 32) |
| 별도 레지스터 | 없음 (MCi_STATUS/ADDR 사용) | MCi_DESTAT / MCi_DEADDR 전용 레지스터 |
| 커널 처리 | memory_failure(pfn, MF_ACTION_REQUIRED) |
memory_failure(pfn, 0) — 선제적 오프라인 |
| 복구 전략 | Late Kill / Early Kill 정책 적용 | 페이지 마이그레이션 후 오프라인 (데이터 보존 가능) |
/* arch/x86/kernel/cpu/mce/amd.c — Deferred Error 처리 */
static void amd_deferred_error_interrupt(void)
{
unsigned int bank;
for (bank = 0; bank < this_cpu_read(mce_num_banks); bank++) {
u64 status, addr;
rdmsrl(MSR_AMD64_SMCA_MCx_DESTAT(bank), status);
if (!(status & MCI_STATUS_VAL))
continue;
if (!(status & MCI_STATUS_DEFERRED)) /* bit 32: Deferred 플래그 */
continue;
rdmsrl(MSR_AMD64_SMCA_MCx_DEADDR(bank), addr);
/* 지연 에러 주소로 선제적 memory_failure 호출 */
if (addr) {
unsigned long pfn = addr >> PAGE_SHIFT;
memory_failure(pfn, 0); /* MF_ACTION_REQUIRED 미설정 */
}
/* DESTAT 클리어 */
wrmsrl(MSR_AMD64_SMCA_MCx_DESTAT(bank), 0);
}
}
/* Deferred Error는 APIC LVT에 별도 벡터(DEFERRED_ERROR_VECTOR)로 전달됨 */
/* AMD 특유: 패트롤 스크러빙이 CE로 교정 불가한 오류를 발견하면 */
/* 즉시 UC를 발생시키지 않고 DESTAT에 기록 → 소프트웨어가 선제 대응 가능 */
Intel vs AMD MCA 심각도 평가 비교
MCE 발생 시 커널은 에러의 심각도(Severity)를 평가하여 패닉(Panic), 프로세스 킬(Kill), 또는 로깅만 수행할지 결정합니다. Intel과 AMD는 서로 다른 심각도 평가 알고리즘을 사용합니다.
| 비교 항목 | Intel 전통 MCA | AMD SMCA |
|---|---|---|
| 심각도 판정 함수 | mce_severity() — arch/x86/kernel/cpu/mce/severity.c |
mce_severity() 공통 + SMCA 뱅크 타입별 추가 로직 |
| 판정 기준 | MCi_STATUS 비트 필드 (UC, EN, PCC, S, AR, MISCV, ADDRV) | 동일 + Deferred bit (bit 32) + IPID 기반 유닛 식별 |
| SRAR (복구 필수) | S=1, AR=1 — 데이터 소비 에러, 즉시 복구 필요 → memory_failure(MF_ACTION_REQUIRED) |
동일한 STATUS 비트로 판정 |
| SRAO (복구 선택) | S=1, AR=0 — 패트롤 스크러빙 등에서 발견, 복구 권장 | SMCA에서는 대부분 Deferred Error로 대체 |
| UCNA (조치 불필요) | UC=1, S=0, AR=0 — 로깅만 수행 | SMCA에서도 동일 패턴 가능 |
| Deferred Error | 해당 없음 | 별도 DESTAT/DEADDR 레지스터, APIC LVT 전용 벡터로 전달 → 선제적 페이지 오프라인 |
| 뱅크 매핑 | 모델별 고정 (Bank 0=IFU, 1=DCU, ... 매뉴얼 참조) | IPID 기반 동적 식별 — 세대 변경에도 코드 수정 불필요 |
| Monarch 메커니즘 | 한 코어가 Monarch로 선출, 전체 뱅크 수집·판정 | 동일 — Monarch 메커니즘은 벤더 공통 |
/* arch/x86/kernel/cpu/mce/severity.c — 심각도 판정 핵심 로직 (Intel/AMD 공통) */
static noinstr int mce_severity(struct mce *m, struct pt_regs *regs,
char **msg, bool is_excp)
{
/* PCC(Processor Context Corrupt) = 1 → PANIC 필수 */
if (m->status & MCI_STATUS_PCC)
return MCE_PANIC_SEVERITY;
/* UC + EN + S + AR → SRAR: 데이터 소비 에러, 즉시 복구 시도 */
if ((m->status & (MCI_STATUS_UC | MCI_STATUS_EN |
MCI_STATUS_S | MCI_STATUS_AR)) ==
(MCI_STATUS_UC | MCI_STATUS_EN |
MCI_STATUS_S | MCI_STATUS_AR))
return MCE_AR_SEVERITY; /* memory_failure() 호출 */
/* AMD SMCA: Deferred bit 확인 */
if (mce_is_deferred(m))
return MCE_DEFERRED_SEVERITY; /* 선제적 오프라인 */
/* ... 추가 심각도 레벨 판정 ... */
}
IIO (Integrated I/O) MCA 아키텍처
Intel 서버 CPU(Xeon Scalable 이후)에서 IIO(Integrated I/O)는 CPU 다이에 통합된 I/O 서브시스템으로, PCIe 루트 포트, DMI(Direct Media Interface), CBDMA/IOAT(Crystal Beach DMA), VT-d(Intel Virtualization Technology for Directed I/O) 엔진 등을 포함합니다. IIO는 CPU 내부에서 I/O 장치와 메시 인터커넥트 사이를 연결하는 핵심 컴포넌트이며, 전용 MCA 뱅크(일반적으로 Bank 5~8)를 통해 I/O 관련 하드웨어 에러를 보고합니다.
IIO 내부 구조
각 IIO 스택(Stack)은 독립적인 PCIe 루트 컴플렉스를 제공합니다. Intel Xeon Scalable 플랫폼에서 소켓당 최대 6개의 IIO 스택이 존재하며, 각 스택은 PCIe 루트 포트, DMA 엔진, IOMMU를 포함합니다.
IIO MCA 뱅크 구조
IIO에서 보고되는 에러는 일반적으로 Bank 5~8에 매핑됩니다(CPU 모델에 따라 다름). 각 뱅크는 특정 IIO 스택에 대응합니다. IIO 뱅크의 MCi_STATUS 에러 코드는 표준 MCA 복합 에러 코드 외에 Intel 모델 고유 에러 코드(MSCOD)도 포함합니다.
| 뱅크 | IIO 스택 | 일반적 장치 | 보고 에러 유형 |
|---|---|---|---|
| Bank 5 | IIO Stack 0 (DMI) | PCH 연결 장치(SATA, USB, 온보드 LAN) | DMI 링크 에러, PCH 통신 타임아웃 |
| Bank 6 | IIO Stack 1 | PCIe Slot 1~2 (GPU, NIC) | PCIe CE/UC, Completion Timeout, Poisoned TLP |
| Bank 7 | IIO Stack 2 | PCIe Slot 3~4 (NVMe, FPGA) | PCIe CE/UC, ECRC Error, Unexpected Completion |
| Bank 8 | IIO Stack 3 (CBDMA) | DMA 엔진, DSA, 추가 PCIe | DMA 에러, IOAT 채널 에러, PCIe 에러 |
IIO MCi_STATUS 에러 코드 해석
IIO 뱅크의 MCi_STATUS 레지스터에서 에러 코드(bits 15:0)는 표준 MCA 형식을 따르지만, MSCOD(Model-Specific Error Code, bits 31:16)에 IIO 고유의 상세 정보가 인코딩됩니다. PCIe 에러의 경우 MSCOD 필드에서 PCIe AER 에러 유형을 구분할 수 있습니다.
| MSCOD (bits 31:16) | 에러 유형 | 설명 | 일반적 원인 |
|---|---|---|---|
0x0000 | Completion Timeout | PCIe 요청에 대한 응답 타임아웃 | 장치 응답 불가, 링크 다운, FLR 중 접근 |
0x0001 | Receiver Overflow | IIO 수신 버퍼 오버플로(Buffer Overflow) | 과부하, 흐름 제어(Flow Control) 실패 |
0x0002 | Poisoned TLP | 독성 데이터(Poison bit) TLP 수신 | 업스트림 장치 메모리 에러, ECC 실패 |
0x0003 | Unsupported Request | 지원하지 않는 PCIe 요청 | 드라이버 버그, BAR 매핑 오류 |
0x0004 | ECRC Error | End-to-End CRC 검증 실패 | PCIe 라이저/케이블 불량, 신호 무결성(Integrity) |
0x0005 | Uncorrectable Internal | IIO 내부 패리티/로직 에러 | CPU 하드웨어 결함, 마이크로코드 버그 |
0x0006 | Malformed TLP | 형식 오류 TLP 수신 | 장치 하드웨어 결함, 링크 트레이닝 불완전 |
0x0007 | Flow Control Protocol | 흐름 제어 프로토콜 위반 | 장치 또는 스위치 펌웨어 버그 |
0x0008 | Surprise Link Down | 예기치 않은 PCIe 링크 다운 | 물리적 분리, 전원 불안정, 열 문제 |
0x0010 | Correctable Internal | IIO 내부 교정 가능 에러 | 일시적 패리티, 재시도로 복구 |
0x0020 | Header Log Overflow | 에러 헤더 로그 오버플로 | 에러 폭주 |
0x0080 | Advisory Non-Fatal | 권고 비치명적 에러 | Poisoned TLP가 Advisory로 재분류 |
0x0200 | DMA Channel Error | CBDMA/IOAT/DSA 채널 에러 | DMA 디스크립터 오류, 주소 변환(Address Translation) 실패 |
0x0400 | VT-d Fault | IOMMU 주소 변환 실패 | DMAR 테이블 오류, 잘못된 DMA 주소 |
IIO MCi_MISC 레지스터 해석
IIO 뱅크에서 MCi_MISC(MISCV=1일 때)에는 에러와 관련된 PCIe 장치의
Bus/Device/Function(BDF) 정보가 인코딩됩니다. 이를 통해 에러를 발생시킨 특정 PCIe 장치를
정확히 식별할 수 있습니다.
| 비트 범위 | 필드 | 설명 |
|---|---|---|
| bits 7:0 | Function Number | PCIe 장치 Function 번호 (0~7) |
| bits 12:8 | Device Number | PCIe 장치 Device 번호 (0~31) |
| bits 20:13 | Bus Number | PCIe 장치 Bus 번호 (0~255) |
| bits 23:21 | Segment Group | PCIe Segment Group (다중 세그먼트 시) |
| bits 31:24 | Root Port | 에러를 감지한 루트 포트 번호 |
/* IIO MCA MISC에서 BDF 추출 예시 (커널 내부) */
static void decode_iio_mce(struct mce *m)
{
u8 func = m->misc & 0xFF;
u8 dev = (m->misc >> 8) & 0x1F;
u16 bus = (m->misc >> 13) & 0xFF;
pr_emerg("IIO MCE: PCIe BDF %02x:%02x.%x MSCOD=0x%04x\n",
bus, dev, func,
(u16)(m->status >> 16) & 0xFFFF);
}
PCIe AER vs IIO MCA: 에러 보고 이중 경로
동일한 PCIe 에러가 두 가지 경로로 보고될 수 있습니다: PCIe AER(Advanced Error Reporting)와 IIO MCA 뱅크입니다. 이 두 경로는 독립적이며, 에러 발생 시 둘 다 트리거될 수 있습니다.
| 특성 | PCIe AER | IIO MCA |
|---|---|---|
| 트리거 | PCIe Config Space AER Capability | CPU 내부 IIO 로직 |
| 인터럽트 | MSI/MSI-X (일반 인터럽트) | #MC (벡터 18) 또는 CMCI |
| 에러 정보 | AER 레지스터 (Header Log 포함) | MCi_STATUS/ADDR/MISC (BDF 인코딩) |
| CE 처리 | AER Correctable → 로그만 | CMCI 또는 폴링으로 수집 |
| UC 처리 | AER Fatal → PCIe 링크 리셋, 드라이버 복구 | MCE Severity 판단 → 패닉 가능 |
| Firmware-First | GHES가 AER을 가로챌 수 있음 | Firmware-First 시 GHES/BERT 경유 |
| 커널 핸들러 | aer_irq() → pcie_do_recovery() | do_machine_check() → severity |
| 장점 | 장치 식별 정확, 드라이버 복구 프레임워크 | CPU 레벨 무결성 보장, 데이터 오염 방지 |
ghes_proc() → memory_failure() 또는
aer_recover_work_func()로 처리됩니다.
APEI/GHES 통합 섹션을 참고하세요.
IIO 에러 시나리오별 분석
시나리오 1: PCIe Completion Timeout (CTO)
가장 흔한 IIO 에러입니다. CPU가 PCIe 장치에 요청을 보냈으나, 설정된 시간(기본 50ms~3.2초) 내에 응답을 받지 못한 경우입니다.
| 항목 | 내용 |
|---|---|
| MCA 코드 | MSCOD=0x0000, MCA 에러 코드=0x0e0b 범위 |
| dmesg 시그니처 | Hardware Error ... IIO MCA Bank ... Completion Timeout |
| AER 대응 | pcieport ... AER: Uncorrectable (Non-Fatal) error received |
| 흔한 원인 |
|
| 조치 |
|
시나리오 2: Poisoned TLP 수신
PCIe 장치가 "독성(Poisoned)" 데이터를 포함한 TLP를 전송한 경우입니다. 이는 상류 장치의 메모리(예: GPU VRAM)에서 ECC 에러가 발생했음을 의미합니다.
| 항목 | 내용 |
|---|---|
| MCA 코드 | MSCOD=0x0002 |
| 의미 | 장치가 DMA 읽기에서 에러 데이터를 CPU에 전달 (EP bit set in TLP header) |
| 영향 |
|
| GPU 컨텍스트 | GPU VRAM ECC UC → Poisoned Completion → IIO MCE. NVIDIA GPU는 Xid 에러(Xid 48: DBE 에러)와 동시에 발생할 수 있음 |
| NVMe 컨텍스트 | NVMe 컨트롤러 내부 DRAM 에러 → Poisoned DMA Read Completion → IIO MCE → I/O 에러 |
| 조치 |
|
시나리오 3: Surprise Link Down
| 항목 | 내용 |
|---|---|
| MCA 코드 | MSCOD=0x0008 |
| dmesg 시그니처 | pcieport ... link down + IIO MCA 에러 |
| 원인 |
|
| 핫플러그 vs 에러 | 핫플러그 지원 슬롯에서의 의도적 제거는 Surprise Link Down을 정상 이벤트로 처리. 비핫플러그 슬롯에서 발생하면 에러로 취급 |
| 조치 |
|
시나리오 4: DMA/IOAT 엔진 에러 (CBDMA/DSA)
| 항목 | 내용 |
|---|---|
| MCA 코드 | MSCOD=0x0200 범위 |
| 영향 범위 | IOAT(I/O Acceleration Technology) DMA 채널, DSA(Data Streaming Accelerator) |
| 원인 |
|
| 커널 IOAT 관련 | 커널의 ioatdma 드라이버가 네트워크 DMA offload,
dmaengine 프레임워크에서 사용. 에러 시 채널 리셋 후 재시도 |
| DSA 관련 | SPR+ CPU의 DSA(Data Streaming Accelerator)는 idxd 드라이버로 관리.
메모리 복사/비교/CRC 가속. 에러 시 work queue 리셋 |
시나리오 5: VT-d (IOMMU) 변환 실패
| 항목 | 내용 |
|---|---|
| MCA 코드 | MSCOD=0x0400 범위 |
| dmesg 시그니처 | DMAR: DRHD: handling fault status reg ... reason 06 |
| 원인 |
|
| 가상화 영향 | KVM/QEMU VFIO passthrough 환경에서 게스트가 잘못된 DMA 주소를 프로그래밍하면 VT-d fault → IIO MCA 에러 → 게스트 강제 종료 가능 |
IIO 데이터 경로와 에러 감지 지점
IIO 스택 내부에서 PCIe 트랜잭션(Transaction)은 여러 단계를 거치며, 각 단계마다 독립적인 에러 감지 로직이 있습니다. 에러가 어느 단계에서 감지되었는지에 따라 MSCOD와 복구 가능성이 달라집니다.
| 에러 지점 | 컴포넌트 | 감지 에러 | MSCOD | 심각도 |
|---|---|---|---|---|
| ① | IRP 주소 디코드 | 잘못된 BAR/MMIO 주소 접근 | 0x0003 | UC (Unsupported Request) |
| ② | IRP CTO 타이머 | Completion Timeout | 0x0000 | UC Non-Fatal 또는 Fatal |
| ③ | ITC Ingress | Poisoned TLP 수신 | 0x0002 | UC — Poison 전파 여부에 따라 변동 |
| ④ | ITC 버퍼 | 수신 버퍼 오버플로 | 0x0001 | UC (데이터 손실) |
| ⑤ | OTC 패리티 | Egress 경로 내부 패리티 | 0x0005 | UC Internal |
| ⑥ | OTC 크레딧 | 흐름 제어 크레딧 에러 | 0x0007 | UC (링크 교착 가능) |
| ⑦ | PCIe TL | ECRC 검증 실패 | 0x0004 | UC (데이터 무결성) |
| ⑧ | PCIe TL | Malformed TLP | 0x0006 | UC Fatal |
| ⑨ | PCIe PL | Surprise Link Down | 0x0008 | UC Fatal |
| ⑩ | PCIe DLL | DLLP CRC / Replay Timeout | 0x0010 (CE) | CE (자동 재전송(Retransmission)) |
| ⑪ | VT-d | DMA 주소 변환 실패 | 0x0400 | UC (DMA Remap Fault) |
IIO Poison 전파 모델과 Viral Mode
PCIe 장치에서 Poisoned TLP를 수신한 IIO는 두 가지 전략 중 하나를 선택합니다: Containment(격리) 또는 Propagation(전파). 이 동작은 BIOS의 Viral Mode 설정과 IIO 내부 로직에 의해 결정됩니다.
| BIOS 설정 | 동작 | 권장 환경 | MCE 관점 |
|---|---|---|---|
| Viral Mode Disabled (기본) | IIO가 Poison을 즉시 소비하고 MCE/CMCI 발생 | 일반 서버, HPC | IIO 뱅크에서 즉시 에러 보고. 피해 범위 명확 |
| Viral Mode Enabled | Poison을 메모리까지 전파, 실제 소비 시점에 SRAR MCE | 미션 크리티컬 (정확한 소비자 식별 필요) | 시간차 MCE. memory_failure()로 해당 페이지만 오프라인 |
| Viral + MCA Recovery | Poison 전파 + 소비 시 프로세스만 킬 (패닉 회피) | 클라우드, 가상화 (VM 격리) | 가장 정교한 복구. CPU의 MCG_CAP.MCG_SER_P=1 필요 |
IIO와 PCIe 스위치/팬아웃 토폴로지(Topology)
GPU 클러스터, NVMe JBOF, 스토리지 어레이에서는 PCIe 스위치(예: Broadcom PEX, Microchip Switchtec)를 통해 여러 장치를 하나의 IIO 스택에 연결합니다. 스위치를 경유한 에러는 디버깅이 더 복잡합니다.
| 토폴로지 | IIO 관점 | 에러 진단 특징 |
|---|---|---|
| 직결 (Direct Attach) | IIO Root Port → 장치 (1:1) | MCi_MISC BDF가 정확히 에러 장치를 가리킴 |
| PCIe 스위치 경유 | IIO Root Port → 스위치 → 장치 (1:N) | MCi_MISC BDF가 스위치 Downstream Port를 가리킴. 실제 에러 장치는 스위치의 AER 로그에서 확인해야 함 |
| 다단 스위치 (Cascaded Switch) | IIO → Switch → Switch → 장치 | BDF가 최상위 스위치를 가리킴. 하위 스위치의 포트별 AER 확인 필요 |
| NTB (Non-Transparent Bridge) | IIO → NTB → 원격 호스트 | NTB 뒤의 에러는 NTB 자체 에러로 보고됨. 원격 호스트 로그 확인 필수 |
| GPU NVLink/NVSwitch | IIO → GPU (NVLink는 별도 경로) | GPU 간 P2P(NVLink)는 IIO 무관. GPU↔CPU는 IIO 경유. GPU PCIe BAR 접근 시 IIO 에러 가능 |
| CXL over IIO | IIO → CXL 장치 (PCIe 물리 계층 공유) | CXL.io 에러는 IIO MCA로, CXL.mem 에러는 GHES/CPER로 보고 |
# PCIe 스위치 뒤 장치 에러 추적 워크플로
# 1. IIO MCE에서 BDF 식별 → 스위치 Downstream Port일 수 있음
$ lspci -s <BDF>
# 출력 예: 85:00.0 PCI bridge: Broadcom PEX 8747 (rev ca)
# 2. 스위치 하위 장치 나열
$ lspci -tv -s <BDF>
# 3. 스위치 자체 AER 상태 확인
$ cat /sys/bus/pci/devices/0000:<BDF>/aer_dev_nonfatal
# 4. 스위치 하위 포트별 AER 확인
for dev in $(lspci -D | grep "PCI bridge" | awk '{print $1}'); do
echo "--- $dev ---"
cat /sys/bus/pci/devices/$dev/aer_dev_nonfatal 2>/dev/null
done
# 5. Switchtec 관리 도구 (Microchip 스위치)
$ switchtec status /dev/switchtec0
$ switchtec log-dump /dev/switchtec0
추가 IIO 에러 시나리오
시나리오 6: PCIe Replay Timer Timeout (DLLP CE)
| 항목 | 내용 |
|---|---|
| MSCOD | 0x0010 (Correctable Internal — DLLP 계층) |
| 의미 | Data Link Layer의 ACK/NAK 프로토콜에서 재전송 타임아웃. TLP가 성공적으로 전달되지 못해 재전송됨 |
| CE/UC | CE — 자동 재전송으로 복구. 반복 시 링크 품질 저하 경고 |
| 원인 |
|
| 모니터링 | lspci -vvv의 CESta:에서 RxErr+, Replay+ 확인.
카운터가 지속 증가하면 물리 계층 점검 필요 |
| Gen5 특이사항 | PCIe 5.0(32 GT/s)에서는 시그널(Signal) 마진이 매우 좁아 리타이머(Retimer) 없이 장거리 연결 시 Replay CE가 빈번할 수 있음 |
시나리오 7: MMIO 트랜잭션 에러 (CPU→장치 방향)
| 항목 | 내용 |
|---|---|
| 경로 | CPU 코어 → Mesh → IIO OTC → PCIe Core → 장치 BAR 영역 |
| 에러 유형 |
|
| 특이사항 | MMIO 에러는 CPU가 동기적으로 접근하는 경우(드라이버의 readl(),
writel())에 발생. DMA(장치→메모리)와 달리 에러 시점이 명확 |
| 커널 보호 | pci_enable_pcie_error_reporting()으로 드라이버가 AER을 활성화.
MMIO 에러 발생 시 pcie_do_recovery()로 장치 리셋 시도 |
시나리오 8: PCIe AtomicOp / Extended Tag 에러
| 항목 | 내용 |
|---|---|
| 에러 유형 | AtomicOp Egress Blocked, Extended Tag Mismatch |
| 배경 | PCIe AtomicOp은 GPU/FPGA가 호스트 메모리에 원자적 연산(Atomic Operation)을 수행할 때 사용. Extended Tag(10-bit)는 Gen4+에서 미해결 트랜잭션 수를 확대 |
| 원인 |
|
| GPU 관련 | NVIDIA GPU의 PCIe P2P 또는 GPUDirect RDMA에서 AtomicOp 사용. IIO에서 차단되면 GPU 드라이버 초기화 실패 또는 성능 저하 |
| 조치 | BIOS에서 PCIe AtomicOp Egress Enable 활성화.
setpci로 Device Control 2 레지스터의 AtomicOp 비트 확인 |
시나리오 9: DMI (Stack 0) 에러
| 항목 | 내용 |
|---|---|
| IIO 스택 | Stack 0 — DMI(Direct Media Interface)로 PCH에 연결 |
| 에러 유형 |
|
| 영향 | DMI는 PCH의 유일한 상류 링크이므로, DMI 에러 시 PCH 하위 모든 장치(SATA, USB, 온보드 LAN, BMC)에 영향. 심한 경우 서버 관리 불가 |
| 원인 | 메인보드 PCB 결함, CPU/PCH 간 라우팅 문제, PCH 하드웨어 결함 |
| 조치 | 메인보드 교체 검토. DMI CE가 지속되면 CPU 또는 PCH 교체 |
시나리오 10: 가상화 환경 VFIO Passthrough IIO 에러
| 항목 | 내용 |
|---|---|
| 환경 | KVM/QEMU + VFIO passthrough (GPU/NIC/NVMe를 VM에 직접 할당) |
| 에러 유형 |
|
| 격리 | VFIO는 IOMMU(VT-d)로 게스트의 DMA를 격리하지만, PCIe 레벨 에러(CTO, Link Down)는 IIO에서 감지되어 호스트에 MCE를 발생시킴. 이로 인해 하나의 게스트 문제가 호스트 전체에 영향을 줄 수 있음 |
| 완화 |
|
Intel 세대별 IIO 변천사
| CPU 세대 | 코드네임 | PCIe 지원 | IIO 특징 | 주요 변경 |
|---|---|---|---|---|
| Xeon Scalable 1st | Skylake-SP (SKX) | PCIe 3.0, 48레인 | IIO Stack 0~5, Bank 5~8 | IIO MCA 최초 표준화, CBDMA 8채널 |
| Xeon Scalable 2nd | Cascade Lake (CLX) | PCIe 3.0, 48레인 | SKX와 동일 | 마이크로코드 개선 (IIO CE 필터링) |
| Xeon Scalable 3rd | Ice Lake-SP (ICX) | PCIe 4.0, 64레인 | IIO 스택 확장, Bank 5~10 | PCIe Gen4 → 대역폭(Bandwidth) 2배, 링크 에러 감지 강화 |
| Xeon Scalable 4th | Sapphire Rapids (SPR) | PCIe 5.0, 80레인 | IIO 재설계, DSA/IAA/QAT 통합 | DSA(Data Streaming Accelerator), IAA(In-Memory Analytics Accelerator) 추가. CXL 1.1 Type 3 지원. MDF(Mesh Data Fabric) MCA 뱅크 신설 |
| Xeon Scalable 5th | Emerald Rapids (EMR) | PCIe 5.0, 80레인 | SPR 기반 개선 | 마이크로코드 IIO 에러 핸들링 개선, LMCE IIO 지원 확대 |
| Xeon 6 | Granite Rapids (GNR) | PCIe 5.0, 96레인 | IIO 확장, CXL 2.0 | CXL 2.0 Type 1/2/3, 더 많은 IIO 스택, MCA 뱅크 확장 |
lspci -vvv의 LnkSta:에서 링크 속도와 폭 다운그레이드 여부를 확인해야 합니다.
IIO 에러 디버깅 워크플로
# 1. IIO MCA 뱅크 에러 확인
$ dmesg | grep -i "iio\|bank [5-8]\|mce.*bank"
# 2. PCIe AER 에러 동시 확인
$ dmesg | grep -i "aer\|pcieport"
# 3. 에러 발생 장치 BDF 식별 (MCA MISC에서 디코딩)
$ mcelog --client | grep -i "iio\|misc"
# 4. 해당 BDF의 장치 정보 확인
$ lspci -vvvs <BDF>
# 5. PCIe 링크 상태 확인
$ lspci -vvvs <BDF> | grep -i "lnksta\|lnkcap\|devsta\|devctl"
# 6. AER 에러 카운터 확인
$ cat /sys/bus/pci/devices/0000:<BDF>/aer_dev_correctable
$ cat /sys/bus/pci/devices/0000:<BDF>/aer_dev_fatal
$ cat /sys/bus/pci/devices/0000:<BDF>/aer_dev_nonfatal
# 7. 장치별 세부 진단
# GPU:
$ nvidia-smi -q -d ERRORS,TEMPERATURE,POWER
# NVMe:
$ nvme smart-log /dev/nvmeN
$ nvme error-log /dev/nvmeN
# NIC:
$ ethtool -S <interface> | grep -i "error\|drop\|crc"
# 8. PCIe topology와 NUMA 매핑 확인
$ lspci -tv
$ lstopo --of txt
Uncore MCA 에러 종합 분석
IIO 외에도 CPU의 Uncore 영역에는 여러 MCA 에러 소스가 존재합니다. Uncore는 CPU 코어 외부에 있지만 CPU 다이 내부에 통합된 공유 자원 — LLC(Last Level Cache), 메모리 컨트롤러, 메시 인터커넥트, 전력 관리 유닛 등 — 을 포함합니다. 데이터센터에서 발생하는 MCE의 상당 부분이 이 Uncore 영역에서 옵니다.
M2M (Mesh to Memory) 에러
M2M은 CPU 내부 메시 인터커넥트와 iMC(메모리 컨트롤러) 사이의 중개 역할을 합니다. 메모리 요청의 주소 디코딩, 인터리빙, 미러링, 스페어링 결정을 담당합니다. Bank 9~12(SKX 기준)에 매핑됩니다.
| 에러 유형 | MSCOD | 설명 | 일반적 원인 |
|---|---|---|---|
| Patrol Scrub UC | 0x0010 | 패트롤 스크러빙이 교정 불가 에러 발견 | DIMM 열화, ECC 2비트+ 오류 |
| Patrol Scrub CE | 0x0010 (UC=0) | 패트롤 스크러빙이 교정 가능 에러 발견 | 단일 비트 에러 (정상 동작) |
| Demand Read UC | 0x0001 | 데이터 읽기 중 교정 불가 에러 | DRAM 셀 고장, Row Hammer |
| Full Mirror Failover | 0x0020 | 미러 채널로 페일오버 발생 | 기본 채널 DIMM 장애 |
| Partial Mirror | 0x0021 | 부분 미러 영역 에러 | 미러 영역 DIMM 열화 |
| Sparing Failover | 0x0030 | 스페어 랭크로 전환 완료 | CE 임계값 초과 → 자동 스페어링 |
| ADDDC Failover | 0x0040 | ADDDC(Adaptive Double DRAM Device Correction) 활성화 | 단일 DRAM 디바이스 장애 교정 |
| Address Parity | 0x0100 | 메모리 주소 버스 패리티 에러 | 하드웨어 결함 (메모리 버스) |
CHA (Caching Home Agent) / LLC 에러
CHA는 LLC(Last Level Cache) 슬라이스와 코히런스 디렉토리를 관리합니다. Bank 21~24+(코어 수에 따라 가변)에 매핑됩니다.
| 에러 유형 | 설명 | 영향 |
|---|---|---|
| LLC ECC CE | LLC 데이터 ECC 교정 가능 에러 | 자동 교정, 로그만 기록 |
| LLC ECC UC | LLC 데이터 ECC 교정 불가 에러 | 데이터 손상 가능 → PCC=1이면 패닉 |
| LLC Tag Parity | LLC 태그 패리티 에러 | 캐시라인 무효화 후 재페치 (교정 가능 시) |
| Snoop Filter Error | 코히런스 디렉토리 에러 | 멀티소켓에서 데이터 불일치 가능 |
| TOR Timeout | Table of Requests 엔트리 타임아웃 | 미처리 요청 → 시스템 행/패닉 |
| SF/LLC Way Error | 특정 LLC Way 접근 실패 | 캐시 용량 감소 (해당 Way 비활성화) |
TOR (Table of Requests) 타임아웃
TOR 타임아웃은 CHA의 트랜잭션 큐에서 요청이 완료되지 못하고 타임아웃된 경우입니다. 이는 매우 심각한 상황으로, 거의 항상 시스템 패닉을 유발합니다.
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: CPU N: Machine Check: ... Bank CHA ... TOR Timeout |
| 일반적 원인 |
|
| MCi_MISC 정보 | 타임아웃된 요청의 주소, 요청 유형(Read/Write/Snoop), 대상 유닛 정보 |
| 조치 |
|
UPI (Ultra Path Interconnect) 링크 에러
UPI는 멀티소켓 시스템에서 CPU 간 통신을 담당합니다. Bank 5~6(SKX 기준)에 매핑됩니다. 원격 메모리 접근(NUMA), 코히런스 프로토콜, 인터럽트 라우팅에 사용됩니다.
| 에러 유형 | 설명 | 영향 | 원인 |
|---|---|---|---|
| CRC Error (CE) | 링크 CRC 검사 실패 (교정됨) | 자동 재전송, 성능 약간 저하 | 일시적 신호 간섭, 커넥터 열화 |
| CRC Error (UC) | 링크 CRC 교정 불가 | 링크 다운, 원격 소켓 격리 | 하드웨어 결함, 심한 전기적 잡음 |
| L0p/L1 Entry Error | 전력 절약 모드 진입 실패 | 전력 효율 저하 | 링크 트레이닝 문제 |
| Protocol Error | UPI 프로토콜 위반 | 시스템 패닉 (PCC=1 가능) | 마이크로코드/하드웨어 결함 |
| Phy Init Error | 물리 계층 초기화 실패 | 링크 비활성 | 소켓/보드 하드웨어 문제 |
PCU (Power Control Unit) 에러
PCU는 CPU 전력 관리, 주파수 제어(P-state, C-state), 온도 모니터링을 담당합니다. Bank 4에 매핑됩니다. PCU 에러는 전력/열 관련 문제를 나타냅니다.
| 에러 유형 | 설명 | 원인 |
|---|---|---|
| Internal Firmware Error | PCU 마이크로컨트롤러 펌웨어 에러 | 마이크로코드 버그, 전력 시퀀싱 문제 |
| VR (Voltage Regulator) Error | 전압 조정기 통신 에러 | VRM 하드웨어 결함, 전원 공급 불안정 |
| MCA Timeout | PCU 내부 타이머 만료 | 전력 상태 전환 중 교착 |
| Core/Mesh Ratio Error | 주파수 비율 설정 실패 | 오버클럭 또는 불안정한 전력 공급 |
MDF (Mesh Data Fabric) — SPR 이후
Sapphire Rapids부터 도입된 MDF(Mesh Data Fabric)는 기존 CHA/메시의 데이터 패브릭을 별도 MCA 뱅크로 분리한 것입니다. 메시 내부의 데이터 전송 경로에서 발생하는 에러를 보고합니다.
| 에러 유형 | 설명 | 영향 |
|---|---|---|
| Data Fabric Parity | 메시 내부 데이터 전송 패리티 에러 | CE: 자동 교정, UC: 패닉 가능 |
| Credit Overflow/Underflow | 크레딧 기반 흐름 제어 에러 | 메시 교착 가능 |
| Routing Error | 메시 라우팅 테이블(Routing Table) 에러 | 패킷(Packet) 오배달 → 데이터 손상 |
Intel Cascade Lake 아키텍처 기준 MCA 뱅크 상세
Cascade Lake-SP(CLX)는 Xeon Scalable 2세대로, Skylake-SP(SKX)의 MCA 아키텍처를 기반으로 하되 RAS 기능이 크게 강화된 세대입니다. 현재도 데이터센터에서 널리 운용되고 있어, MCE 분석의 기준 아키텍처로 적합합니다. 이후 세대(ICX, SPR, EMR, GNR)와의 차이를 이해하는 데도 Cascade Lake를 기준으로 삼으면 효과적입니다.
Cascade Lake 고유 RAS 기능과 MCA 영향
| 기능 | SKX (1세대) | CLX (2세대) 변경 | MCA 관점 영향 |
|---|---|---|---|
| ADDDC | 미지원 | Adaptive Double DRAM Device Correction 도입 | M2M 뱅크(Bank 9~12)에서 ADDDC Failover 이벤트(MSCOD=0x0040) 보고. ADDDC 활성화 시 단일 DRAM 칩 고장을 교정 → CE로 보고 (UC 방지) |
| Optane PMem (2LM) | 미지원 | Memory Mode: DRAM이 캐시, Optane이 주 메모리 | iMC 뱅크에서 Optane 미디어 에러 보고. 2LM 캐시 미스 시 Optane 접근 → 추가 지연. Optane UC 에러 시 DRAM 캐시에 있으면 복구 가능, 없으면 UC MCE |
| Partial Mirror | Full Mirror만 | 주소 범위 지정 미러링 지원 | M2M 뱅크에서 Partial Mirror Failover(MSCOD=0x0021) 보고. 미러 영역 외 에러는 일반 UC 처리 |
| IIO CE 필터링 | 모든 IIO CE 보고 | 마이크로코드 개선으로 일부 IIO CE 자동 필터링 | IIO CMCI 빈도 감소. spurious CE에 의한 CMCI 스톰 완화 |
| LMCE 확대 | 제한적 | 더 많은 에러 유형에 LMCE 적용 | iMC UC(Demand Read) 시 LMCE로 해당 코어만 MCE 수신 → Monarch 오버헤드 감소 |
| Corrected Error Collector | 기본 | CE 누적 임계값 기반 사전 경고 강화 | CE 누적이 임계값 초과 시 soft_offline_page() 자동 호출.
/sys/devices/system/machinecheck/machinecheck0/ce_count에서 확인 |
- Cascade Lake(CLX): 최대 ~48개 뱅크. 기준 아키텍처. UPI 2개, IIO 6스택, 6채널 DDR4
- Ice Lake-SP(ICX): ~60개 뱅크. PCIe Gen4, 8채널 DDR4. IIO 뱅크 확장(Bank 5~10). CHA 뱅크 수 증가(40코어 → Bank 21~60)
- Sapphire Rapids(SPR): ~80개+ 뱅크. PCIe Gen5, 8채널 DDR5, CXL 1.1. MDF 뱅크 신설(Mesh Data Fabric), HBM MC 뱅크(HBM SKU), DSA/IAA/QAT 가속기 뱅크. M2M → IMC(Integrated Memory Controller)로 재편
- Granite Rapids(GNR): ~100개+ 뱅크. CXL 2.0, 12채널 DDR5(MRDIMM). 더 많은 IIO 스택, CXL DP(Downstream Port) 전용 뱅크 추가
Cascade Lake ADDDC 동작 상세
ADDDC(Adaptive Double DRAM Device Correction)는 CLX에서 도입된 핵심 RAS 기능으로, DIMM 내 단일 DRAM 칩(x4)의 완전 장애까지 교정할 수 있습니다. 기존 SDDC(Single Device Data Correction)의 확장입니다.
| 단계 | 상태 | MCA 이벤트 | 커널 동작 |
|---|---|---|---|
| 1. 정상 | ADDDC 대기 (Standby) | 없음 | 정상 동작, patrol scrub 활성 |
| 2. CE 누적 | SDDC로 교정 중 | iMC CE (Bank 13~20), EDAC 카운터 증가 | rasdaemon 기록, 모니터링 |
| 3. CE 임계값 초과 | ADDDC Region 활성화 | M2M ADDDC Failover (MSCOD=0x0040) | EDAC: "ADDDC engaged". 해당 Rank에서 추가 ECC 비트 할당 |
| 4. ADDDC 활성 중 | 2개 DRAM 디바이스 에러까지 교정 | CE로 계속 보고 (UC 방지) | DIMM 교체 권고. ADDDC 소진 시 UC 위험 |
| 5. ADDDC 소진 | 3번째 디바이스 장애 | iMC UC → M2M UC → 패닉 가능 | DIMM 교체 필수 |
# ADDDC 상태 확인 (EDAC sysfs)
$ cat /sys/devices/system/edac/mc/mc0/dimm0/dimm_edac_mode
# 출력 예: "x4 SDDC" 또는 "x4 ADDDC"
# ADDDC Failover 이벤트 확인
$ dmesg | grep -i "adddc\|failover"
$ ras-mc-ctl --errors | grep -i "adddc"
# Cascade Lake에서 ADDDC 활성화 여부 (BIOS 설정)
# BIOS → Advanced → Memory Configuration → ADDDC Sparing: Enabled
Uncore 에러 간 연쇄(Cascade) 패턴
Uncore 에러는 종종 연쇄적으로 발생합니다. 하나의 근본 원인(root cause)이 여러 MCA 뱅크에서 동시다발적으로 에러를 보고하게 만듭니다. 연쇄 패턴을 이해하면 근본 원인을 더 빠르게 식별할 수 있습니다. 아래는 Cascade Lake 아키텍처를 기준으로 한 에러 전파 경로입니다.
연쇄 에러 근본 원인 분석 체크리스트
여러 MCA 뱅크에서 동시에 에러가 보고되면, 다음 절차로 근본 원인을 식별합니다.
- 타임스탬프 정렬:
dmesg -T또는rasdaemonDB에서 에러를 시간 순서로 정렬. 가장 이른 타임스탬프의 에러가 root cause 후보 - OVER 비트 확인: MCi_STATUS.OVER=1인 뱅크는 정보 손실이 있으므로, 해당 뱅크가 실제 1차 에러 소스였을 가능성 높음
- 뱅크 간 우선순위:
- iMC(Bank 13~20) UC가 있으면 → DIMM이 근본 원인일 가능성 최우선
- IIO(Bank 7~8) CTO가 있으면 → PCIe 장치가 근본 원인
- UPI(Bank 5~6) UC가 있으면 → 소켓 간 링크 문제
- CHA TOR Timeout만 있으면 → 2차 에러, 다른 뱅크에서 근본 원인 탐색
- BERT 확인: 패닉 후 재부팅 시
dmesg | grep BERT로 이전 부팅의 에러 정보 수집. BERT에 1차 에러가 남아 있을 수 있음 - BMC/IPMI SEL:
ipmitool sel list에서 OS 레벨 로그와 교차 확인. 펌웨어가 OS보다 먼저 에러를 감지한 경우 있음 - 동일 소켓/채널 집중: 에러가 특정 소켓, 특정 메모리 채널, 특정 PCIe 슬롯에 집중되면 해당 하드웨어 교체 고려
# 연쇄 에러 분석 스크립트 예시
# 모든 MCA 뱅크 에러를 타임스탬프순으로 정렬
$ dmesg -T | grep -i "mce\|hardware error\|machine check" | sort
# rasdaemon DB에서 최근 1시간 에러를 뱅크별로 그룹화
$ sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT bank, error_type, COUNT(*), MIN(timestamp), MAX(timestamp)
FROM mce_record
WHERE timestamp > strftime('%s','now','-1 hour')
GROUP BY bank ORDER BY MIN(timestamp);"
# BERT (Boot Error Record) 확인 — 패닉 후 재부팅 시
$ dmesg | grep -A 20 "BERT"
# IPMI SEL 로그와 교차 확인
$ ipmitool sel list | tail -20
- TOR Timeout은 결과지 원인이 아닙니다. TOR Timeout만 보이면 다른 뱅크(iMC, IIO, UPI)에서 근본 원인을 반드시 찾아야 합니다.
- M2M 에러는 경유지입니다. M2M은 메시와 메모리 사이의 중개 역할이므로, M2M UC는 iMC 에러의 전파이거나 patrol scrub 발견일 수 있습니다.
- IIO Poison은 시간차가 있습니다. Viral Mode Enabled 시 Poison이 메모리에 저장된 후 나중에 SRAR로 나타나므로, IIO MCE와 코어 SRAR MCE 사이에 시간 차이가 있을 수 있습니다.
- CLX의 ADDDC는 연쇄를 지연시킵니다. ADDDC가 활성화되면 단일 DRAM 칩 장애를 CE로 교정하여 UC MCE 발생을 지연시키지만, ADDDC가 소진되면 갑자기 UC 연쇄가 시작될 수 있습니다.
LMCE (Local MCE)
전통적으로 MCE는 모든 논리 프로세서에 브로드캐스트됩니다. LMCE(Local Machine Check Exception)는 특정 CPU에만 영향을 미치는 에러를 해당 CPU에만 전달하여 불필요한 Monarch 랑데부를 피합니다.
LMCE 활성화 조건
MCG_CAP.MCG_LMCE_P = 1— CPU가 LMCE를 지원MCG_EXT_CTL.LMCE_EN = 1— 소프트웨어가 LMCE를 활성화IA32_FEATURE_CONTROL.LMCE_ON = 1— 펌웨어가 LMCE를 허용
LMCE의 이점
| 시나리오 | 기존 MCE | LMCE |
|---|---|---|
| 64코어 서버에서 1개 코어 CE | 64개 코어 모두 MCE 핸들러 진입, 랑데부 | 1개 코어만 핸들러 실행 |
| 멀티소켓에서 로컬 캐시 에러 | 전체 소켓 동기화 → 성능 영향 큼 | 해당 소켓의 해당 코어만 처리 |
| 가상화 환경 | 모든 vCPU에 VMEXIT 발생 | 에러 발생 vCPU만 VMEXIT |
/* arch/x86/kernel/cpu/mce/core.c */
static void mce_intel_feature_init(struct cpuinfo_x86 *c)
{
u64 cap, feat;
rdmsrl(MSR_IA32_MCG_CAP, cap);
if (cap & MCG_LMCE_P) {
rdmsrl(MSR_IA32_FEAT_CTL, feat);
if (feat & FEAT_CTL_LMCE_ENABLED) {
rdmsrl(MSR_IA32_MCG_EXT_CTL, cap);
cap |= MCG_EXT_CTL_LMCE_EN;
wrmsrl(MSR_IA32_MCG_EXT_CTL, cap);
}
}
}
/* LMCE 발생 여부 확인 */
static bool mce_is_lmce(struct mce *m)
{
return m->mcgstatus & MCG_STATUS_LMCES;
}
dmesg | grep LMCE로 부팅 시 LMCE 활성화 여부를 확인할 수 있습니다.
또는 rdmsr 0x4D0으로 MCG_EXT_CTL 레지스터를 직접 읽어볼 수 있습니다.
LMCE 지원 CPU 목록
| 프로세서 | 마이크로아키텍처 | LMCE 지원 | 비고 |
|---|---|---|---|
| Intel Xeon E5 v4 | Broadwell-EP (2016) | 최초 지원 | MCG_CAP.MCG_LMCE_P 비트 최초 도입 |
| Intel Xeon Scalable 1세대 | Skylake-SP (2017) | 완전 지원 | 서버 플랫폼 기본 활성화 |
| Intel Xeon Scalable 3세대 | Ice Lake-SP (2021) | 향상된 지원 | LMCE + Enhanced MCA 통합 |
| Intel Xeon Scalable 4세대 | Sapphire Rapids (2023) | 향상된 지원 | In-Band ECC + LMCE 연동 개선 |
| Intel Core 6세대+ | Skylake 클라이언트+ | 지원 (BIOS 의존) | IA32_FEATURE_CONTROL 설정 필요 |
| AMD EPYC/Ryzen | Zen 1~5 | N/A | SMCA(Scalable MCA)로 다른 접근: MCA_STATUS에 에러 소스 CPU 기록, 별도 LMCE 불필요 |
LMCE 판별 로직 상세
do_machine_check()에서 LMCE 여부에 따라 Monarch 랑데부를 건너뛰는 핵심 분기 로직입니다.
MCG_STATUS의 LMCES(bit 3) 비트가 설정되어 있으면 해당 MCE는 로컬 전용이므로
다른 CPU와 동기화할 필요가 없습니다.
/* arch/x86/kernel/cpu/mce/core.c — do_machine_check() 내부 LMCE 분기 */
noinstr void do_machine_check(struct pt_regs *regs)
{
struct mce m;
int worst = 0, order, no_way_out = 0;
bool lmce = false;
/* 1단계: MCG_STATUS 읽기 */
m.mcgstatus = mce_rdmsrl(MSR_IA32_MCG_STATUS);
/* 2단계: LMCE 여부 판별 — MCG_STATUS.LMCES (bit 3) */
lmce = m.mcgstatus & MCG_STATUS_LMCES;
/* 3단계: MCA 뱅크 순회 — 에러 정보 수집 */
for (i = 0; i < this_cpu_read(mce_num_banks); i++) {
m.status = mce_rdmsrl(mca_msr_reg(i, MCA_STATUS));
if (!(m.status & MCI_STATUS_VAL))
continue;
/* severity 평가, worst 갱신 */
worst = mce_severity(&m, regs, ...);
}
/* 4단계: LMCE면 랑데부 건너뛰기 */
if (!lmce) {
/* 브로드캐스트 MCE — Monarch 랑데부 진입 */
order = mce_start(&no_way_out);
/* ... 모든 CPU 동기화 후 Monarch 판정 ... */
mce_end(order);
} else {
/* LMCE — 이 CPU만 로컬 처리 */
/* 랑데부 불필요, 바로 에러 처리 진행 */
}
/* 5단계: 에러 로깅 및 액션 */
mce_log(&m);
if (worst >= MCE_PANIC_SEVERITY && !lmce)
mce_panic("Fatal MCE", &m, msg);
else if (worst >= MCE_AR_SEVERITY)
kill_me_maybe(cb); /* 프로세스 kill 또는 페이지 오프라인 */
}
IA32_FEATURE_CONTROL.LMCE_ON을
설정하지 않으면 CPU가 LMCE를 지원하더라도 비활성화 상태로 남습니다.
MCE와 KVM 가상화
물리 서버에서 하드웨어 메모리 오류가 발생하면, 호스트 커널의 MCE 핸들러가 먼저 처리합니다. 해당 오류가 게스트 VM에 할당된 메모리 영역에 영향을 미치는 경우, KVM 하이퍼바이저는 게스트에도 MCE를 주입하여 게스트 OS가 적절히 대응할 수 있도록 합니다.
호스트→게스트 MCE 전파 흐름
VMEXIT와 #MC 인터셉트
Intel VT-x 및 AMD-V는 게스트 실행 중 #MC 예외가 발생하면 무조건 VMEXIT를 수행합니다. 호스트가 반드시 먼저 제어권을 확보하는 구조입니다.
| 항목 | Intel VT-x | AMD-V (SVM) |
|---|---|---|
| VMEXIT 사유 | EXIT_REASON_MCE_DURING_VMENTRY (41) | SVM_EXIT_MC (0x052) |
| 인터셉트 설정 | VMCS Exception Bitmap bit 18 | VMCB Intercept[INTERCEPT_MC] |
| 비활성화 가능 여부 | 불가 (항상 인터셉트) | 불가 (항상 인터셉트) |
| 호스트 핸들러 | handle_machine_check() | svm_handle_mce() |
KVM_X86_SET_MCE ioctl을 통한 게스트 MCE 주입
/* include/uapi/linux/kvm.h — MCE 주입 구조체 */
struct kvm_x86_mce {
__u64 status; /* MCi_STATUS 값 */
__u64 addr; /* MCi_ADDR 값 */
__u64 misc; /* MCi_MISC 값 */
__u64 mcg_status; /* MCG_STATUS 값 */
__u8 bank; /* MCA 뱅크 번호 */
__u8 pad1[7];
__u64 pad2[3];
};
/* QEMU에서의 MCE 주입 예시 */
struct kvm_x86_mce mce = {
.status = MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN
| MCI_STATUS_ADDRV | 0x0136, /* SRAR: Data Load */
.addr = guest_phys_addr,
.mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV,
.bank = 1,
};
ioctl(vcpu_fd, KVM_X86_SET_MCE, &mce);
HWPoison과 게스트 메모리 영향
호스트가 물리 페이지를 HWPoison으로 마킹하면, KVM은 GPA↔HPA 매핑을 추적하여 해당 게스트에 MCE를 주입합니다. 동시에 EPT/NPT 테이블에서 매핑을 제거합니다.
| 단계 | 처리 주체 | 동작 |
|---|---|---|
| 1. 오류 감지 | 호스트 MCE 핸들러 | MCA 뱅크 읽기, 오류 분류 |
| 2. 페이지 격리 | 호스트 메모리 관리 | HWPoison 플래그 설정, 페이지 오프라인 |
| 3. QEMU 알림 | SIGBUS | QEMU 프로세스에 BUS_MCEERR_AO 시그널 전달 |
| 4. MCE 주입 | QEMU → KVM | GPA 계산 후 KVM_X86_SET_MCE ioctl 호출 |
| 5. EPT/NPT 언맵 | KVM MMU | 오염 페이지의 2차 매핑 제거 |
| 6. 게스트 처리 | 게스트 커널 | 게스트 내부 memory_failure() 실행 |
/* arch/x86/kvm/x86.c — MCE 브로드캐스트 및 주입 (개념 코드) */
void kvm_mce_broadcast(struct kvm *kvm, unsigned long pfn)
{
struct kvm_vcpu *vcpu;
gfn_t gfn = kvm_pfn_to_gfn(pfn);
kvm_for_each_vcpu(i, vcpu, kvm) {
struct kvm_x86_mce mce = {
.status = MCI_STATUS_VAL | MCI_STATUS_UC | 0x0136,
.addr = gfn << PAGE_SHIFT,
.bank = 1,
};
/* LMCE 지원 시 오류 소유 vCPU에만 주입 */
if (kvm_vcpu_supports_lmce(vcpu) && kvm_vcpu_owns_gfn(vcpu, gfn)) {
mce.mcg_status |= MCG_STATUS_LMCE_S;
kvm_inject_mce(vcpu, &mce);
break;
}
kvm_inject_mce(vcpu, &mce); /* LMCE 미지원: 브로드캐스트 */
}
}
라이브 마이그레이션과 MCE/HWPoison
| 시나리오 | 처리 방식 | 위험도 |
|---|---|---|
| HWPoison 페이지 존재 (게스트 미접근) | 해당 페이지 스킵, 대상에서 제로 페이지 매핑 | 중간 |
| HWPoison 페이지 존재 (게스트 이미 통보됨) | 페이지 스킵, 가상 MCE 뱅크 상태 전송 | 중간 |
| 마이그레이션 중 UCE 발생 | 마이그레이션 중단 또는 페이지 격리 후 계속 | 높음 |
| vCPU MCE 뱅크 상태 전송 | KVM_GET_MSRS / KVM_SET_MSRS로 직렬화(Serialization) | 낮음 |
-cpu host,+mce,+mca,+lmce로 게스트에 MCE 기능을 노출합니다.
+lmce를 추가하면 로컬 MCE도 지원하여 가상화 오버헤드를 줄입니다.
KVM MCE 주입 상세 — 테스트 시나리오
가상화 환경에서 MCE 처리 경로를 테스트하려면 호스트에서 게스트로 다양한 유형의 MCE를 주입해야 합니다. 아래는 각 심각도별 MCE 주입 방법과 게스트에서의 예상 동작입니다.
/* KVM MCE 주입 테스트 — 심각도별 시나리오 (C 프로그램) */
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kvm.h>
void inject_mce(int vcpu_fd, u64 status, u64 addr,
u64 misc, u8 bank, const char *desc)
{
struct kvm_x86_mce mce = {
.status = status,
.addr = addr,
.misc = misc,
.mcg_status = (1ULL << 2), /* MCG_STATUS_MCIP */
.bank = bank,
};
printf("주입: %s (Bank=%d STATUS=0x%016llx)\n", desc, bank, status);
int ret = ioctl(vcpu_fd, KVM_X86_SET_MCE, &mce);
if (ret < 0)
perror("KVM_X86_SET_MCE 실패");
}
int main(void)
{
int vcpu_fd = open("/dev/kvm", O_RDWR);
/* ... KVM VM/vCPU 생성 코드 생략 ... */
/* 시나리오 1: CE (Corrected Error) — 게스트 로그만 기록 */
inject_mce(vcpu_fd,
0x9C00000000000150ULL, /* VAL+EN+MISCV+ADDRV, UC=0 */
0x0000000012340000ULL, /* 물리 주소 */
0x0000000000000086ULL, /* AddrMode=물리, LSB=6 */
9, /* Bank 9: 메모리 컨트롤러 */
"CE 메모리 에러");
/* 시나리오 2: SRAR — 게스트 프로세스 SIGBUS */
inject_mce(vcpu_fd,
0xBD80000000100134ULL, /* VAL+UC+EN+MISCV+ADDRV+S+AR */
0x0000000087650000ULL, /* 에러 물리 주소 */
0x000000000000008CULL, /* AddrMode=물리, LSB=12(페이지) */
7, /* Bank 7 */
"SRAR 데이터 소비 에러");
/* 시나리오 3: UC+PCC — 게스트 패닉 */
inject_mce(vcpu_fd,
0xBE00000000000400ULL, /* VAL+OVER+UC+EN+MISCV+ADDRV+PCC */
0x0000000000000000ULL,
0x0000000000000000ULL,
4, /* Bank 4: PCU */
"UC+PCC 치명적 에러");
return 0;
}
# QEMU 모니터를 통한 간편 MCE 주입 방법
# QEMU 실행 시 MCE 기능 활성화
$ qemu-system-x86_64 \
-cpu host,+mce,+mca,+lmce \
-enable-kvm \
-m 8G \
-monitor stdio \
-drive file=guest.qcow2,format=qcow2
# 모니터에서 CE 주입 (게스트 커널 로그에만 기록)
(qemu) mce 0 9 0x9C00000000000150 0x12340000 0x86
# 모니터에서 SRAR 주입 (게스트 프로세스 SIGBUS)
(qemu) mce 0 7 0xBD80000000100134 0x87650000 0x8C
# 모니터에서 UC+PCC 주입 (게스트 패닉 — 주의!)
(qemu) mce 0 4 0xBE00000000000400 0x0 0x0
# 파라미터 형식: mce CPU BANK STATUS ADDR MISC
# 게스트에서 확인: dmesg | grep -i "mce\|hardware error"
KVM HWPoison 전파 메커니즘 상세
호스트에서 물리 메모리 페이지에 HWPoison이 설정되면, KVM은 해당 페이지를 매핑하고 있는 모든 게스트를 식별하여 적절한 조치를 수행합니다. 이 과정은 여러 서브시스템의 협력으로 이루어집니다.
/* KVM HWPoison 전파 경로 (최근 6.x 계열 개념 코드) */
/* 1단계: 호스트 memory_failure()가 페이지를 HWPoison으로 마킹 */
/* → collect_procs()가 해당 HPA를 매핑한 프로세스(QEMU 포함) 수집 */
/* → QEMU 프로세스에 SIGBUS(BUS_MCEERR_AO) 전달 */
/* 2단계: QEMU SIGBUS 핸들러 */
static void kvm_hwpoison_page_add(ram_addr_t addr)
{
/* HPA → GPA 변환 */
ram_addr_t ram_addr = qemu_ram_addr_from_host(siginfo->si_addr);
hwaddr paddr = ram_addr; /* QEMU 내부 물리 주소 = GPA */
/* 3단계: KVM ioctl로 게스트에 MCE 주입 */
struct kvm_x86_mce mce = {
.status = MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN
| MCI_STATUS_ADDRV | MCI_STATUS_MISCV
| MCI_STATUS_S | MCI_STATUS_AR /* SRAR */
| 0x0134, /* L3 Data Read error code */
.addr = paddr,
.misc = (2ULL << 6) | PAGE_SHIFT, /* 물리 주소 모드, 페이지 정밀도 */
.mcg_status = MCG_STATUS_MCIP | MCG_STATUS_RIPV,
.bank = 9,
};
/* 4단계: LMCE 지원 시 해당 vCPU에만 주입 */
for_each_vcpu(vcpu) {
if (kvm_vcpu_ioctl(vcpu, KVM_X86_SET_MCE, &mce) < 0)
error_report("MCE 주입 실패: vCPU %d", vcpu->id);
}
/* 5단계: EPT/NPT에서 해당 GPA의 매핑 제거 */
/* → 게스트가 이 주소에 다시 접근하면 EPT violation → KVM 핸들러 */
/* → KVM이 에러 반환 (KVM_EXIT_SHUTDOWN 또는 MCE 재주입) */
}
APEI/GHES 통합
APEI(ACPI Platform Error Interface)는 플랫폼 펌웨어와 OS 간의 표준 에러 보고 인터페이스입니다. 서버 플랫폼에서는 펌웨어가 먼저 에러를 수집하고(Firmware-First), GHES(Generic Hardware Error Source)를 통해 OS에 전달하는 방식이 일반적입니다.
APEI 테이블 구성요소
| ACPI 테이블 | 역할 |
|---|---|
| HEST (Hardware Error Source Table) | 하드웨어 에러 소스 목록과 각 소스의 통지 방법 정의 |
| BERT (Boot Error Record Table) | 이전 부팅에서 발생한 치명적 에러를 다음 부팅에서 읽기 |
| ERST (Error Record Serialization Table) | 에러 레코드를 비휘발성 저장소에 영속 저장 |
| EINJ (Error Injection Table) | 테스트용 에러 주입 인터페이스 |
CPER (Common Platform Error Record)
GHES가 전달하는 에러 데이터는 CPER 포맷을 따릅니다. 각 에러 레코드에는 하나 이상의 Section이 포함되며, 섹션 타입별로 메모리 에러, PCIe 에러, 프로세서 에러 등을 구분합니다.
/* drivers/acpi/apei/ghes.c — GHES 처리 */
static int ghes_proc(struct ghes *ghes)
{
struct acpi_hest_generic_status *estatus;
estatus = ghes->estatus;
ghes_copy_tofrom_phys(estatus, ghes->buffer_paddr, ...);
/* CPER 섹션 순회 */
apei_estatus_for_each_section(estatus, gdata) {
guid_t *sec_type = (guid_t *)gdata->section_type;
if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
/* 메모리 에러 → memory_failure() */
ghes_handle_memory_failure(gdata, sev);
} else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
/* PCIe 에러 */
ghes_handle_aer(gdata);
}
}
}
GHES 통지 방법
GHES는 에러를 OS에 알리는 여러 통지 메커니즘을 지원합니다. 통지 방법은 HEST 테이블에 정의되며, 에러 심각도에 따라 다른 경로를 사용합니다.
| 통지 유형 | 사용 시나리오 | 커널 핸들러 |
|---|---|---|
| SCI (System Control Interrupt) | CE, 비긴급 에러 | ACPI SCI → ghes_proc() |
| NMI | UC, 긴급 에러 | NMI 핸들러 → ghes_proc() |
| GPIO/IRQ | 플랫폼 고유 인터럽트 | IRQ 핸들러 → ghes_proc() |
| Polled | 폴링 기반 수집 | 타이머 → ghes_proc() |
| SEA (ARM) | 동기 외부 중단 | do_sea() → ghes_notify_sea() |
| SEI (ARM) | 비동기 에러 | do_serror() → ghes_notify_sei() |
BERT (Boot Error Record Table)
이전 부팅에서 치명적 에러로 시스템이 리셋된 경우, 펌웨어는 에러 정보를 BERT 영역에 저장합니다. 다음 부팅 시 커널이 BERT를 읽어 이전 에러를 dmesg에 출력합니다.
# BERT 데이터 확인
$ dmesg | grep BERT
[ 0.123456] BERT: Error records from previous boot:
[ 0.123457] [Firmware Error]: Hardware error from APEI Generic Hardware Error Source: 0
[ 0.123458] severity: fatal
# BERT ACPI 테이블 덤프 (디버그)
$ sudo cat /sys/firmware/acpi/tables/BERT | hexdump -C | head
ERST (Error Record Serialization Table)
ERST는 에러 레코드를 비휘발성 저장소(플래시, NVRAM)에 영속적으로 저장하는 인터페이스입니다.
커널은 /sys/firmware/acpi/tables/ERST를 통해 에러 레코드를 쓰고 읽을 수 있습니다.
이는 crash dump와 함께 사후 분석에 활용됩니다.
CPER 메모리 에러 섹션 구조
GHES가 전달하는 메모리 에러는 UEFI 표준의 CPER Memory Error Section
(CPER_SEC_PLATFORM_MEM) 포맷을 따릅니다.
각 필드에는 Validation Bits가 있어 해당 필드가 유효한지를 나타냅니다.
| 필드 | 크기 | Validation Bit | 설명 |
|---|---|---|---|
| Validation Bits | 8 바이트 | — | 이후 각 필드의 유효 여부를 나타내는 비트맵(Bitmap) |
| Error Status | 8 바이트 | Bit 0 | UEFI 에러 상태 코드 (에러 유형: 내부/버스/메모리 등) |
| Physical Address | 8 바이트 | Bit 1 | 에러가 발생한 물리 주소 — memory_failure(pfn) 호출에 사용 |
| Physical Address Mask | 8 바이트 | Bit 2 | 유효 비트 범위 (어느 범위까지 영향받는지) |
| Node | 2 바이트 | Bit 3 | NUMA 노드 번호 |
| Card | 2 바이트 | Bit 4 | 메모리 카드/라이저 번호 |
| Module | 2 바이트 | Bit 5 | DIMM 모듈 번호 |
| Bank | 2 바이트 | Bit 6 | DRAM 뱅크 번호 |
| Device | 2 바이트 | Bit 7 | DRAM 디바이스 (칩) 번호 |
| Row | 2 바이트 | Bit 8 | 에러 발생 행 주소 |
| Column | 2 바이트 | Bit 9 | 에러 발생 열 주소 |
| Bit Position | 2 바이트 | Bit 10 | 에러 발생 비트 위치 (싱글비트 CE 시) |
| Error Type | 1 바이트 | Bit 14 | 0=Unknown, 2=Multi-bit, 3=Single-symbol Chipkill, 8=Scrub CE 등 |
| Module Handle (SMBIOS) | 2 바이트 | Bit 11 | SMBIOS Type 17 핸들 — DIMM 식별 (dmidecode 연동) |
/* drivers/acpi/apei/ghes.c — CPER 메모리 섹션 처리 */
static void ghes_handle_memory_failure(
struct acpi_hest_generic_data *gdata, int sev)
{
struct cper_sec_mem_err *mem_err;
unsigned long pfn;
mem_err = acpi_hest_get_payload(gdata);
/* Validation Bits 확인: 물리 주소 유효한지 */
if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
return;
/* Physical Address to PFN 변환 */
pfn = mem_err->physical_addr >> PAGE_SHIFT;
/* Physical Address Mask 적용 (유효 범위 계산) */
if (mem_err->validation_bits & CPER_MEM_VALID_PA_MASK)
pfn &= ~(mem_err->physical_addr_mask >> PAGE_SHIFT);
/* severity에 따라 분기 */
if (sev == GHES_SEV_RECOVERABLE) {
/* Action Required: 즉시 memory_failure() */
memory_failure_queue(pfn, MF_ACTION_REQUIRED);
} else {
/* Corrected: soft offline (예방적 페이지 격리) */
memory_failure_queue(pfn, 0);
}
/* 트레이스 이벤트 발생 — rasdaemon/mcelog 수집용 */
trace_mc_event(err_type, ...,
mem_err->node, mem_err->card, mem_err->module,
mem_err->bank, mem_err->device,
mem_err->row, mem_err->column, ...);
}
GHES 에러 심각도 매핑
GHES의 에러 심각도(CPER Error Severity)는 커널 내부의 MCE severity 수준으로 매핑됩니다. 이 매핑은 에러 처리 액션(패닉, 프로세스 kill, 로깅 등)을 결정합니다.
| GHES Severity | CPER 값 | 커널 매핑 | 처리 액션 |
|---|---|---|---|
| Fatal | CPER_SEV_FATAL (0) |
MCE_PANIC_SEVERITY |
즉시 커널 패닉(Kernel Panic) — 복구 불가, 시스템 정지 |
| Recoverable | CPER_SEV_RECOVERABLE (1) |
MCE_AR_SEVERITY |
memory_failure() → 페이지 오프라인, 프로세스 SIGBUS kill |
| Corrected | CPER_SEV_CORRECTED (2) |
MCE_KEEP_SEVERITY |
로깅 + 임계치 기반 soft offline (예방적 격리) |
| Informational | CPER_SEV_INFORMATIONAL (3) |
MCE_NO_SEVERITY |
로깅만 — 통계 수집, 추세 분석용 |
dmesg | grep "Hardware error"로 GHES가 보고한 에러의
심각도를 확인할 수 있습니다. severity: fatal이면 시스템이 이미 패닉 상태이므로
crash dump에서 확인해야 합니다.
rasdaemon --record를 실행하면 CE/UCE 이벤트를 SQLite DB에 기록하여
시계열 추세를 분석할 수 있습니다.
Firmware-First vs OS-First 선택 기준
에러 처리의 주체를 펌웨어(Firmware-First)와 OS(OS-First) 중 어디로 설정할지는 플랫폼 유형과 운영 환경에 따라 달라집니다. 다음 표는 실무 기준의 선택 가이드입니다.
| 플랫폼 유형 | 권장 방식 | 에러 경로 | 근거 |
|---|---|---|---|
| 서버 (UEFI + BMC/IPMI) | Firmware-First | HW → SMI → 펌웨어 → GHES(SCI/NMI) → OS | 펌웨어가 SEL에 기록, BMC 원격 알림, DIMM 위치 식별 가능 |
| 데스크톱/워크스테이션 | OS-First | HW → #MC → do_machine_check() 직접 | 대부분 HEST 없음. OS가 MCA 뱅크를 직접 읽는 것이 유일한 경로 |
| VM 게스트 | 하이퍼바이저 위임 | HW → 호스트 MCE → 하이퍼바이저 → 가상 MCE 주입 | 게스트는 MCA 뱅크 접근 불가. KVM은 kvm_mce_broadcast()로 가상 MCE 주입 |
| ARM 서버 | Firmware-First (SDEI/SEA) | HW → EL3 펌웨어 → GHES(SEA/SDEI) → OS | ARM APEI 지원. RAS 확장(ARMv8.2+) 필요 |
| 임베디드/IoT | 플랫폼 고유 | 플랫폼별 인터럽트 / GPIO / 폴링 | ACPI/UEFI 없는 환경. DT 기반 에러 핸들러 또는 SoC 고유 드라이버 |
| 클라우드 인스턴스 | CSP 위임 | HW → CSP 펌웨어 → 라이브 마이그레이션 또는 가상 MCE | AWS/GCP/Azure가 HW 에러 관리. 게스트에 필터링된 에러만 전달 |
mce=no_cmci와 BIOS에서 Firmware-First 비활성화를 함께 설정하면
OS-First로 전환할 수 있지만, DIMM 위치 식별 등 펌웨어 기능을 잃게 됩니다.
EDAC 서브시스템 연동
EDAC(Error Detection And Correction)은 ECC 메모리 에러를 추적하고 물리적 DIMM 위치까지 디코딩하는 리눅스 커널 서브시스템입니다. MCE 또는 GHES로부터 에러 통지를 받아 어떤 DIMM의 어떤 행/열에서 에러가 발생했는지 알려줍니다.
주요 EDAC 드라이버
| 드라이버 | 플랫폼 | 모듈명 |
|---|---|---|
| i10nm_edac | Intel Ice Lake / Sapphire Rapids | i10nm_edac |
| skx_edac | Intel Skylake-SP / Cascade Lake | skx_edac |
| sb_edac | Intel Sandy/Ivy Bridge | sb_edac |
| amd64_edac | AMD K8/Fam10h~Zen4 | amd64_edac |
| ghes_edac | GHES 기반 (모든 UEFI 서버) | ghes_edac |
/* drivers/edac/edac_mc.c */
void edac_mc_handle_error(
const enum hw_event_mc_err_type type,
struct mem_ctl_info *mci,
const u16 error_count,
const unsigned long page_frame_number,
const unsigned long offset_in_page,
const unsigned long syndrome,
const int top_layer, /* 채널 */
const int mid_layer, /* DIMM */
const int low_layer, /* 행/열 */
const char *msg,
const char *other_detail)
{
/* EDAC 에러 카운터 증가 + tracepoint + sysfs 업데이트 */
trace_mc_event(type, msg, label, error_count, ...);
edac_inc_ce_error(mci, enable_per_layer_report, pos);
}
edac-util -s (요약), edac-util -r (DIMM별 상세),
ras-mc-ctl --summary (rasdaemon 연동 요약).
EDAC sysfs 인터페이스 상세
EDAC은 /sys/devices/system/edac/ 아래에 계층적 sysfs 인터페이스를 제공합니다.
메모리 컨트롤러(mc), 칩셋 검증(pci), 에러 카운터 등을 확인할 수 있습니다.
# EDAC 메모리 컨트롤러 계층
/sys/devices/system/edac/mc/
├── mc0/ # 메모리 컨트롤러 0
│ ├── ce_count # 총 CE 횟수
│ ├── ue_count # 총 UE 횟수
│ ├── ce_noinfo_count # 위치 정보 없는 CE
│ ├── ue_noinfo_count # 위치 정보 없는 UE
│ ├── seconds_since_reset # 카운터 리셋 후 경과 시간
│ ├── mc_name # 드라이버 이름 (예: "skx_edac")
│ ├── size_mb # 총 메모리 크기
│ ├── dimm0/ # DIMM 0
│ │ ├── dimm_ce_count # 이 DIMM의 CE 횟수
│ │ ├── dimm_dev_type # DDR4, DDR5 등
│ │ ├── dimm_edac_mode # SECDED, Chipkill 등
│ │ ├── dimm_label # DIMM 물리 위치 레이블
│ │ ├── dimm_location # 채널/슬롯/랭크
│ │ └── dimm_mem_type # Registered, Unbuffered
│ └── csrow0/ # 칩-셀렉트 행 0
│ ├── ch0_ce_count # 채널 0의 CE
│ └── ch1_ce_count # 채널 1의 CE
echo "DIMM_A1" > /sys/devices/system/edac/mc/mc0/dimm0/dimm_label로
물리 슬롯 이름을 설정하면 에러 메시지에 해당 레이블이 포함되어 DIMM 식별이 쉬워집니다.
ras-mc-ctl --register-labels를 사용하면 DMI/SMBIOS에서 자동 설정합니다.
EDAC 주소 디코딩 알고리즘
MCE/GHES로부터 전달받은 물리 주소를 실제 DIMM 위치로 변환하는 과정은 플랫폼마다 다릅니다. 주소 인터리빙, 채널 해싱, 랭크 인터리빙 등 메모리 컨트롤러 설정에 따라 동일한 물리 주소라도 다른 DIMM에 매핑될 수 있습니다.
| 디코딩 방식 | 플랫폼 | 디코드 소스 | 정확도 | 특징 |
|---|---|---|---|---|
| Intel ADXL | SKX / ICX / SPR | ACPI DSM 디코드 테이블 | 매우 높음 | 런타임 메모리 재매핑(sparing, mirroring failover) 자동 반영 |
| AMD UMC 레지스터 | Zen ~ Zen4+ | UMC PCI config space | 높음 | Data Fabric interleave 비트 해석, NPS 모드 반영 |
| GHES/CPER | 모든 UEFI 서버 | 펌웨어 사전 디코드 | 높음 (펌웨어 의존) | Physical Address Mask로 에러 범위 표시 가능 |
| 레거시 (sb_edac 등) | Sandy/Ivy Bridge | PCI SAD/TAD 레지스터 | 중간 | 수동 레지스터 파싱, 일부 인터리빙 모드 미지원 |
/* Intel ADXL 기반 디코딩 — drivers/edac/skx_common.c */
static int skx_adxl_decode(struct decoded_addr *res, bool error_in_1st_level_mem)
{
struct skx_dev *d;
int i, len = 0;
/* ADXL 라이브러리에 물리 주소 전달 → 디코드 테이블 참조 */
if (adxl_decode(res->addr, adxl_values)) {
edac_dbg(0, "Failed to decode addr 0x%llx\n", res->addr);
return -EINVAL;
}
/* 디코드 결과에서 소켓/IMC/채널/DIMM/랭크 추출 */
res->socket = (int)adxl_values[adxl_component_indices[INDEX_SOCKET]];
res->imc = (int)adxl_values[adxl_component_indices[INDEX_MEMCTRL]];
res->channel = (int)adxl_values[adxl_component_indices[INDEX_CHANNEL]];
res->dimm = (int)adxl_values[adxl_component_indices[INDEX_DIMM]];
res->rank = (int)adxl_values[adxl_component_indices[INDEX_RANK]];
/* mem_ctl_info에서 해당 컨트롤러/DIMM 구조체 검색 */
if (skx_adxl_get_mci(res))
return -ENODEV;
return 0;
}
/* AMD UMC 기반 디코딩 — drivers/edac/amd64_edac.c */
static int umc_normaddr_to_sysaddr(u64 norm_addr, u16 nid,
u8 umc, u64 *sys_addr)
{
/* UMC 정규화 주소 → 시스템 물리 주소 변환 */
/* Data Fabric interleave 비트, NPS 모드, CS base/mask 참조 */
get_df_interleave(nid, umc, &intlv_num_chan, &intlv_addr_sel);
/* ... */
}
EDAC과 rasdaemon 연동
rasdaemon은 커널 EDAC 서브시스템이 발생시키는 ras:mc_event tracepoint를
구독하여 메모리 에러를 SQLite 데이터베이스에 기록합니다. 이를 통해 장기적인 DIMM 건강 추이 분석과
사전 교체 판단이 가능합니다.
| 데이터 흐름 단계 | 구성 요소 | 설명 |
|---|---|---|
| 1. 에러 감지 | HW → MCE/GHES | ECC 에러 발생 → MCA bank 또는 GHES CPER 기록 |
| 2. EDAC 처리 | edac_mc_handle_error() | 물리 주소 → DIMM 위치 디코딩, ce_count/ue_count 증가 |
| 3. Tracepoint 발행 | trace_mc_event() | ras:mc_event tracepoint로 에러 정보 브로드캐스트 |
| 4. rasdaemon 수집 | rasdaemon → tracefs | /sys/kernel/tracing/events/ras/mc_event 구독 |
| 5. DB 기록 | SQLite | /var/lib/rasdaemon/ras-mc_event.db에 영구 저장 |
# rasdaemon 설치 및 서비스 시작
$ sudo apt install rasdaemon # Debian/Ubuntu
$ sudo dnf install rasdaemon # RHEL/Fedora
$ sudo systemctl enable --now rasdaemon
# rasdaemon이 trace event를 수신하는지 확인
$ sudo cat /sys/kernel/tracing/events/ras/mc_event/enable
1
# SQLite DB 위치 확인
$ ls -la /var/lib/rasdaemon/ras-mc_event.db
DIMM 건강 분석 SQL 쿼리: rasdaemon의 SQLite DB를 직접 쿼리하면 EDAC sysfs보다 훨씬 풍부한 시계열 분석이 가능합니다.
-- 최근 7일간 DIMM별 CE 추이
SELECT
date(timestamp, 'unixepoch') AS day,
mc, top_layer AS channel, mid_layer AS dimm,
COUNT(*) AS ce_count
FROM mc_event
WHERE err_type = 'Corrected'
AND timestamp > strftime('%s', 'now', '-7 days')
GROUP BY day, mc, channel, dimm
ORDER BY day, ce_count DESC;
-- 에러가 가장 많은 상위 5개 DIMM
SELECT
mc, top_layer AS channel, mid_layer AS dimm,
SUM(CASE WHEN err_type = 'Corrected' THEN error_count ELSE 0 END) AS total_ce,
SUM(CASE WHEN err_type = 'Uncorrected' THEN error_count ELSE 0 END) AS total_ue,
MIN(datetime(timestamp, 'unixepoch')) AS first_error,
MAX(datetime(timestamp, 'unixepoch')) AS last_error
FROM mc_event
GROUP BY mc, channel, dimm
ORDER BY total_ce + total_ue * 1000 DESC
LIMIT 5;
-- CE → UE 에스컬레이션 탐지: CE 급증 후 UE 발생한 DIMM
SELECT ue.mc, ue.top_layer AS channel, ue.mid_layer AS dimm,
ce_before.ce_count AS ce_before_ue,
datetime(ue.timestamp, 'unixepoch') AS ue_time
FROM mc_event ue
JOIN (
SELECT mc, top_layer, mid_layer, COUNT(*) AS ce_count,
MAX(timestamp) AS last_ce
FROM mc_event
WHERE err_type = 'Corrected'
GROUP BY mc, top_layer, mid_layer
) ce_before ON ue.mc = ce_before.mc
AND ue.top_layer = ce_before.top_layer
AND ue.mid_layer = ce_before.mid_layer
WHERE ue.err_type = 'Uncorrected'
AND ce_before.ce_count > 10
ORDER BY ce_before.ce_count DESC;
RAS 유저스페이스 도구
MCE 이벤트를 수집, 디코딩, 분석하는 유저스페이스 도구입니다.
mcelog은 레거시이고, 현재는 rasdaemon이 표준입니다.
도구 비교
| 도구 | 상태 | 데이터 소스 | 저장 | 특징 |
|---|---|---|---|---|
| mcelog | 레거시 (deprecated) | /dev/mcelog | 로그 파일 | 독립 데몬, 간단한 디코딩. 최신 커널에서 /dev/mcelog 제거 추세 |
| rasdaemon | 현재 표준 | perf tracepoint | SQLite DB | 다양한 에러 소스 통합(MCE/EDAC/GHES/AER/CXL), 구조화 로깅 |
| ras-mc-ctl | rasdaemon 포함 | rasdaemon DB | - | EDAC 요약, 에러 카운트, DIMM 레이블 관리 |
| edac-util | 활성 | sysfs EDAC | - | EDAC sysfs 인터페이스의 사용자 친화적 래퍼 |
rasdaemon 설정 및 사용
# 설치 (RHEL/CentOS)
$ sudo yum install rasdaemon
# 서비스 시작
$ sudo systemctl enable --now rasdaemon
# 에러 요약 조회
$ sudo ras-mc-ctl --summary
Memory controller events summary:
Corrected on DIMM Label(s): CPU_SrcID#0_MC#0_Chan#0_DIMM#0 location: 0:0:0:-1 errors: 42
No Uncorrected errors.
# SQLite DB 직접 조회
$ sudo sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT timestamp, err_type, mc, top_layer, mid_layer, msg FROM mc_event ORDER BY id DESC LIMIT 10;"
# MCE 이벤트 조회
$ sudo ras-mc-ctl --errors
$ sudo sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT timestamp, mcgstatus, status, addr, bank FROM mce_record ORDER BY id DESC LIMIT 5;"
systemd-journald도 MCE 이벤트를
journalctl -k -g "mce:"로 필터링할 수 있지만,
구조화된 분석에는 rasdaemon의 SQLite DB가 훨씬 효과적입니다.
rasdaemon 아키텍처
rasdaemon은 커널의 perf_event tracepoint를 구독하여 에러 이벤트를 수집합니다.
ras:mc_event, ras:aer_event, ras:mce_record,
ras:cxl_poison 등의 tracepoint를 모니터링하며, 수집된 데이터를 SQLite 데이터베이스에 저장합니다.
# rasdaemon이 구독하는 tracepoint 확인
$ sudo ls /sys/kernel/debug/tracing/events/ras/
aer_event cxl_general_media cxl_poison mc_event
arm_event cxl_memory_module extlog_mem_event mce_record
cxl_dram cxl_overflow non_standard_event
# 실시간 트레이스 확인 (디버그 용)
$ sudo cat /sys/kernel/debug/tracing/events/ras/mc_event/enable
1
$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep mc_event
rasdaemon vs mcelog 마이그레이션
기존 mcelog 사용 환경에서 rasdaemon으로 전환할 때 주의할 점:
/dev/mcelog인터페이스는 커널 6.x에서 제거 추세 — mcelog 사용 불가해질 수 있음- rasdaemon은 모든 RAS 소스(MCE/EDAC/AER/CXL/ARM)를 통합 관리
- SQLite 기반이므로 SQL 쿼리로 유연한 분석 가능
- ABRT(Automatic Bug Reporting Tool)와 연동하여 자동 리포팅 가능
# mcelog → rasdaemon 전환
$ sudo systemctl stop mcelog
$ sudo systemctl disable mcelog
$ sudo systemctl enable --now rasdaemon
# 이전 mcelog 데이터는 수동 마이그레이션 필요
# rasdaemon은 tracepoint 기반이므로 /dev/mcelog 비사용
ras-mc-ctl --summary— 전체 에러 요약ras-mc-ctl --errors— 에러 목록 상세ras-mc-ctl --mainboard— 메인보드 정보ras-mc-ctl --layout— DIMM 레이아웃ras-mc-ctl --status— EDAC 드라이버 상태
CXL 메모리 에러와 MCE
CXL(Compute Express Link) 메모리 장치는 기존 DRAM과는 다른 에러 보고 경로를 사용합니다.
CXL 프로토콜 에러는 PCIe AER 또는 GHES를 통해 보고되며, 궁극적으로 memory_failure()로 이어집니다.
CXL 에러 경로
| 에러 유형 | 보고 경로 | 커널 처리 |
|---|---|---|
| CXL.mem 프로토콜 에러 | GHES → CPER CXL 섹션 | cxl_cper_handle_prot_err() |
| CXL 미디어 에러 (UC) | GHES → CPER Memory → MCE notifier | memory_failure() → HWPoison |
| CXL 미디어 에러 (CE) | CXL 이벤트 로그 / GHES | EDAC/rasdaemon 통계 |
| CXL Poison List | CXL mailbox 명령 | cxl_mem_get_poison() → 선제 오프라인 |
/* drivers/cxl/core/mbox.c — Poison List 조회 */
int cxl_mem_get_poison(struct cxl_memdev *cxlmd,
u64 offset, u64 len,
struct cxl_region *cxlr)
{
struct cxl_mbox_poison_out *po;
int rc;
rc = cxl_internal_send_cmd(mds, &mbox_cmd);
if (rc)
return rc;
/* Poison 리스트 엔트리 순회 */
for (i = 0; i < le16_to_cpu(po->count); i++) {
trace_cxl_poison(cxlmd, cxlr, &po->record[i],
po->flags, po->overflow_ts, CXL_POISON_TRACE_LIST);
}
return 0;
}
GPU/가속기 MCE 연쇄 에러 분석
현대 데이터센터와 AI/ML 학습 클러스터에서 GPU 및 하드웨어 가속기에서 발생하는 하드웨어 에러(ECC 오류, DMA 장애 등)는 PCIe AER을 거쳐 CPU의 IIO MCA 뱅크에 기록되고, 최종적으로 MCE로 전파됩니다. 이 연쇄 과정을 정확히 이해해야 장애 원인을 신속하게 격리할 수 있습니다.
GPU 에러 연쇄 전파 경로
Xid 에러 코드와 MCE 상관관계
| Xid 코드 | 설명 | MCE 전파 여부 | IIO MSCOD | 증상 | 조치 |
|---|---|---|---|---|---|
Xid 48 | DBE (Double Bit Error) | 높음 (UE) | 0x0136 | 학습 NaN, GPU 행(hang) | GPU 교체, nvidia-smi -r |
Xid 63 | Row remapping 실패 | 중간 | 0x0136 | ECC 에러 급증 후 remapping 실패 | GPU 교체 예약 |
Xid 79 | GPU 복구 실패 | 높음 | 0x0402 | GPU 응답 없음 | 노드 재시작 필요 |
Xid 94 | Contained ECC 에러 | 낮음 (CE) | - | 정상 동작, CE 카운터 증가 | 카운터 모니터링 |
Xid 95 | Uncontained ECC 에러 | 높음 (UE) | 0x0136 | 프로세스 강제 종료, 패닉 가능 | 즉시 GPU 교체 |
GPU Xid와 MCE 뱅크/IIO 스택 상관분석
# GPU의 PCIe 위치 확인
$ lspci -d 10de: -vvs 41:00.0
41:00.0 3D controller: NVIDIA Corporation A100
UESta: 0x00004000 # ← Completion Abort 비트
# MCE 로그에서 Bank 번호 → IIO 스택 역추적
# Bank 6 = IIO Stack 1 (Skylake-SP 기준)
mce: [Hardware Error]: CPU 24 Bank 6:
STATUS 0xbc20000001000136
# MSCOD=0x0136: Received Parity Error
# UC=1, PCC=1 → 프로세서 컨텍스트 손상
# GPU ECC 상태 확인
$ nvidia-smi --query-gpu=ecc.errors.corrected.volatile.total,\
ecc.errors.uncorrected.volatile.total,retired_pages.count --format=csv
GPU 관련 MCE 패턴 종합표
| 패턴 | MSCOD | 증상 | 근본 원인 | 조치 |
|---|---|---|---|---|
| GPU ECC UE | 0x0136 | 학습 NaN, Xid 48/95 | HBM/GDDR 결함 | GPU 교체 |
| Completion Timeout | 0x0402 | GPU 행, 디바이스 소실 | GPU 펌웨어/PCIe 링크 | PCIe 리트레이닝, FLR |
| Poisoned TLP | 0x0136 | DMA 읽기 실패 | 호스트 메모리 UCE | DIMM 교체 |
| VT-d Translation Fault | 0x0010 | IOMMU 폴트 | 드라이버/IOMMU 설정 오류 | iommu=pt, 드라이버 업데이트 |
| NVLink Degradation | 0x0136 | Xid 74, 성능 저하 | NVLink 케이블/NVSwitch 결함 | 케이블 교체 |
AI/ML 학습 클러스터 MCE 영향
/* 분산 학습 중 단일 GPU ECC UE 발생 시 타임라인 */
T+0ms GPU 3의 HBM에서 DBE(Double Bit Error) 발생
T+0.1ms NVIDIA 드라이버 Xid 48 기록
T+0.5ms PCIe AER Uncorrectable Error 전파
T+1ms IIO MCA Bank 7에 기록 → MCE 핸들러 호출
T+2ms memory_failure() 호출 — 오염 페이지 오프라인
T+100ms GPU CUDA 컨텍스트 무효화
T+200ms PyTorch/NCCL: Rank 3 통신 실패 감지
T+30s NCCL timeout → 전체 AllReduce 데드락
T+60s 모든 Rank 학습 중단 → 마지막 체크포인트로 롤백 필요
// 영향: 8-GPU 노드 1대의 GPU 1개 장애
// → 전체 클러스터 학습 중단
클러스터 운영 권장사항
- 사전 모니터링:
rasdaemon과nvidia-smi연동, CE 급증 시 자동 노드 드레인 - 체크포인트(Checkpoint) 전략: CE 급증 시 체크포인트 주기 단축
- tolerant 설정:
tolerant=1로 복구 가능 MCE 패닉 방지 - GPU 헬스체크:
dcgmi diag -r 3(NVIDIA DCGM)으로 주기적 GPU 진단 - NUMA/IIO 인지 배치: GPU와 동일 소켓/IIO 스택의 DIMM CE 감시 → NUMA 토폴로지 기반 장애 격리
아키텍처별 하드웨어 에러 처리 비교
MCE는 x86 고유 개념이지만, ARM, RISC-V, POWER 등 모든 서버급 아키텍처에 유사한 하드웨어 에러 보고 메커니즘이 있습니다. 각 아키텍처의 에러 감지/보고/복구 설계를 비교하면 하드웨어 RAS(Reliability, Availability, Serviceability)의 본질적인 문제와 해결 패턴을 더 깊이 이해할 수 있습니다.
ARM RAS Extension 상세
| 메커니즘 | 유형 | 특성 |
|---|---|---|
| SEA (Synchronous External Abort) | 동기 | x86 SRAR과 유사. 데이터 소비 중 UC 에러. memory_failure(MF_ACTION_REQUIRED) |
| SEI/SError | 비동기 | x86 SRAO/UCNA와 유사. 비동기적 외부 에러. 복구 가능성은 ESR_ELx로 판단 |
| SDEI (Software Delegated Exception Interface) | 펌웨어 위임 | Firmware-First 모델. EL3/Secure World에서 에러 수집 → GHES로 전달 |
| ERRn 레지스터 | RAS Extension v1 | ERR<n>STATUS, ERR<n>ADDR, ERR<n>MISC0/1 — x86 MCA 뱅크와 구조적으로 유사 |
/* arch/arm64/mm/fault.c — SEA 처리 */
static int do_sea(unsigned long far, unsigned long esr,
struct pt_regs *regs)
{
/* GHES SEA handler에 먼저 전달 */
if (ghes_notify_sea() >= 0)
return 0; /* GHES가 처리 완료 */
/* GHES가 처리하지 못하면 프로세스 킬 */
arm64_notify_die("Synchronous External Abort", regs, esr);
return 0;
}
ARM RAS Extension v1 레지스터
ARM RAS Extension v1은 x86 MCA 뱅크와 유사한 에러 레코드 레지스터를 정의합니다. 각 에러 노드(Error Record)는 ERRn 접두사를 가진 시스템 레지스터 세트로 구성됩니다.
| 레지스터 | 역할 | x86 MCA 대응 |
|---|---|---|
| ERRIDR_EL1 | 에러 레코드 수 및 기능 확인 | MCG_CAP |
| ERRnSTATUS | 에러 상태 (V/UE/CE/OF 비트) | MCi_STATUS |
| ERRnADDR | 에러 발생 물리 주소 | MCi_ADDR |
| ERRnMISC0/1 | 추가 에러 정보 | MCi_MISC |
| ERRnCTLR | 에러 보고 활성화 제어 | MCi_CTL |
| ERRnFR | Feature Register — 지원 기능 | 해당 없음 |
RISC-V 하드웨어 에러 표준화 현황
RISC-V는 아직 하드웨어 에러 보고에 대한 공식 표준이 확립되지 않았습니다. 현재 진행 중인 표준화 작업과 벤더 독자 구현이 혼재합니다:
- SBI MPXY (Message Proxy Extension) — 펌웨어-OS 간 에러 메시지 전달
- SiFive S76 — BEU(Bus Error Unit) + 커스텀 인터럽트
- T-Head C910 — 플랫폼 고유 CSR 기반 에러 보고
- UEFI 서버 — ACPI GHES 경유 (x86과 동일한 최종 경로)
IBM POWER EEH (Enhanced Error Handling) 상세
IBM POWER 아키텍처는 PCI I/O 에러 처리를 위해 EEH(Enhanced Error Handling)를 제공합니다. x86의 AER과 유사한 역할이지만, 하이퍼바이저 계층을 통한 격리와 자동화된 단계적 복구에 초점을 맞춥니다.
EEH 동작 메커니즘
- 에러 감지: PHB(PCI Host Bridge)가 PCI/PCIe 버스 에러를 감지
- PE 격리: 에러 발생 PE(Partitionable Endpoint)를 즉시 I/O 격리(frozen) 상태로 전환. MMIO 읽기는 모두
0xFF반환 - OPAL 통지: 하이퍼바이저가 OS에 EEH 이벤트 전달
- 단계적 복구: I/O 재시도 → 슬롯 리셋 → PHB 리셋 → 실패 시 PE 영구 비활성화 (최대 5회)
/* arch/powerpc/kernel/eeh_driver.c — EEH 복구 콜백 */
static int eeh_report_error(struct eeh_dev *edev, struct eeh_pe *pe)
{
struct pci_driver *drv = eeh_pcid_get(edev->pdev);
if (!drv || !drv->err_handler || !drv->err_handler->error_detected)
return PCI_ERS_RESULT_NONE;
/* 드라이버에 에러 통지 — 복구 가능 여부 응답 */
return drv->err_handler->error_detected(edev->pdev, edev->state);
}
POWER vs x86 vs ARM 에러 처리 비교
| 비교 항목 | IBM POWER | x86 (Intel/AMD) | ARM (Neoverse) |
|---|---|---|---|
| 에러 예외 | Machine Check (벡터 0x200) | #MC (벡터 18) | SEA / SError |
| 에러 레지스터 | SRR1, DSISR, OPAL 에러 로그 | MCA 뱅크 (MCi_STATUS/ADDR/MISC) | ERRn 레지스터 (RAS Extension) |
| PCIe I/O 에러 | EEH (PHB 기반 PE 격리) | AER + IIO MCA | SMMU + AER + GHES |
| 에러 격리 단위 | PE (Partitionable Endpoint) | PCIe 장치/기능 단위 | SMMU Stream ID 기반 |
| 복구 메커니즘 | EEH 단계적 복구 (최대 5회) | AER 드라이버 콜백(Callback) + memory_failure() | GHES → memory_failure() |
| 펌웨어 역할 | OPAL/skiboot (필수, 에러 수집/필터링) | APEI/GHES (선택적) | SDEI → GHES (주요 경로) |
| Poison/SUE | SUE (Special Uncorrectable Error) | Viral Mode / Data Poisoning | Poison 비트 (CMN-700) |
| CPU 동기화 | Hypervisor 위임 (FWNMI) | Monarch 랑데부 | 코어별 독립 처리 |
| 메모리 에러 복구 | memory_failure() + HWPoison (OPAL 경유) | memory_failure() + HWPoison | memory_failure() + HWPoison (GHES 경유) |
| 가상화 환경 | FWNMI — 하이퍼바이저가 게스트에 에러 주입 | LMCE + vMCE (KVM) | SDEI → 게스트 GHES |
ARM RAS Extension v1/v1.1
ARMv8.2-A에서 도입된 FEAT_RAS(RAS Extension v1)는 하드웨어 에러 감지와 보고를 표준화한 것으로, x86 MCA에 대응하는 ARM의 공식 RAS 프레임워크입니다. ARMv8.4-A에서 FEAT_RASv1p1(v1.1)으로 강화되었습니다.
ARM FEAT_RAS vs FEAT_RASv1p1 차이
| 기능 | FEAT_RAS (v1, ARMv8.2) | FEAT_RASv1p1 (v1.1, ARMv8.4) |
|---|---|---|
| 에러 레코드 | ERRnSTATUS/ADDR/MISC0/1/CTLR/FR | 동일 + ERRnPFGF, ERRnPFGCTL (Fault Injection) |
| DoubleFault | 미지원 | FEAT_DoubleFault — 에러 핸들링 중 2번째 에러 시 EL3로 라우팅 |
| 에러 분류 세분화 | CE/UC 2단계 | CE/DE(Deferred)/UC/UEO(Uncorrected Error Overwritten) 4단계 |
| Deferred Error | 기본 지원 | DE(Deferred Error) 전용 상태 비트 표준화, AMD SMCA Deferred와 유사 |
| SError 분류 | AET 필드 (2비트) | IESB(Implicit Error Synchronization Barrier) 추가 — 에러 동기화 강화 |
| Fault Injection | 미지원 | ERRnPFG* 레지스터로 소프트웨어 에러 주입 가능 (x86 EINJ 대응) |
| Timestamp | 미지원 | ERRnTS 레지스터 — 에러 타임스탬프 기록 (Generic Timer 기반) |
ARM Neoverse 플랫폼별 RAS 구현
ARM Neoverse는 서버/인프라용 CPU IP로, 세대별로 RAS 기능이 다릅니다. AWS Graviton, Ampere Altra, Microsoft Cobalt 등 클라우드 서버에 사용됩니다.
| 코어 IP | 사용 제품 | RAS 버전 | 에러 노드 수 | 주요 RAS 기능 |
|---|---|---|---|---|
| Neoverse N1 | Graviton2, Altra | FEAT_RAS v1 | 코어당 ~10개 | L1/L2 ECC, 캐시 라인(Cache Line) 무효화 복구, SEA/SError, GHES 연동 |
| Neoverse V1 | Graviton3 | FEAT_RAS v1.1 | 코어당 ~14개 | V1 추가: DoubleFault, Deferred Error 표준화, 256-bit SVE 에러 감지 |
| Neoverse N2 | Graviton4, Cobalt 100 | FEAT_RAS v1.1 | 코어당 ~16개 | N2 추가: MPAM RAS 통합, CMN-700 인터커넥트 에러 강화 |
| Neoverse V2 | Graviton4(고성능 변종) | FEAT_RAS v1.1 | 코어당 ~18개 | V2 추가: 128-bit SME 에러 감지, 향상된 에러 신드롬 |
| Neoverse V3/N3 | 차세대 | FEAT_RAS v2 (예정) | 확장 | FEAT_RASv2: 에러 카운터 표준화, 더 정교한 에러 분류 |
ARM CMN (Coherent Mesh Network) 에러
ARM 서버 SoC의 인터커넥트인 CMN-600/CMN-700은 x86의 Mesh/CHA에 대응합니다. CMN 에러는 별도의 에러 노드를 통해 보고되며, GHES 경로로 OS에 전달됩니다.
| CMN 에러 유형 | x86 대응 | 설명 |
|---|---|---|
| SN(Slave Node) ECC Error | CHA LLC ECC | System Level Cache(SLC) ECC 에러 |
| HN-F(Home Node Filter) Error | CHA Snoop Filter | 코히런스 디렉토리 에러 |
| XP(Cross Point) Error | Mesh Data Fabric | 메시 라우팅 노드 에러 |
| RN-D(Request Node DMA) Error | IIO DMA | DMA 에이전트 에러 |
| SBSX(SB SLC Exclusive) Error | M2M | 메모리 인터페이스 노드 에러 |
Intel vs AMD MCA 아키텍처 차이 종합
x86 내에서도 Intel과 AMD는 MCA 구현에 상당한 차이가 있습니다. 같은 "MCA"라도 뱅크 식별 방식, 에러 분류, 복구 메커니즘, 레지스터 확장이 다릅니다.
| 비교 항목 | Intel (Xeon Scalable) | AMD (EPYC, Zen) |
|---|---|---|
| 뱅크 식별 | 고정 번호 (Bank 0=IFU, Bank 4=PCU 등). CPU 세대마다 번호-유닛 매핑이 변경됨. SDM/에러코드 레퍼런스 참조 필요 | MCi_IPID 레지스터의 HWID+MCATYPE으로 런타임 식별. Zen 1~5 모두 동일 디코딩 코드 사용 가능 |
| 레지스터 수 | 뱅크당 5개 (CTL, STATUS, ADDR, MISC, CTL2) | 뱅크당 최대 10개 (+IPID, SYND, DESTAT, DEADDR, CONFIG) |
| 에러 분류 | SRAR/SRAO/UCNA (STATUS 비트 조합으로 분류). MCG_CAP.MCG_SER_P=1이면 복구 가능 | Deferred Error가 별도 유형으로 존재. SRAR/SRAO 외에 Deferred(잠재적 에러) 독립 처리 |
| CE 보고 | CMCI (MCi_CTL2.CMCI_EN, 임계값 기반). CMCI 스톰 시 자동 폴링 전환 | Threshold Interrupt (APIC LVT). 뱅크별 CE 임계값 설정. CMCI 호환 모드도 지원 |
| 브로드캐스트 제어 | LMCE (Local MCE) — 특정 에러를 해당 CPU에만 전달. 다른 CPU의 VMEXIT 방지 | LMCE 미지원. 모든 UC MCE는 브로드캐스트. (Zen 5+에서 지원 가능성) |
| ECC 신드롬 | MCi_MISC에 제한적 신드롬 정보 | MCi_SYND 전용 레지스터에 상세 신드롬 (에러 비트 위치, ECC 패턴) |
| Deferred Error | 없음 (patrol scrub UC → 즉시 MCE 또는 SRAO) | DESTAT/DEADDR로 잠재 에러 기록 → 소프트웨어가 선제 오프라인 가능 |
| I/O 에러 | IIO 뱅크 (MSCOD로 PCIe 에러 세분화). AER과 이중 보고 | NBIO/PCIE 뱅크 (SMCA IPID로 식별). PCIe AER은 별도 |
| 인터커넥트 | UPI (Bank 5~6). 소켓 간 CRC/Protocol Error | xGMI PCS/PHY, GMI PCS/PHY, WAFL. 다이 간/소켓 간 링크 별도 뱅크 |
| 메모리 컨트롤러 | iMC(Bank 13~20) + M2M(Bank 9~12). ADDDC, Mirroring, Sparing | UMC (SMCA_UMC). On-die ECC(DDR5), Post Package Repair(PPR) |
| 보안 프로세서 | 해당 없음 (별도 MCA 뱅크 없음) | PSP(Platform Security Processor) 뱅크 — PSP 펌웨어 에러 보고 |
| Poison 전파 | Viral Mode (BIOS 설정) — Containment/Propagation 선택 | Data Poisoning 기본 지원 — Deferred Error로 선제 대응 가능 |
| 커널 소스 | arch/x86/kernel/cpu/mce/intel.c, severity.c |
arch/x86/kernel/cpu/mce/amd.c, smca.c |
- 에러 로그 분석: Intel은
CPU N Bank M MSCOD=...를 모델별 매뉴얼로 해석. AMD는Scalable MCA: IPID=... HWID=0xB0 → Load Store로 자동 식별 - 메모리 에러 대응: Intel은 ADDDC+Patrol Scrub UC → MCE → memory_failure(). AMD는 Patrol Scrub → Deferred Error → 소프트웨어 선제 오프라인 (UC MCE 없이 페이지 격리 가능)
- PCIe 에러: Intel IIO MSCOD로 CTO/Poison/LinkDown 세분화. AMD NBIO/PCIE SMCA는 IPID로 식별하지만 세부 에러 코드는 덜 세분화
- 디버깅 도구: 둘 다
rasdaemon이 자동 디코딩. Intel은mcelog도 사용 가능. AMD는rasdaemon이 SMCA를 더 잘 지원
크로스 아키텍처 에러 처리 종합 비교
x86(Intel/AMD), ARM, RISC-V, POWER를 포괄하는 종합 비교표입니다. 서버 플랫폼을 설계하거나 다중 아키텍처 환경을 운영할 때 참고합니다.
| 비교 항목 | x86 Intel | x86 AMD | ARM (Neoverse) | RISC-V | POWER (IBM) |
|---|---|---|---|---|---|
| 에러 예외 | #MC (벡터 18) | #MC (벡터 18) | SEA / SError | 플랫폼 종속 | Machine Check (0x200) |
| 에러 레지스터 | MCA 뱅크 (MSR) | SMCA 뱅크 (MSR + IPID) | ERRn 레지스터 | CSR (벤더 독자) | SRR1 + DSISR |
| 뱅크/노드 수 | ~48~100개 | ~32~64개 | 코어당 ~10~18개 | 미표준화 | ~20~40개 |
| CE 인터럽트 | CMCI | Threshold Int. | Fault IRQ / GHES | 플랫폼 종속 | CE 핸들러 |
| Firmware-First | APEI/GHES (선택) | APEI/GHES (선택) | SDEI → GHES (주요 경로) | SBI + GHES (개발 중) | OPAL/skiboot (필수) |
| 에러 동기화 | Monarch 랑데부 | Monarch 랑데부 | 코어별 독립 처리 | 미정의 | Hypervisor 위임 |
| 메모리 복구 | memory_failure() + HWPoison | memory_failure() + HWPoison | memory_failure() + HWPoison | memory_failure() (GHES 경유 시) | memory_failure() + HWPoison |
| Deferred Error | 없음 | DESTAT/DEADDR | FEAT_RASv1p1 DE | 없음 | 없음 |
| 에러 주입 | EINJ (ACPI) | EINJ (ACPI) | ERRnPFG (v1.1) | 벤더 종속 | OPAL inject |
| Poison 전파 | Viral Mode | Data Poisoning | Poison 비트 (CMN) | 미정의 | SUE (Special Uncorrectable Error) |
| PCIe 에러 | IIO MCA + AER | NBIO/PCIE SMCA + AER | SMMU + AER + GHES | AER + 플랫폼 | EEH (Enhanced Error Handling) |
| 메모리 ECC | SDDC/ADDDC/Mirroring | On-die ECC + PPR | 플랫폼 종속 | 플랫폼 종속 | Chipkill + 동적 Sparing |
| EDAC 드라이버 | i10nm_edac, skx_edac | amd64_edac | 플랫폼별 (thunderx2, xgene 등) | 없음/개발 중 | pasemi_edac 등 |
| 유저스페이스 도구 | rasdaemon, mcelog | rasdaemon | rasdaemon (GHES 경유) | 없음/개발 중 | opal-prd |
| 성숙도 | 매우 높음 (20년+) | 높음 (Zen 이후 ~8년) | 중간 (서버 진출 ~6년) | 낮음 (초기) | 매우 높음 (30년+) |
IBM POWER의 EEH (Enhanced Error Handling)
IBM POWER 아키텍처의 EEH는 PCIe 에러 처리에서 x86 AER/IIO MCA보다 진보된 모델입니다. x86에서는 PCIe UC 에러 시 장치가 사실상 죽지만, POWER EEH는 자동 복구 단계를 제공합니다.
| EEH 복구 단계 | 동작 | x86 대응 |
|---|---|---|
| 1. I/O Freeze | 에러 장치의 MMIO/DMA를 냉동 (데이터 오염 차단) | AER pci_channel_io_frozen 유사 |
| 2. MMIO Detect | 냉동된 장치의 MMIO 읽기가 all-1s(0xFFFFFFFF) 반환 → 드라이버가 감지 | x86에서도 동일 패턴이지만 표준화되지 않음 |
| 3. Slot Reset | PHB(PCI Host Bridge) 레벨 리셋 → 장치 재초기화 | AER pcie_do_recovery() 유사하지만 더 강력 |
| 4. Resume | 드라이버의 resume 콜백(Callback)으로 정상 동작 복귀 | AER mmio_enabled/resume 콜백 |
| 5. Full Reset | 위 단계 실패 시 PE(Partitionable Endpoint) 전체 리셋 | x86에는 대응 없음 (보통 재부팅) |
- 메모리 RAS 최우선: Intel ADDDC + Mirroring 또는 IBM POWER Chipkill + 동적 Sparing
- PCIe 복구 최우선: IBM POWER EEH가 가장 성숙. x86은 AER+IIO MCA 조합으로 대응
- Deferred Error (선제 대응): AMD SMCA 또는 ARM RAS v1.1이 Deferred Error를 네이티브 지원
- 다중 아키텍처 운영: GHES/CPER 기반 통합 모니터링이 핵심. rasdaemon이 x86/ARM 모두 지원하므로 통합 대시보드 구축 가능
- ARM 서버 주의: ARM은 Firmware-First(SDEI→GHES)가 주요 경로이므로, 펌웨어(TF-A/UEFI) 품질이 RAS 성능에 직접 영향. Intel/AMD의 OS-First 모델보다 펌웨어 의존도 높음
RISC-V 하드웨어 에러 표준화 현황
RISC-V는 아직 하드웨어 에러 보고에 대한 공식 표준이 확립되지 않았습니다. 현재 진행 중인 표준화 작업과 벤더 독자 구현이 혼재합니다:
- SBI MPXY (Message Proxy Extension) — 펌웨어-OS 간 에러 메시지 전달
- SiFive S76 — BEU(Bus Error Unit) + 커스텀 인터럽트
- T-Head C910 — 플랫폼 고유 CSR 기반 에러 보고
- UEFI 서버 — ACPI GHES 경유 (x86과 동일한 최종 경로)
- Sscofpmf — 성능 카운터 오버플로 확장 (에러 카운팅에 활용 가능)
- Smcdeleg — 카운터 위임 확장 (에러 모니터링 위임)
MCE 테스트 주입
MCE 처리 경로의 정확성을 검증하려면 하드웨어 에러를 시뮬레이션해야 합니다. 리눅스 커널은 여러 단계의 주입 메커니즘을 제공합니다.
주입 방법 비교
| 방법 | 수준 | 요구사항 | 특징 |
|---|---|---|---|
| mce-inject | 소프트웨어 MCE | CONFIG_X86_MCE_INJECT=y | 커널 내부에서 가짜 MCE 생성. MCA 뱅크를 실제로 쓰지 않음 |
| EINJ | 펌웨어 레벨 | CONFIG_ACPI_APEI_EINJ=y, BIOS 지원 | ACPI EINJ 테이블로 실제 메모리 에러 주입. 가장 현실적 |
| hwpoison-inject | 페이지 레벨 | CONFIG_HWPOISON_INJECT=y | debugfs에서 직접 PFN 지정하여 HWPoison 마킹 |
mce-inject 사용법
# mce-inject 모듈 로드
$ sudo modprobe mce-inject
# 주입 파일 작성 (SRAO 메모리 에러)
$ cat > /tmp/mce-test.txt <<EOF
CPU 0 BANK 9
STATUS 0xBD2000000000017A
ADDR 0x12345678
MISC 0x0000000000000086
MCGSTATUS 0x0
EOF
# 주입 실행
$ sudo sh -c "cat /tmp/mce-test.txt > /sys/kernel/debug/mce-inject/mce-inject"
# dmesg 확인
$ dmesg | tail -20
EINJ (Error Injection) 사용법
# EINJ 모듈 로드
$ sudo modprobe einj
# 지원되는 에러 유형 확인
$ cat /sys/kernel/debug/apei/einj/available_error_type
0x00000002 Memory Correctable
0x00000008 Memory Uncorrectable non-fatal
0x00000010 Memory Uncorrectable fatal
# CE 주입 (특정 물리 주소)
$ sudo sh -c "echo 0x2 > /sys/kernel/debug/apei/einj/error_type"
$ sudo sh -c "echo 0x100000000 > /sys/kernel/debug/apei/einj/param1" # 물리 주소
$ sudo sh -c "echo 0xFFFFFFFFFFFFF000 > /sys/kernel/debug/apei/einj/param2" # 마스크
$ sudo sh -c "echo 1 > /sys/kernel/debug/apei/einj/error_inject"
hwpoison-inject 사용법
# 특정 PFN에 HWPoison 주입
$ sudo sh -c "echo 0x12345 > /sys/kernel/debug/hwpoison/corrupt-pfn"
# soft offline (CE 예방)
$ sudo sh -c "echo 0x12345 > /sys/kernel/debug/hwpoison/soft-offline-pfn"
# HWPoison 해제
$ sudo sh -c "echo 0x12345 > /sys/kernel/debug/hwpoison/unpoison-pfn"
tolerant=3 설정으로 패닉을 방지하거나
VM 스냅샷을 준비하세요.
MCE 테스트 시나리오 매트릭스
| 시나리오 | 주입 방법 | 예상 결과 | 검증 사항 |
|---|---|---|---|
| CE 단일 발생 | mce-inject (UC=0) | dmesg CE 로그, EDAC 카운트 증가 | rasdaemon DB에 기록 확인 |
| CE 폭주 (CMCI 스톰) | mce-inject 반복 | CMCI → 폴링 전환 | cmci_storm 감지 로그 확인 |
| SRAR (데이터 소비) | EINJ + 메모리 접근 | SIGBUS, 프로세스 종료 | HWPoison 페이지 확인 |
| SRAO (백그라운드) | mce-inject (UC=1,S=1,AR=0) | 페이지 오프라인 | buddy allocator 제외 확인 |
| UC+PCC (패닉) | mce-inject (UC=1,PCC=1) | 시스템 패닉 | BERT 또는 crash dump |
| HWPoison 복구 | hwpoison-inject | 페이지 오프라인, 매핑 제거 | /proc/kpageflags 확인 |
| soft offline | hwpoison soft-offline | 마이그레이션 후 페이지 격리 | vmstat migration 카운트 |
가상 머신에서의 MCE 테스트
QEMU/KVM에서 MCE를 주입하여 게스트 커널의 MCE 핸들링을 테스트할 수 있습니다.
# QEMU 모니터에서 게스트에 MCE 주입
(qemu) mce 0 9 0xBD2000000000017A 0x12345678 0x86
# 파라미터: CPU BANK STATUS ADDR MISC
# CPU 0, Bank 9, STATUS = SRAO 메모리 에러
# KVM에서 ioctl로 프로그래밍 주입
# KVM_X86_SET_MCE ioctl → struct kvm_x86_mce
MCE 디버깅 기법 (perf/ftrace/eBPF)
MCE 이벤트는 하드웨어 오류의 핵심 진단 정보를 담고 있지만, 발생 빈도가 낮고 재현이 어려워 체계적인 디버깅 도구 활용이 필수적입니다. 리눅스 커널은 perf, ftrace, eBPF 등 다양한 트레이싱 프레임워크를 통해 MCE 이벤트를 실시간 모니터링하고 분석할 수 있는 인프라를 제공합니다.
perf를 이용한 MCE 이벤트 추적
# MCE 관련 트레이스포인트 확인
$ perf list 'ras:*'
# MCE 이벤트 실시간 기록
$ perf record -e ras:mce_record -a --overwrite -o mce_trace.data
# 기록된 MCE 이벤트 상세 출력
$ perf script -i mce_trace.data
# EDAC/AER 이벤트도 함께 추적
$ perf record -e ras:mce_record -e ras:mc_event -e ras:aer_event -a -o ras_all.data
ftrace를 이용한 MCE 핸들러 추적
# function_graph 트레이서로 MCE 핸들러 추적
$ cd /sys/kernel/debug/tracing
$ echo 0 > tracing_on
$ echo function_graph > current_tracer
$ echo do_machine_check > set_graph_function
$ echo 1 > tracing_on
# MCE 관련 함수만 필터링
$ echo do_machine_check > set_ftrace_filter
$ echo mce_severity >> set_ftrace_filter
$ echo mce_reign >> set_ftrace_filter
# 트레이스포인트 직접 활성화
$ echo 1 > events/ras/mce_record/enable
eBPF/bpftrace를 이용한 MCE 모니터링
# MCE 이벤트 발생 시 뱅크 번호와 상태 출력
$ bpftrace -e '
tracepoint:ras:mce_record {
printf("MCE: cpu=%d bank=%d status=0x%llx addr=0x%llx\n",
args->cpu, args->bank, args->status, args->addr);
}'
# MCE 뱅크별 발생 횟수 집계
$ bpftrace -e '
tracepoint:ras:mce_record {
@mce_by_bank[args->bank] = count();
@mce_by_cpu[args->cpu] = count();
}'
# 물리 주소 기반 반복 오류 감지
$ bpftrace -e '
tracepoint:ras:mce_record {
@addr_count[args->addr] = count();
if (@addr_count[args->addr] >= 3) {
printf("WARNING: 반복 MCE at addr=0x%llx (count=%d)\n",
args->addr, @addr_count[args->addr]);
}
}'
# do_machine_check() 실행 시간 측정
$ bpftrace -e '
kprobe:do_machine_check { @start[tid] = nsecs; }
kretprobe:do_machine_check /@start[tid]/ {
@mce_latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
크래시 덤프(Dump) 분석 (kdump + crash)
치명적 MCE로 인한 커널 패닉 이후에는 kdump가 캡처한 메모리 덤프를 crash 유틸리티로 분석합니다.
# crash 유틸리티로 덤프 분석
$ crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/<timestamp>/vmcore
# crash 프롬프트에서 MCE 정보 조회
crash> log | grep -i "mce\|machine check"
crash> bt -a # 모든 CPU 백트레이스
crash> struct mce mcelog.entry[0]
crash> px mcelog.entry[0].status
crash> px mcelog.entry[0].addr
MCE 이벤트 상관 분석
# 타임스탬프 기반 통합 분석
$ journalctl --since "2 hours ago" -k | \
grep -iE "mce|edac|aer|ghes|hardware.error" | \
sort -k1,3 | tee /tmp/hw_error_timeline.log
# rasdaemon DB에서 시간 범위 기반 교차 조회
$ sqlite3 /var/lib/rasdaemon/ras-mc_event.db "
SELECT 'MCE' as type, timestamp, error_msg FROM mce_record
WHERE timestamp > datetime('now', '-2 hours')
UNION ALL
SELECT 'MC' as type, timestamp, error_msg FROM mc_event
WHERE timestamp > datetime('now', '-2 hours')
UNION ALL
SELECT 'AER' as type, timestamp, error_msg FROM aer_event
WHERE timestamp > datetime('now', '-2 hours')
ORDER BY timestamp;"
ADDR 필드 물리 주소를 EDAC의 DIMM 위치(채널/슬롯/랭크)와 매핑하면, 특정 DIMM의 물리적 교체 여부를 판단할 수 있습니다. page-types -b hwpoison으로 오프라인 페이지도 확인하세요.
최신 MCE 인프라 변화 (v6.7~v6.14)
MCE 서브시스템은 하드웨어 벤더의 변화(Intel EMCA2, AMD Zen5, CXL 메모리 계층)에 발맞춰 꾸준히 확장되고 있습니다. 운영 관점에서 주요 신기능 중심으로 정리합니다.
Intel EMCA2 (Extended MCA 2, v6.7+)
Intel의 EMCA2는 Skylake-SP에서 도입된 EMCA의 확장판으로, Sapphire Rapids 이후 프로세서에서 지원됩니다. 핵심은 "더 많은 에러 클래스에 대한 firmware-first 처리"와 "에러 컨텍스트의 확장된 필드"입니다.
- Firmware-first 경로: 기존에는 치명적 에러만 BIOS/펌웨어가 먼저 수집하고 SCI를 통해 OS에 전달했지만, EMCA2는 대부분의 corrected error까지 펌웨어가 수집합니다. OS는
apei_mce_report_mem_error()로 보고를 받고 그 안에 포함된 확장 필드(뱅크 타입, 에러 severity, 벤더 종속 컨텍스트)를 참조합니다. - Enhanced Error Source Table: ACPI HEST의 EMCA2 엔트리(
GHES v3)가 확장되어 에러 소스별 상세 메타데이터가 노출됩니다. 커널은drivers/acpi/apei/ghes.c에서 이를 해석해 RAS 이벤트로 변환. - EDAC 자동 매핑: v6.10에서
i10nm_edac이 EMCA2 경로의 주소를 DIMM 채널/슬롯에 직접 매핑하도록 확장되어, 사용자 공간rasdaemon의 리포팅이 별도 설정 없이 상세해졌습니다.
/* arch/x86/kernel/cpu/mce/apei.c (v6.7+) - EMCA2 경로 */
void apei_mce_report_mem_error(int severity, struct cper_sec_mem_err *mem_err)
{
struct mce m;
mce_setup(&m);
m.bank = -1; /* firmware-first: 뱅크 미상 */
m.status = MCI_STATUS_VAL | MCI_STATUS_UC | (severity << 56);
m.addr = mem_err->physical_addr;
/* EMCA2 확장 필드 전달 */
if (mem_err->validation_bits & CPER_MEM_VALID_NODE)
m.node = mem_err->node;
mce_log(&m);
}
AMD Zen5 / Turin 대응 (v6.11~v6.13)
AMD 5세대 Zen(코드네임 Turin, EPYC 9005 시리즈)는 SMCA에 새로운 뱅크 타입을 추가하고, AI 가속기 오류 처리 경로를 정비했습니다. 리눅스 대응 현황:
- v6.11: Zen5 CPU 식별 및 기본 MCA 뱅크 인식(CPUID 기반).
arch/x86/kernel/cpu/mce/amd.c의smca_bank_names[]가 확장됨. - v6.12: Zen5의 새로운 "Memory Controller v2" 뱅크 타입 추가. 기존 UMC(Unified Memory Controller)와 분리된 DDR5 전용 에러 경로.
- v6.13: AI accelerator(XDNA2 NPU)의 on-die 에러가 SMCA 뱅크로 승격되어 OS에 보고되도록 통합. 해당 뱅크는 주로 CE(Correctable Error)로 동작하지만, AI 모델 실행 중 데이터 corruption을 조기 감지.
- CXL 1.1 컨트롤러: Zen5의 내장 CXL 컨트롤러 오류가
SMCA_CS(Core & System) 뱅크에 매핑됨. CXL 디바이스 자체 오류(cxl_mem)는 별도 RAS 경로로 처리.
Memory Tiering과 MCE (v6.10+)
CXL 메모리, PMEM(Persistent Memory), 또는 NUMA hotplug로 도입된 메모리는 기존 DRAM과는 다른 신뢰도 특성을 가집니다. v6.10 이후 MCE 경로는 다음을 개선했습니다.
- Tier 식별: 에러가 발생한 물리 주소가 어떤 NUMA 노드/메모리 티어에 속하는지 식별해 로그에 포함.
/sys/bus/nd/devices/*의 nd_region 정보와 상관 분석 가능. - HWPoison과 메모리 migration: "이 페이지는 손상된 메모리에 있다"고 표시되면, 정상 DRAM의 페이지로 자동 복사한 뒤 원본 페이지를 오프라인합니다. v6.12에서 CXL 메모리의 poison 이벤트가 이 경로에 통합되어, 응용 프로그램이 크래시하지 않고 계속 동작 가능한 범위가 확대되었습니다.
- DAX 디바이스 에러:
dax/pmem의 DIMM 수준 에러가memory_failure()경로와 일관되게 처리됩니다. 파일시스템(Filesystem) (ext4/xfs DAX)에서 해당 파일 접근 시 SIGBUS를 반환.
MCE Severity 분류 개선 (v6.9+)
MCE severity 분류는 오랫동안 "복구 가능 vs 패닉"의 이분법에 가까웠습니다. v6.9 이후 mce_severity() 경로가 다음과 같이 세분화되었습니다.
| 단계 | 의미 | 커널 동작 |
|---|---|---|
MCE_NO_SEVERITY | 유효 에러 아님 | 무시 |
MCE_DEFERRED_SEVERITY | 지연 처리 가능 | CMCI/poll이 수거 |
MCE_KEEP_SEVERITY | 펌웨어가 이미 처리 / OS 무시 | 로그만 남김 |
MCE_SOME_SEVERITY | Correctable | EDAC 이벤트 생성 |
MCE_AO_SEVERITY (v6.9 세분화) | Action Optional — 복구 가능하나 필수 아님 | memory_failure() 비동기 호출 |
MCE_UCNA_SEVERITY | Uncorrected, No Action 필요 | 페이지 offline 시도 |
MCE_AR_SEVERITY | Action Required — 즉시 복구 필요 | SIGBUS / kernel panic if kernel mode |
MCE_PANIC_SEVERITY | 복구 불가 | panic |
- AO vs UCNA: 이전에는 모두 "correctable과 비슷하게 처리"로 묶였으나, AO는 명시적 복구 절차가 있고 UCNA는 단순 기록/오프라인만 가능하다는 차이를 코드에서 구분합니다.
- 사용자 공간 노출:
rasdaemon이 이러한 세분화를 인식해 보고하도록 v2.24+ 버전에서 업데이트되었습니다. 서비스 운영에서는rasdaemon --sevnames옵션을 확인하세요.
최신 MCE/RAS 인프라 변화 (v6.15~v6.17)
Linux v6.14까지의 변화는 앞 절에서 다루었습니다. 이 절에서는 v6.15 이후 메인라인에 병합된 주요 변화를 정리합니다.
EDAC Memory Repair Control 프레임워크 (v6.15)
Linux 6.15에서 EDAC 서브시스템에 Memory Repair Control 프레임워크가 추가되었습니다. 이 프레임워크는 메모리 하드웨어가 제공하는 자가 복구 기능을 커널에서 표준 인터페이스로 노출합니다.
지원하는 복구 동작:
- PPR (Post Package Repair): 손상된 DRAM 행(row)을 예비 행으로 교체합니다.
- Soft PPR — 전원 차단 시 효과가 소멸되는 임시 복구
- Hard PPR — 영구적인 행 교체 (JEDEC DDR4/DDR5 표준)
- Memory Sparing: 메모리 컨트롤러 수준에서 캐시라인·행·뱅크·랭크 단위 예비 교체를 지원합니다.
사용자 공간에서는 /sys/bus/edac/devices/<dev>/mem_repair/ 하위의 sysfs 인터페이스를 통해
복구 동작을 트리거할 수 있으며, rasdaemon이 이 인터페이스와 연동되도록 업데이트되었습니다.
상세 사용법은 커널 문서
Documentation/edac/memory_repair.rst를
참조하십시오.
커널 v6.15: EDAC Memory Repair Control 프레임워크 (PPR soft/hard, Memory Sparing) sysfs 인터페이스 추가.
CXL RAS/EDAC 통합 강화 (v6.16)
Linux 6.16에서는 CXL(Compute Express Link) 장치의 RAS 기능이 EDAC 프레임워크와 더욱 깊이 통합되었습니다. CXL 3.2 사양 및 JEDEC 표준에 정의된 다음 기능들이 커널에 추가되었습니다.
| 기능 | 표준 | 설명 |
|---|---|---|
| Patrol Scrub Control | CXL 3.2 | CXL 메모리 장치가 배경에서 주기적으로 메모리를 읽어 정정 가능한 오류를 조기에 발견하는 기능. sysfs를 통해 스크럽 속도 및 활성화를 제어합니다. |
| ECS (Error Check Scrub) | JEDEC DDR5 / ECS | DDR5 DRAM 모듈 내장 오류 검사 스크럽. 오류 임계값, 모드(On-Demand / Background), 카운트 리포트 등을 커널에서 설정합니다. |
| Perform Maintenance | CXL 3.2 | CXL 장치에 유지보수 작업(예: 내부 캐시 플러시(Flush), 미디어 검사)을 지시하는 표준화된 명령 인터페이스. |
| Memory Sparing | CXL / DDR5 | 캐시라인·행·뱅크·랭크 단위의 예비 교체를 CXL 장치 수준에서 제어합니다. |
이 기능들은 /sys/bus/edac/devices/ 및 /sys/bus/cxl/ 하위의 sysfs를 통해
사용자 공간에 노출됩니다. 스크럽 인터페이스에 대한 전반적인 설명은
Documentation/edac/scrub.rst를
참조하십시오.
커널 v6.16: CXL RAS — Patrol Scrub Control, ECS (DDR5), Perform Maintenance, Memory Sparing이 EDAC 프레임워크에 통합.
SGX EPC 페이지 독극물(Poison) 처리 수정 (v6.16)
Intel SGX(Software Guard Extensions)에서 Enclave Page Cache(EPC) 페이지에 MCE가 발생하면
해당 페이지가 독극물 처리(poisoned)됩니다.
v6.16 이전에는 독극물 처리된 EPC 페이지가 sgx_active_page_list에 잔류하여
커널 reclaimer가 해당 페이지를 EWB(Enclave Write Back) 마이크로코드 작업으로 내보내려 시도할 수 있었습니다.
그러나 EWB 명령은 독극물 처리된 페이지에 대해 또 다른 MCE를 발생시켜 CPU 강제 종료 → 나머지 CPU panic으로 이어지는 치명적 결함이 있었습니다.
epc_page->poison 플래그 설정 →
reclaimer가 리스트 순회 중 해당 페이지를 발견 →
EWB 마이크로코드 실행 →
두 번째 MCE →
커널 panic
v6.16 수정 사항: reclaimer가 EPC 페이지를 처리하기 전에 poison 플래그를 확인하여
독극물 처리된 페이지는 즉시 건너뛰도록 변경되었습니다.
결과적으로 독극물 처리된 EPC 페이지가 정상적으로 sgx_active_page_list에서 제거되며
추가 MCE 발생이 방지됩니다.
커널 v6.16: SGX EPC 독극물 페이지 reclaim 경로에서 이중 MCE → panic 버그 수정. epc_page->poison 플래그 검사 추가.
HWPoison 대형 폴리오(folio) 처리 개선
메모리 오류 처리 경로(memory_failure())는 페이지 관리 구조가 folio로 전환됨에 따라
지속적인 리팩토링이 진행 중입니다.
특히 대형 폴리오(large folio)에 대한 독극물 처리 지원이 개선되었습니다.
- 대형 폴리오 분할(split) 후 독극물 플래그가 올바르게 전파되도록 수정
- 분할 과정에서
PG_hwpoison추적 정확도 향상 - HWPoison 이벤트의 folio 단위 보고로 사용자 공간 정밀도 개선
이 작업은 v6.15~v6.17에 걸쳐 점진적으로 반영되고 있으며, 대규모 메모리를 운용하는 서버 환경에서 신뢰성 있는 MCE 복구 경로를 보장합니다.
커널 설정 옵션
MCE/RAS 관련 커널 설정 옵션의 종합 목록입니다. 프로덕션 서버에서는 대부분 활성화하는 것이 권장됩니다.
| 설정 | 기본값 | 설명 |
|---|---|---|
CONFIG_X86_MCE | y (x86에서) | MCE 핸들러 핵심. 비활성화하면 MCE 시 triple fault |
CONFIG_X86_MCE_INTEL | y | Intel 전용 MCE 확장 (CMCI, LMCE, CEC 등) |
CONFIG_X86_MCE_AMD | y | AMD 전용 MCE 확장 (SMCA, Threshold 인터럽트) |
CONFIG_X86_MCE_THRESHOLD | y | AMD Threshold 카운터 인터럽트 지원 |
CONFIG_X86_MCE_INJECT | m | MCE 주입 모듈 (테스트용) |
CONFIG_MEMORY_FAILURE | y | memory_failure() / HWPoison 지원 |
CONFIG_HWPOISON_INJECT | m | HWPoison 주입 debugfs 인터페이스 |
CONFIG_EDAC | y | EDAC 서브시스템 핵심 |
CONFIG_EDAC_GHES | y | GHES 기반 EDAC 드라이버 |
CONFIG_EDAC_SKX | m | Intel Skylake-SP EDAC |
CONFIG_EDAC_I10NM | m | Intel Ice Lake / SPR EDAC |
CONFIG_EDAC_AMD64 | m | AMD64 EDAC (Fam10h~Zen4) |
CONFIG_ACPI_APEI | y | ACPI Platform Error Interface |
CONFIG_ACPI_APEI_GHES | y | GHES(Generic HW Error Source) 지원 |
CONFIG_ACPI_APEI_EINJ | m | EINJ 에러 주입 지원 |
CONFIG_ACPI_APEI_ERST_DEBUG | m | ERST 디버그 인터페이스 |
CONFIG_RAS_CEC | y | Corrected Error Collector (Intel) |
zcat /proc/config.gz | grep -E "MCE|EDAC|APEI|MEMORY_FAILURE|HWPOISON"
또는 grep -r "MCE\|EDAC" /boot/config-$(uname -r)
MCE 로그 디코딩 실전
실제 MCE 로그를 단계별로 해석하는 실전 가이드입니다. dmesg에 출력되는 MCE 메시지의 각 필드가 무엇을 의미하는지 분석합니다.
STATUS 디코딩 단계별 안내
예시: STATUS = 0xBE00000000800400
# 64비트 이진 분해
0xBE00000000800400 =
1 0 1 1 1 1 1 0 0000... 1000 0000 0000 0100 0000 0000
63 62 61 60 59 58 57 56 [31:16] [15:0] = 0x0400
# 상위 비트 해석
bit 63 (VAL) = 1 → 유효한 에러
bit 62 (OVER) = 0 → 오버플로 없음
bit 61 (UC) = 1 → 교정 불가 (Uncorrected)
bit 60 (EN) = 1 → 에러 보고 활성
bit 59 (MISCV) = 1 → MISC 레지스터 유효
bit 58 (ADDRV) = 1 → ADDR 레지스터 유효
bit 57 (PCC) = 1 → 프로세서 컨텍스트 손상!
bit 56 (S) = 0 → MCG_SER_P 미지원 또는 비활성
# 에러 코드 (bits 15:0) = 0x0400
0x0400 = 0000 0100 0000 0000
→ Internal Timer Error (Simple Error Code)
# 결론: UC + PCC → 프로세서 상태 복구 불가 → 시스템 패닉
유용한 디코딩 도구
# mcelog으로 디코딩 (레거시)
$ sudo mcelog --ascii --file /tmp/mce.log
# rasdaemon SQLite 쿼리
$ sudo sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT datetime(timestamp,'unixepoch'), bank, status, addr,
CASE WHEN (status >> 61) & 1 THEN 'UC' ELSE 'CE' END as type
FROM mce_record ORDER BY id DESC LIMIT 10;"
# edac-util로 DIMM 매핑 확인
$ edac-util -s
$ edac-util -r
CE (Corrected Error) 로그 예제
# EDAC CE 로그 예시
[54321.987] EDAC MC0: 1 CE memory read error on CPU_SrcID#0_MC#0_Chan#0_DIMM#0
(channel:0 slot:0 page:0x1234 offset:0x0 grain:32 syndrome:0x0)
[54321.987] EDAC MC0: 1 CE ... area:DRAM err_code:0001:0091
# 해석:
# - MC0: 메모리 컨트롤러 0
# - 1 CE: Corrected Error 1회
# - Channel 0, Slot 0: 첫 번째 채널의 첫 번째 DIMM
# - page:0x1234: 물리 페이지 프레임 번호
# - grain:32: 에러 입도 (바이트)
# - err_code:0001:0091: 계층/에러 코드
SRAR (Action Required) 로그 예제
[98765.432] mce: [Hardware Error]: CPU 12 Bank 7: bd80000000100134
[98765.432] mce: [Hardware Error]: RIP !INEXACT! 33:<00007f1234567890>
[98765.432] mce: [Hardware Error]: TSC a1b2c3d4e5f67890 ADDR 0000000087654000
[98765.432] mce: [Hardware Error]: MISC 000000000000008c PPIN 1234567890abcdef
[98765.432] mce: [Hardware Error]: PROCESSOR 2:806f8 TIME 1710000000 SOCKET 1 APIC 24
[98765.432] Memory failure 0x87654: recovery action for dirty LRU page: Recovered
# STATUS 0xBD80000000100134 디코딩:
# bit 63 VAL=1, bit 61 UC=1, bit 60 EN=1
# bit 59 MISCV=1, bit 58 ADDRV=1
# bit 57 PCC=0 → 프로세서 컨텍스트 정상
# bit 56 S=1, bit 55 AR=1 → SRAR
# Error Code (bits 15:0) = 0x0134 → L3 캐시 데이터 읽기 에러
# 결과: memory_failure()로 페이지 복구 성공 ("Recovered")
자주 발생하는 MCE 패턴
데이터센터에서 흔히 관찰되는 MCE 패턴과 각각의 원인, 대응 방법을 정리합니다.
패턴 1: CE 누적 (DIMM 열화)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | EDAC MC0: 1 CE ... on DIMM0 (반복) |
| STATUS 특성 | UC=0, VAL=1 — CE (Corrected Error) |
| 원인 | DIMM 셀 열화, 접촉 불량, 전압 마진 부족 |
| 조치 | 1) DIMM 재장착 시도 2) CE 임계값 초과 시 교체 3) BIOS에서 스페어링 활성화 |
| 긴급도 | 낮음 → 중간 (교체 계획 수립) |
패턴 2: UC no PCC (복구 가능한 에러)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: Uncorrected hardware memory error in user-access at ... |
| STATUS 특성 | UC=1, PCC=0, S=1, AR=1 → SRAR |
| 원인 | 메모리 비트 플립 (ECC 2비트 이상 오류) |
| 조치 | 1) 해당 페이지 HWPoison 자동 처리됨 2) 동일 DIMM 모니터링 3) 반복 시 DIMM 교체 |
| 긴급도 | 중간 (서비스 재시작 필요할 수 있음) |
패턴 3: UC + PCC (시스템 패닉)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | Kernel panic - not syncing: Fatal machine check on current CPU |
| STATUS 특성 | UC=1, PCC=1 — 프로세서 컨텍스트 손상 |
| 원인 | CPU 캐시 패리티, 마이크로코드 버그, 전력 불안정, 과열 |
| 조치 | 1) BERT 데이터 확인 2) 마이크로코드 업데이트 3) CPU 교체 고려 4) 전력/냉각 점검 |
| 긴급도 | 높음 (즉시 대응) |
패턴 4: OVER + UC (에러 오버플로)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: Overflowed uncorrected error |
| STATUS 특성 | OVER=1, UC=1 — 이전 에러가 읽히기 전에 새 에러 발생 |
| 원인 | 에러 폭주, 하드웨어 심각한 결함 |
| 조치 | 즉시 하드웨어 점검. 정보 손실로 인해 root cause 분석 어려울 수 있음 |
| 긴급도 | 매우 높음 |
패턴 5: Thermal MCE
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | CPU0: Temperature above threshold, cpu clock throttled |
| 원인 | CPU 과열 — 쿨러 고장, 열 전도 패드 열화, 데이터센터 냉방 문제 |
| 조치 | 1) 즉시 냉각 확인 2) 팬 RPM 점검 3) thermal paste 재도포 4) 부하 분산(Load Balancing) |
| 참고 | Thermal Management 페이지 참조 |
패턴 6: 버스/인터커넥트 에러
| 항목 | 내용 |
|---|---|
| 에러 코드 | 0x0800 범위 (Bus/Interconnect Error) |
| 원인 | QPI/UPI 링크 에러, PCIe 버스 에러, 물리적 커넥터 문제 |
| 조치 | 1) PCIe 카드 재장착 2) 케이블 점검 3) BIOS에서 링크 속도 조정 |
패턴 7: IIO PCIe Completion Timeout
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: IIO Bank ... MSCOD=0x0000 Completion Timeout +
pcieport ... AER: Uncorrectable error received |
| MCA 뱅크 | Bank 5~8 (IIO 스택, CPU 모델별 가변) |
| MSCOD | 0x0000 (Completion Timeout) |
| 원인 | GPU/NVMe/NIC 행(hang), FLR 중 접근, PCIe 링크 불안정, 라이저 접촉 불량 |
| 조치 | 1) lspci -vvvs <BDF>로 링크 상태 확인
2) 장치 펌웨어 업데이트 3) 물리적 재장착 4) CTO 값 조정 |
| 긴급도 | 중간~높음 (장치 유형에 따라 서비스 영향 판단) |
패턴 8: IIO Poisoned TLP (데이터 독성 전파)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: IIO Bank ... MSCOD=0x0002 Poisoned TLP |
| MSCOD | 0x0002 (Poisoned TLP) |
| 원인 | GPU VRAM ECC UC, NVMe 내부 DRAM 에러, 장치 메모리 결함 |
| GPU 관련 | NVIDIA Xid 48/63 (DBE/Row Remapper Failure)과 동시 발생 가능 |
| 조치 | 1) 장치 에러 로그 확인 2) 장치 ECC 상태 확인 3) BIOS Viral Mode 설정 확인 4) 장치 교체 검토 |
| 긴급도 | 높음 (데이터 무결성 위협) |
패턴 9: TOR (Table of Requests) 타임아웃
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | Kernel panic ... MCE: CHA Bank ... TOR Timeout |
| MCA 뱅크 | Bank 21~24+ (CHA/LLC) |
| 원인 | DIMM 하드 페일 → 메모리 접근 불가, PCIe 장치 무응답 → DMA 체류, UPI 링크 다운 → 원격 메모리 접근 중단, 마이크로코드 버그 |
| 분석 핵심 | TOR Timeout은 거의 항상 2차 에러. 동시 발생한 다른 뱅크 에러(IIO/M2M/UPI)에서 근본 원인을 찾아야 함 |
| 조치 | 1) BERT 데이터에서 선행 에러 확인 2) 동시 발생 MCA 뱅크 에러 전수 분석 3) 마이크로코드 업데이트 4) 메모리/장치 하드웨어 점검 |
| 긴급도 | 매우 높음 (패닉 후 원인 분석 필수) |
패턴 10: UPI 링크 에러 (멀티소켓)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: CPU N Bank 5 ... UPI CRC Error |
| MCA 뱅크 | Bank 5~6 (UPI) |
| CE 상황 | CRC 교정 가능 → 자동 재전송, 성능 약간 저하. CE 빈도 증가 시 링크 열화 경고 |
| UC 상황 | 링크 다운 → 원격 NUMA 노드 접근 불가 → 해당 노드 프로세스 대량 SIGBUS/패닉 |
| 원인 | 소켓 간 인터커넥트 하드웨어 열화, 메인보드 UPI 트레이스 결함, 전기적 잡음 |
| 조치 | 1) UPI CE 추세 모니터링 2) BIOS에서 UPI 링크 속도 다운그레이드 시도 3) CPU 또는 메인보드 교체 검토 |
패턴 11: M2M Patrol Scrub UC (사전 발견 메모리 에러)
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | mce: M2M Bank ... Patrol Scrub Error UC |
| MCA 뱅크 | Bank 9~12 (M2M) |
| 의미 | Patrol Scrubbing이 배경에서 교정 불가 메모리 에러를 사전 발견 |
| 장점 | 아직 소비되지 않은 데이터 → SRAO로 처리 가능 (memory_failure로 페이지 오프라인) |
| 원인 | DIMM 열화, ECC 2비트+ 오류 (Demand 접근 전에 발견) |
| 조치 | 1) 해당 DIMM CE 이력 확인 2) HWPoison 페이지 확인 3) DIMM 교체 계획 |
| 긴급도 | 중간 (사전 발견이므로 즉각적 서비스 영향은 제한적) |
패턴 12: GPU/가속기 관련 IIO 연쇄 에러
| 항목 | 내용 |
|---|---|
| 에러 체인 | GPU VRAM ECC UC → Poisoned TLP → IIO MCE (MSCOD=0x0002) → 소비 코어 SRAR → SIGBUS |
| NVIDIA 시그니처 | dmesg에 NVRM: Xid ... 48(DBE),
Xid ... 63(Row Remapper Failure) 등장 |
| AMD GPU 시그니처 | dmesg에 amdgpu: ... ECC error |
| 조치 |
|
| 긴급도 | 높음 (학습 작업 중단, GPU 하드웨어 결함 확인 필요) |
패턴 13: Row Hammer 유발 MCE
Row Hammer는 DRAM의 물리적 특성을 이용한 현상으로, 특정 행을 반복 접근(Hammering)하면 인접 행(Victim Row)의 셀에서 전하 누출이 발생하여 비트 플립(Bit Flip)이 일어납니다. 이는 의도적 공격뿐 아니라 고부하 워크로드에서도 자연적으로 발생할 수 있습니다.
Row Hammer → MCE 발생 경로
- 반복 행 활성화: 공격자 또는 워크로드가 동일 DRAM 행을 수만~수십만 회 반복 접근 (
clflush+ 메모리 읽기 루프) - 인접 행 비트 플립: Victim Row의 셀 커패시터(Capacitor) 전하가 누출되어 0→1 또는 1→0 전환
- ECC 감지: 메모리 컨트롤러의 ECC가 비트 오류를 감지 — 단일 비트 → CE, 다중 비트 → UCE → memory_failure() 호출
- MCE 생성: MCA 뱅크(메모리 컨트롤러)에 에러 기록, STATUS.VAL=1 설정
TRR (Target Row Refresh)과 한계
DDR4 이후 DRAM 제조사들은 TRR(Target Row Refresh)을 도입하여 Aggressor Row 감지 시 Victim Row를 사전 리프레시(Refresh)합니다. 그러나 TRRespass, Half-Double, Blacksmith 등 연구에서 TRR 우회 패턴이 발견되었으며, DDR5에서는 표준화된 RFM(Refresh Management) 명령으로 개선되었으나 공정 미세화에 따라 취약성은 증가 추세입니다.
| 항목 | 내용 |
|---|---|
| dmesg 시그니처 | EDAC MC0: 1 CE ... on DIMM0 (단시간 대량 반복) +
mce: [Hardware Error]: ... ADDR: 동일 뱅크 내 인접 주소 |
| STATUS 특성 | UC=0 (CE 단계) 또는 UC=1, PCC=0 (UCE 확대 시). 에러 코드 0x0150~0x0151 (메모리 컨트롤러) |
| 원인 | DRAM Row Hammer — 워크로드 또는 의도적 공격에 의한 인접 행 비트 플립 |
| 구별 방법 | 1) CE가 동일 DIMM의 인접 물리 행에 집중 2) 짧은 시간에 비정상적 CE 폭증 3) edac-util로 특정 랭크/뱅크 집중 확인 |
| 조치 | 1) BIOS에서 TRR/pTRR 활성화 확인 2) DIMM 펌웨어 업데이트 3) ECC 모드 강화(Chipkill) 4) 반복 시 DIMM 교체 5) 보안 관점: CAP_SYS_RAWIO 제한 검토 |
| 긴급도 | 중간~높음 (CE 단계: 모니터링, UCE 확대 시: 즉시 대응) |
성능 영향과 튜닝
MCE 서브시스템은 정상 상태에서 거의 오버헤드가 없지만, 에러 발생 시나 폴링 설정에 따라 성능에 영향을 줄 수 있습니다. 대규모 서버 클러스터에서는 MCE 관련 설정이 전체 가용성에 영향을 미칠 수 있으므로 적절한 튜닝이 필요합니다.
MCE 관련 커널 부트 파라미터
| 파라미터 | 설명 | 기본값 |
|---|---|---|
mce=off | MCE 핸들링 완전 비활성화 (위험) | 비활성 |
mce=dont_log_ce | CE를 dmesg에 기록하지 않음 | 비활성 |
mce=ignore_ce | CE를 완전히 무시 | 비활성 |
mce=no_cmci | CMCI 비활성화 (폴링만 사용) | 비활성 |
mce=bootlog | 부팅 시 MCA 뱅크의 이전 에러 로깅 | 활성 |
mce=recovery | SRAR/SRAO 복구 경로 강제 활성화 | 자동 감지 |
memory_corruption_check=1 | 주기적 메모리 무결성 검사 | 0 |
sysfs 튜닝 파라미터
| 파라미터 | 경로 | 기본값 | 설명 |
|---|---|---|---|
| check_interval | /sys/devices/system/machinecheck/machinecheck0/check_interval | 300 (5분) | MCE 폴링 주기 (초). CMCI 비지원 CPU에서만 의미 있음 |
| tolerant | /sys/devices/system/machinecheck/machinecheck0/tolerant | 1 | MCE 대응 레벨 (0=항상 패닉, 1=기본, 2=관대, 3=무시) |
| monarch_timeout | /sys/devices/system/machinecheck/machinecheck0/monarch_timeout | CPU 종속 | Monarch 랑데부 타임아웃 (us). 0이면 타임아웃 없음 |
| dont_log_ce | /sys/devices/system/machinecheck/machinecheck0/dont_log_ce | 0 | 1이면 CE를 dmesg에 기록하지 않음 (CE 폭주 시 유용) |
| ignore_ce | /sys/devices/system/machinecheck/machinecheck0/ignore_ce | 0 | 1이면 CE를 완전히 무시 (CMCI도 비활성화) |
ECC Patrol Scrubbing
메모리 스크러빙은 DRAM의 모든 위치를 주기적으로 읽어 CE를 사전에 발견하고 교정합니다. 스크럽 주기가 짧을수록 UC 에러(CE 누적 → 2비트 오류)를 예방하지만, 메모리 대역폭을 소비합니다.
| 설정 | 위치 | 권장값 |
|---|---|---|
| Patrol Scrub | BIOS/UEFI 설정 | Enabled (기본) |
| Scrub Rate | BIOS/UEFI 설정 | 24시간 이내 전체 메모리 스캔 |
| EDAC scrub rate | sysfs (일부 드라이버) | 드라이버 종속 |
- Memory Patrol Scrub: Enabled
- Memory ECC: Enabled (기본)
- Memory RAS Mode: Independent (성능) 또는 Mirroring (가용성)
- ADDDC (Adaptive Double DRAM Device Correction): Enabled (서버)
- Corrected Error Threshold: 제조사 권장값
성능 영향 요약
| 상황 | 성능 영향 | 완화 방법 |
|---|---|---|
| 정상 (에러 없음) | 무시할 수 있음 (~0%) | - |
| MCE 폴링 (CMCI 없음) | 매우 낮음 (5분마다 MSR 읽기) | CMCI 지원 CPU 사용 |
| CMCI 스톰 | 중간 (인터럽트 폭주) | 자동 폴링 전환 (커널 기본 동작) |
| MCE 핸들링 (UC) | 높음 (모든 CPU 동기화) | LMCE 활성화 |
| memory_failure() | 해당 페이지 오프라인 | 총 메모리 대비 무시 가능 |
| Patrol Scrub | 낮음 (~1-3% 대역폭) | 비활성화 가능하나 비권장 |
Prometheus + Grafana 모니터링 구축
대규모 서버 환경에서는 rasdaemon의 SQLite DB를 Prometheus 메트릭으로 변환하여
Grafana 대시보드에서 실시간 모니터링하고, Alertmanager로 임계값 알림을 설정합니다.
node_exporter의 textfile collector를 활용하면 별도 exporter 개발 없이 구현 가능합니다.
#!/bin/bash
# /usr/local/bin/edac-metrics.sh
# node_exporter textfile collector용 EDAC 메트릭 생성 스크립트
# cron: */5 * * * * /usr/local/bin/edac-metrics.sh
TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector"
PROM_FILE="${TEXTFILE_DIR}/edac.prom"
TMP_FILE="${PROM_FILE}.tmp"
DB="/var/lib/rasdaemon/ras-mc_event.db"
mkdir -p "${TEXTFILE_DIR}"
{
echo "# HELP edac_ce_total Total corrected errors per DIMM"
echo "# TYPE edac_ce_total counter"
# EDAC sysfs에서 직접 메트릭 수집
for mc in /sys/devices/system/edac/mc/mc*; do
[ -d "$mc" ] || continue
mc_id=$(basename "$mc" | sed 's/mc//')
for dimm in "${mc}"/dimm*; do
[ -d "$dimm" ] || continue
dimm_id=$(basename "$dimm" | sed 's/dimm//')
label=$(cat "${dimm}/dimm_label" 2>/dev/null || echo "unknown")
ce=$(cat "${dimm}/dimm_ce_count" 2>/dev/null || echo 0)
ue=$(cat "${dimm}/dimm_ue_count" 2>/dev/null || echo 0)
echo "edac_ce_total{mc=\"${mc_id}\",dimm=\"${dimm_id}\",label=\"${label}\"} ${ce}"
echo "edac_ue_total{mc=\"${mc_id}\",dimm=\"${dimm_id}\",label=\"${label}\"} ${ue}"
done
done
echo "# HELP edac_ue_total Total uncorrected errors per DIMM"
echo "# TYPE edac_ue_total counter"
# rasdaemon DB에서 최근 1시간 CE rate 계산
echo "# HELP edac_ce_rate_1h CE count in last hour"
echo "# TYPE edac_ce_rate_1h gauge"
if [ -f "${DB}" ]; then
sqlite3 "${DB}" "SELECT mc, top_layer, mid_layer, COUNT(*) FROM mc_event \
WHERE err_type='Corrected' AND timestamp > strftime('%s','now','-1 hour') \
GROUP BY mc, top_layer, mid_layer;" | while IFS='|' read mc ch dm cnt; do
echo "edac_ce_rate_1h{mc=\"${mc}\",channel=\"${ch}\",dimm=\"${dm}\"} ${cnt}"
done
fi
} > "${TMP_FILE}"
mv "${TMP_FILE}" "${PROM_FILE}"
# node_exporter 실행 시 textfile collector 경로 지정
$ node_exporter --collector.textfile.directory=/var/lib/node_exporter/textfile_collector
# Prometheus에서 확인
$ curl -s http://localhost:9090/api/v1/query?query=edac_ce_total | jq .
# Alertmanager 규칙 예시 (prometheus.rules.yml)
# CE가 1시간에 50회 이상이면 경고, UE 발생 시 긴급
# Prometheus alerting rules — /etc/prometheus/rules/edac.rules.yml
groups:
- name: edac_alerts
rules:
- alert: HighCorrectedErrorRate
expr: increase(edac_ce_total[1h]) > 50
for: 5m
labels:
severity: warning
annotations:
summary: "DIMM {{ $labels.label }} CE 급증 ({{ $value }}/h)"
description: "MC {{ $labels.mc }} DIMM {{ $labels.dimm }}에서 1시간 내 {{ $value }}건의 CE 발생. DIMM 교체 검토 필요."
- alert: UncorrectedError
expr: increase(edac_ue_total[5m]) > 0
labels:
severity: critical
annotations:
summary: "DIMM {{ $labels.label }} UE 발생!"
description: "MC {{ $labels.mc }} DIMM {{ $labels.dimm }}에서 복구 불가 에러. 즉시 DIMM 교체 필요."
- alert: CEtoUEEscalation
expr: increase(edac_ce_total[24h]) > 100 and increase(edac_ue_total[24h]) > 0
labels:
severity: critical
annotations:
summary: "CE→UE 에스컬레이션 감지: {{ $labels.label }}"
- 패널 1: 시간축 CE/UE 추이 그래프 (increase(edac_ce_total[1h]) by (label))
- 패널 2: DIMM별 누적 에러 히트맵 (소켓×채널 행렬)
- 패널 3: 서버별 EDAC 상태 테이블 (정상/주의/위험 색상 코딩)
- 패널 4: 최근 24시간 UE 발생 서버 리스트 (즉시 대응 필요)
대규모 서버 클러스터 MCE 통계
Google, Meta(Facebook), Microsoft 등이 발표한 대규모 데이터센터 연구에서 DRAM 에러율과 DIMM 수명 패턴에 대한 실측 데이터를 확인할 수 있습니다. 이 데이터는 DIMM 교체 정책과 용량 계획에 중요한 근거가 됩니다.
| 연구 출처 | 플릿 규모 | CE 발생 DIMM 비율 | UE 발생 DIMM 비율 | 주요 발견 |
|---|---|---|---|---|
| Google (2009) | ~수십만 서버 | 연간 ~32% 서버에서 CE | 연간 ~1.3% 서버에서 UE | CE 있는 DIMM은 이후 CE 발생 확률 6~30배 증가 |
| Meta/Facebook (2015) | ~수백만 DIMM | 연간 ~20% DIMM에서 CE | 연간 ~0.03% DIMM에서 UE | 온도 10도 상승 시 에러율 ~2배 증가 |
| Microsoft Azure (2018) | 수백만 서버 | 연간 ~9% 서버에서 CE | - | CE 패턴으로 72시간 내 UE 예측 가능 (논문 기반) |
CE → UE 에스컬레이션 확률
| CE 패턴 | 24시간 내 UE 확률 | 7일 내 UE 확률 | 권장 조치 |
|---|---|---|---|
| 단일 CE (1회) | < 0.1% | ~0.5% | 모니터링 유지 |
| 반복 CE (같은 주소, 10회+) | ~2-5% | ~8-15% | 해당 페이지 soft offline, DIMM 교체 계획 |
| CE 폭주 (24시간 100회+) | ~10-20% | ~25-40% | 즉시 DIMM 교체, 워크로드 마이그레이션 |
| 다중 행/열 CE (여러 주소) | ~15-30% | ~35-50% | 긴급 DIMM 교체, 행/열 패턴은 DRAM 셀 열화 표시 |
DIMM 수명 곡선 (Bathtub Curve)
| 운영 기간 | 고장률 특성 | 주요 원인 | 운영 가이드 |
|---|---|---|---|
| 0~6개월 (초기 고장) | 상대적으로 높음 (~2-3x 정상) | 제조 결함, 솔더링 불량, 초기 전기적 스트레스 | DOA 검사 강화, burn-in 테스트 수행 |
| 6개월~3년 (안정기) | 최저 (기준 고장률) | 랜덤 소프트 에러 (우주선, 알파입자) | 정상 모니터링, patrol scrub 유지 |
| 3~5년+ (마모기) | 점진적 증가 (~1.5-4x) | DRAM 셀 열화, Row Hammer, 반복 스트레스 | CE 임계값 강화, 사전 교체 프로그램 운영 |
MCE와 ECC 메모리 모드
서버 메모리 컨트롤러는 여러 ECC/RAS 모드를 지원하며, 가용성과 성능 사이의 트레이드오프에 따라 적절한 모드를 선택해야 합니다. BIOS/UEFI에서 설정하며, MCE/EDAC이 보고하는 에러 유형과 복구 능력에 직접 영향을 줍니다.
| 모드 | 용량 영향 | 대역폭 영향 | ECC 능력 | 가용성 | 적용 시나리오 |
|---|---|---|---|---|---|
| Independent | 100% 사용 가능 | 최대 대역폭 | 기본 SECDED (1비트 교정, 2비트 검출) | 보통 | HPC, 대용량 메모리 필요 환경, 일반 서버 |
| Mirroring | 50% (절반이 미러) | 읽기: ~동일, 쓰기: ~동일 | 전체 채널 장애까지 복구 가능 | 최고 | 미션 크리티컬 DB, 금융, 의료 시스템 |
| Lockstep | 100% (채널 쌍 결합) | 50% (2채널 → 1논리채널) | x8 SDDC (단일 DRAM 칩 전체 오류 교정) | 높음 | Chipkill 필요 환경, 단일 칩 고장 허용 |
| Sparing | 1 Rank/DIMM 예비 | 전환 시 일시 저하 | CE 임계값 초과 시 자동 전환 | 높음 | 장기 무중단 운영 서버, 교체 주기가 긴 환경 |
| ADDDC | ~100% (동적) | 활성화 시 약간 저하 | 2x DRAM 디바이스 에러 교정 (Adaptive) | 매우 높음 | Intel SPR+, DDR5 서버, 최신 데이터센터 |
- 기본 권장: Independent + ADDDC Enabled (DDR5 서버). 용량 손실 없이 높은 RAS 제공
- 최고 가용성: Mirroring + ADDDC. 미러 채널 내에서도 ADDDC로 이중 보호
- HPC/대역폭: Independent + Patrol Scrub. 성능 최우선, CE 모니터링으로 보완
- 레거시 DDR4: Lockstep (x4 DIMM) 또는 Rank Sparing. Chipkill 수준 보호
dimm_edac_mode sysfs 항목으로 보고합니다.
DDR5 On-Die ECC와 MCE 변화
DDR5는 JEDEC 표준으로 On-Die ECC를 필수 기능으로 규정합니다. 각 DRAM 다이(Die) 내부에 ECC 엔진이 탑재되어, 데이터가 DRAM 칩 외부로 나가기 전에 단일 비트 오류를 자체 교정합니다. 이 변화는 호스트 측 MCE 패턴에 근본적인 영향을 미칩니다.
On-Die ECC 동작 원리
- 내부 구조: DDR5 다이는 128비트 데이터에 8비트 ECC 체크 비트를 추가하여 총 136비트를 저장. 읽기 시 내부에서 SECDED 수행
- 투명성: On-Die ECC 교정은 호스트 메모리 컨트롤러에 투명(Transparent)하므로, 교정된 오류는 CE MCE로 보고되지 않음
- 2계층 ECC: DDR5 서버 환경에서는 On-Die ECC(1차) + 호스트 ECC/Chipkill(2차)의 이중 보호가 적용됨
MCE 모니터링에 미치는 영향
- CE 감소 착시: DIMM 셀이 열화되더라도 On-Die ECC가 교정하므로 호스트 CE 카운트가 낮게 유지됨. DIMM이 실제로는 악화 중이어도 "정상"으로 보일 수 있음
- 급격한 UCE 전환: On-Die ECC 교정 한계를 넘는 오류(2비트 이상)가 발생하면, CE 없이 갑자기 UCE로 나타날 수 있음
- 기존 CE 임계값 무효화: DDR4 시절 "CE 100회/일 초과 시 DIMM 교체" 같은 정책이 DDR5에서는 적합하지 않을 수 있음
DDR5 시대의 대안적 건강 모니터링
- DDR5 ECS(Error Check and Scrub): JEDEC 표준의 인밴드(In-band) ECS 명령으로 DRAM 내부 오류 카운터 조회 가능. 호스트 메모리 컨트롤러가 주기적으로 ECS 수행
- 내부 에러 로깅: 일부 DDR5 DRAM은 MR(Mode Register)를 통해 On-Die ECC 교정 횟수를 노출. 하드웨어 벤더별 지원 수준 차이 있음
- CXL 메모리 건강 명령: CXL 연결 메모리의 경우 CXL 2.0+ Health Status 명령(
Get Health Info)으로 미디어 에러율, 온도, 수명 정보를 직접 조회 - Intel PPR(Post Package Repair): Hard PPR은 결함 있는 DRAM 행을 영구적으로 스페어 행으로 대체. Soft PPR은 재부팅 전까지만 유효한 임시 수리
- Device-level Sparing: Intel Sapphire Rapids 이후, 결함 DRAM 디바이스를 투명하게 스페어 디바이스로 교체 (ADDDC 확장)
DDR4 ECC vs DDR5 On-Die ECC + Host ECC 비교
| 비교 항목 | DDR4 (Host ECC만) | DDR5 (On-Die ECC + Host ECC) |
|---|---|---|
| 1차 ECC | 없음 (호스트 ECC만) | On-Die ECC (DRAM 내부 SECDED) |
| 2차 ECC | SECDED / Chipkill (호스트) | SECDED / Chipkill (호스트) |
| 단일 비트 오류 | 호스트 CE MCE 생성 | On-Die에서 교정, CE MCE 미생성 |
| 2비트 오류 (같은 워드) | 호스트 UCE → memory_failure() | On-Die ECC 미교정 → 호스트 ECC 감지, CE 또는 UCE |
| CE 기반 DIMM 건강 감시 | 효과적 (CE 증가 = 열화 신호) | 제한적 (On-Die ECC가 CE를 마스킹) |
| 대안 모니터링 | CE 카운터, EDAC sysfs | ECS, 내부 에러 로깅, CXL Health, PPR 이력 |
| UCE 발생 패턴 | CE 점진적 증가 후 UCE 전환 | CE 경고 없이 갑작스런 UCE 가능 |
| Patrol Scrub 효과 | 잠재 CE 조기 발견에 효과적 | On-Die 교정으로 마스킹된 오류는 감지 불가, ECS 병행 필요 |
| RAS 운영 전략 | CE 임계값 기반 사전 교체 | 다층 모니터링 필요 (ECS + CE + CXL Health + PPR 이력 종합 판단) |
rasdaemon의 DDR5 지원 버전(v0.8.0+)을 사용하여 ECS 이벤트와 CE를 함께 모니터링하세요.
CE 카운트만으로 DIMM 교체를 판단하지 말고, ECS 결과와 PPR 이력을 종합적으로 평가해야 합니다.
MCE 운영 플레이북
CE 인시던트 대응 체크리스트
- 에러 수집:
ras-mc-ctl --summary로 DIMM별 CE 카운트 확인 - 추세 분석: rasdaemon SQLite에서 시간대별 CE 빈도 쿼리
- DIMM 식별: EDAC sysfs에서 소켓/채널/DIMM 슬롯 위치 확인
- 임계값 판단: 24시간 내 CE > 100 → DIMM 교체 권장 (벤더 가이드 참조)
- 선제 조치:
soft_offline_page()로 문제 페이지 사전 격리 고려
UC 인시던트 대응 체크리스트
- 서비스 영향 확인: SIGBUS로 종료된 프로세스 확인 (
dmesg | grep -i sigbus) - HWPoison 상태:
page-types -l -b hwpoison으로 오프라인 페이지 수 확인 - 이전 CE 이력: 동일 DIMM에서 CE 누적이 있었는지 확인 (예측 가능했는지)
- DIMM 교체: UC 발생 DIMM은 교체 우선순위 최상위로 에스컬레이션
- 마이크로코드: 최신 마이크로코드 적용 여부 확인
패닉 후 복구 체크리스트
- BERT 확인: 재부팅 후
dmesg | grep BERT로 이전 에러 정보 수집 - crash dump: kdump/ERST로 저장된 vmcore 분석 (
crash유틸리티) - BIOS 에러 로그: BMC/IPMI SEL 로그 확인 (
ipmitool sel list) - 하드웨어 점검: CPU/DIMM/보드 교체 결정
- 사후 보고서: 타임라인, root cause, 재발 방지 작성
모니터링 설정 권장사항
# rasdaemon + 알림 설정 예시
# 1. rasdaemon 서비스 활성화
$ sudo systemctl enable --now rasdaemon
# 2. CE 모니터링 스크립트 (cron에 등록)
$ cat /usr/local/bin/mce-monitor.sh
#!/bin/bash
CE_COUNT=$(sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT COUNT(*) FROM mc_event WHERE err_type='Corrected' AND timestamp > strftime('%s','now','-1 hour');")
if [ "$CE_COUNT" -gt 10 ]; then
echo "ALERT: $CE_COUNT CE errors in last hour" | mail -s "MCE Alert" ops@example.com
fi
# 3. journald 기반 실시간 감시
$ journalctl -k -f -g "mce:|EDAC|Hardware Error"
프로덕션 MCE 분석 사례
실제 데이터센터 운영에서 발생하는 MCE를 단계적으로 분석하고 대응하는 실전 사례입니다. 각 사례는 dmesg 로그에서 시작하여 근본 원인(Root Cause) 규명과 조치까지 다룹니다.
사례 1: DIMM 점진적 열화 — CE 누적에서 UCE까지
# 사례 1: DIMM 열화 진단 실전 명령어
# 1단계: DIMM별 CE 카운트 확인
$ sudo ras-mc-ctl --errors
Label CE count UE count
CPU_SrcID#0_MC#0_Chan#0_DIMM#0 847 0
CPU_SrcID#0_MC#0_Chan#1_DIMM#0 2 0
CPU_SrcID#0_MC#1_Chan#0_DIMM#0 0 0
# 2단계: 시간대별 CE 증가 추이 확인
$ sudo sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT date(timestamp,'unixepoch') as day,
COUNT(*) as ce_count
FROM mc_event
WHERE err_type='Corrected'
AND mc_location LIKE '%Chan#0_DIMM#0%'
GROUP BY day ORDER BY day DESC LIMIT 14;"
# 결과:
# 2026-04-04 203 ← 급증
# 2026-04-03 156
# 2026-04-02 87
# 2026-04-01 42
# 2026-03-31 15
# 2026-03-30 8
# ...점진적 증가 패턴 → 교체 필요
# 3단계: 에러 주소 패턴 확인 (동일 행 집중 여부)
$ sudo sqlite3 /var/lib/rasdaemon/ras-mc_event.db \
"SELECT printf('0x%x', address >> 12) as page,
COUNT(*) as hits
FROM mce_record
WHERE bank IN (13,14,15,16,17,18,19,20)
GROUP BY page
HAVING hits > 5
ORDER BY hits DESC LIMIT 10;"
# 4단계: edac-util로 DIMM 물리 위치 확인
$ edac-util -s
mc0: csrow0: ch0: 847 CEs, 0 UEs — DIMM A1 (Samsung M393A4K40DB3-CWE)
$ edac-util -l
mc0: 0: DIMM_A1: 32768 MB DDR4 2933 MHz, ECC: SECDED
# 5단계: DIMM 교체 요청 — BMC/IPMI 이벤트 확인
$ ipmitool sel list | grep -i "correctable\|memory"
1 | 04/01/2026 | Memory #0x01 | Correctable ECC | Asserted
사례 2: GPU PCIe 에러 → IIO MCE 연쇄 장애
AI 학습 클러스터에서 NVIDIA GPU의 VRAM ECC 에러가 PCIe Poisoned TLP를 통해 CPU의 IIO MCA 뱅크에 연쇄적으로 보고되는 실전 시나리오입니다.
# GPU 측 에러 (nvidia-smi 또는 dmesg)
[45678.123] NVRM: Xid (PCI:0000:3b:00): 48, pid=12345, name=python3,
DBE (double bit error) was detected in VRAM. GPU will fall off the bus.
# IIO MCA 뱅크 에러 (CPU 측)
[45678.124] mce: [Hardware Error]: CPU 8 Bank 6: bd200000000e000b
[45678.124] mce: [Hardware Error]: TSC abcdef1234567890
[45678.124] mce: [Hardware Error]: MISC 003b0000003b0000
[45678.124] mce: [Hardware Error]: PROCESSOR 0:806f8 TIME 1712234567 SOCKET 0 APIC 16
# PCIe AER 에러 (동일 이벤트의 AER 경로)
[45678.124] pcieport 0000:00:01.0: AER: Uncorrectable (Non-Fatal) error received: 0000:3b:00.0
[45678.124] pcieport 0000:00:01.0: AER: Poisoned TLP received
# 분석 절차
# 1단계: IIO MCi_STATUS 디코딩
# STATUS = 0xBD200000000E000B
# bit 63 VAL=1, bit 61 UC=1, bit 60 EN=1
# bit 59 MISCV=1, bit 58 ADDRV=0 (주소 없음 — I/O 에러)
# bit 57 PCC=0 → 프로세서 컨텍스트 정상
# MSCOD(31:16) = 0x000E → IIO 내부 에러
# MCA code(15:0) = 0x000B → 버스/인터커넥트 에러
# 2단계: MISC에서 BDF(Bus:Dev.Func) 추출
# MISC = 0x003B0000003B0000
# Bus = 0x3B, Dev = 0x00, Func = 0x00 → PCIe 3b:00.0
$ lspci -s 3b:00.0
3b:00.0 3D controller: NVIDIA Corporation A100 PCIe 80GB
# 3단계: GPU 에러 이력 확인
$ nvidia-smi -q -d ECC -i 0000:3b:00.0
ECC Errors
Volatile
SRAM Correctable : 0
SRAM Uncorrectable : 1 ← DBE!
DRAM Correctable : 12
DRAM Uncorrectable : 1 ← VRAM ECC UCE
# 4단계: 근본 원인 판단
# GPU VRAM DBE → Poisoned TLP → IIO MCE
# 원인: GPU VRAM 하드웨어 결함
# 조치: GPU 교체 + 학습 체크포인트에서 재시작
# 5단계: GPU 리셋 시도 (교체 전 임시 조치)
$ sudo sh -c "echo 1 > /sys/bus/pci/devices/0000:3b:00.0/reset"
# 또는 전체 노드 재부팅 후 GPU 상태 확인
$ nvidia-smi --gpu-reset -i 0000:3b:00.0
사례 3: 열(Thermal) 관련 MCE — 과열 CPU 보호
서버룸 냉각 장애로 CPU 온도가 임계점을 초과하면 CPU의 PCU(Power Control Unit) 뱅크에서 Thermal MCE가 발생합니다. 이 에러는 일반적으로 CE 등급이지만, 지속되면 CPU가 자발적으로 성능을 저하시키거나 최악의 경우 셧다운됩니다.
# 서버 dmesg — PCU 뱅크 Thermal MCE
[12345.678] mce: [Hardware Error]: CPU 0 Bank 4: 8c00000000000400
[12345.678] mce: [Hardware Error]: TSC 12345abcde ADDR 0000000000000000
[12345.678] mce: [Hardware Error]: PROCESSOR 0:806f8 TIME 1712345678 SOCKET 0
# STATUS = 0x8C00000000000400
# bit 63 VAL=1, bit 61 UC=0 → CE (교정됨)
# bit 60 EN=0, bit 59 MISCV=1
# Bank 4 = PCU (Power Control Unit)
# MCA code 0x0400 = Internal Timer Error
# → Thermal Throttle로 인한 PCU 내부 에러
# 동시에 나타나는 Thermal Throttle 메시지
[12345.679] CPU0: Core temperature above threshold, cpu clock throttled
[12345.679] CPU0: Package temperature above threshold, cpu clock throttled
# 진단 명령어
$ cat /sys/class/thermal/thermal_zone*/temp # CPU 온도 확인
$ turbostat --show Package,Core,CPU,Bzy_MHz,PkgTmp # 클럭/온도 실시간
$ ipmitool sdr | grep -i temp # BMC 온도 센서
# 조치: 냉각 시스템 점검, 팬 속도 확인, 서멀 페이스트 교체
사례 4: memory_failure() 복구 성공 — SRAR 에러 실전 흐름
사용자 프로세스가 ECC 2비트 오류가 발생한 메모리 페이지의 데이터를 읽으려 할 때 발생하는 SRAR(Software Recoverable Action Required) MCE의 전체 복구 흐름입니다.
사례 5: 간헐적 UPI 링크 에러 — 소켓 간 통신 장애
듀얼 소켓 서버에서 UPI(Ultra Path Interconnect) 링크의 간헐적 CRC 에러가 MCA Bank 5~6에 CE로 기록되다가, 링크 열화가 진행되면서 UC로 확대되는 사례입니다. 이 패턴은 DIMM 에러와 혼동하기 쉬우므로 뱅크 번호와 에러 코드로 구별해야 합니다.
# UPI CE 로그 (초기 단계)
[23456.789] mce: [Hardware Error]: CPU 0 Bank 5: 9c00000000000800
[23456.789] mce: [Hardware Error]: MISC 0000000000000000
# STATUS = 0x9C00000000000800
# VAL=1, UC=0(CE), EN=1, MISCV=1, ADDRV=1, PCC=0
# Bank 5 = UPI Link 0 (Skylake-SP 기준)
# MCA code 0x0800 = 버스/인터커넥트 에러
# → UPI 링크 CRC 에러 (교정됨)
# 진단 명령어
$ sudo rdmsr -p 0 0x415 # Bank 5 STATUS (0x401 + 4*5 = 0x415)
$ sudo lspci -vvv | grep -A5 "UPI"
$ ipmitool sel list | grep -i "QPI\|UPI"
# UPI CE가 누적되면 (UC 확대 전):
# 1. BIOS에서 UPI 링크 속도 다운그레이드 (10.4 GT/s → 9.6 GT/s)
# 2. CPU 소켓 재장착 (물리적 접촉 불량 배제)
# 3. 메인보드 또는 CPU 교체 검토
- Bank 0~3 (Core 뱅크): IFU/DCU/DTLB/MLC — CPU 코어 내부 에러
- Bank 4 (PCU): 전원/Thermal — 냉각/전원 공급 문제
- Bank 5~6 (UPI): 소켓 간 인터커넥트 — 물리 연결/보드 문제
- Bank 7~8 (IIO): PCIe 장치 — 슬롯/카드/케이블 문제
- Bank 9~12 (M2M): 메시→메모리 — 주소 디코드/스크러빙
- Bank 13~20 (iMC): 메모리 컨트롤러 — DIMM ECC 에러 (가장 흔함)
- Bank 21+ (CHA): LLC 슬라이스 — L3 캐시 에러
MCE 고급 커널 모듈 프로그래밍
커스텀 RAS 솔루션이나 하드웨어 모니터링 도구를 개발할 때, MCE notifier chain에 등록하여 실시간으로 MCE 이벤트를 수신하고 처리하는 방법을 설명합니다.
MCE Notifier Chain 등록 모듈
/* mce_monitor.c — MCE 이벤트 실시간 모니터링 커널 모듈 */
#include <linux/module.h>
#include <linux/notifier.h>
#include <asm/mce.h>
static atomic_t ce_count = ATOMIC_INIT(0);
static atomic_t uc_count = ATOMIC_INIT(0);
static int mce_event_handler(struct notifier_block *nb,
unsigned long val, void *data)
{
struct mce *m = (struct mce *)data;
if (!(m->status & MCI_STATUS_VAL))
return NOTIFY_DONE;
if (m->status & MCI_STATUS_UC) {
atomic_inc(&uc_count);
pr_warn("MCE Monitor: UC 에러! CPU=%d Bank=%d STATUS=0x%016llx ADDR=0x%016llx\n",
m->extcpu, m->bank, m->status, m->addr);
if (m->status & MCI_STATUS_PCC)
pr_emerg("MCE Monitor: PCC=1! 프로세서 컨텍스트 손상!\n");
} else {
atomic_inc(&ce_count);
/* CE는 통계만 기록 (로그 폭주 방지) */
if ((atomic_read(&ce_count) % 100) == 0)
pr_info("MCE Monitor: CE 누적 %d건 (Bank=%d)\n",
atomic_read(&ce_count), m->bank);
}
return NOTIFY_OK;
}
static struct notifier_block mce_nb = {
.notifier_call = mce_event_handler,
.priority = MCE_PRIO_LOWEST, /* 다른 핸들러 처리 후 실행 */
};
static int __init mce_monitor_init(void)
{
mce_register_decode_chain(&mce_nb);
pr_info("MCE Monitor: x86_mce_decoder_chain에 등록 완료\n");
return 0;
}
static void __exit mce_monitor_exit(void)
{
mce_unregister_decode_chain(&mce_nb);
pr_info("MCE Monitor: 해제. 총 CE=%d, UC=%d\n",
atomic_read(&ce_count), atomic_read(&uc_count));
}
module_init(mce_monitor_init);
module_exit(mce_monitor_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Real-time MCE Event Monitor via Notifier Chain");
eBPF를 활용한 MCE 실시간 추적
bpftrace를 사용하면 커널 모듈 없이도 MCE tracepoint를 실시간으로 추적하고,
커스텀 필터링과 집계를 수행할 수 있습니다.
# bpftrace를 사용한 MCE 실시간 모니터링
# 1. 모든 MCE 이벤트 실시간 출력
$ sudo bpftrace -e '
tracepoint:mce:mce_record {
printf("MCE: CPU=%d Bank=%d STATUS=0x%llx ADDR=0x%llx severity=%s\n",
args->cpu, args->bank, args->status, args->addr,
(args->status >> 61) & 1 ? "UC" : "CE");
}'
# 2. 뱅크별 MCE 빈도 히스토그램 (10초간 수집)
$ sudo bpftrace -e '
tracepoint:mce:mce_record {
@bank_hist[args->bank] = count();
}
interval:s:10 { exit(); }'
# 3. UC 에러만 필터링하여 PFN 추출
$ sudo bpftrace -e '
tracepoint:mce:mce_record
/(args->status >> 61) & 1/
{
$pfn = args->addr >> 12;
printf("UC MCE! CPU=%d Bank=%d PFN=0x%lx\n",
args->cpu, args->bank, $pfn);
@uc_pages[$pfn] = count();
}'
# 4. memory_failure() 호출 추적 (HWPoison 복구 모니터링)
$ sudo bpftrace -e '
kprobe:memory_failure {
printf("memory_failure: pfn=0x%lx flags=0x%x\n", arg0, arg1);
}
kretprobe:memory_failure {
printf(" → 결과: %d (%s)\n", retval,
retval == 0 ? "성공" : "실패");
}'
# 5. DIMM별 CE 누적 카운트 (장기 모니터링용)
$ sudo bpftrace -e '
tracepoint:mce:mce_record
/!((args->status >> 61) & 1)/ /* CE만 */
{
@ce_by_bank[args->bank] = count();
@ce_by_cpu[args->cpu] = count();
}
interval:m:5 {
printf("--- 5분 CE 요약 ---\n");
print(@ce_by_bank);
print(@ce_by_cpu);
}'
rasdaemon과 병행하여 커스텀 분석에 활용할 수 있습니다.
관련 문서
MCE는 하드웨어 에러 처리의 핵심 인프라로, 커널의 여러 서브시스템과 깊이 연관됩니다. 아래 문서들은 MCE의 다양한 측면을 이해하는 데 도움이 됩니다.
- 프로세서 공식 사양
- Intel SDM Vol. 3B, Chapter 15–16 "Machine-Check Architecture" — MCA 뱅크, 에러 코드, 복구 모델의 공식 사양입니다
- AMD BKDG/PPR (Processor Programming Reference) — AMD Scalable MCA(SMCA) 뱅크 타입, IPID, SYND 레지스터 사양입니다
- UEFI Specification, Appendix N "Common Platform Error Record" — CPER 포맷 정의입니다
- ACPI Specification, Chapter 18 "ACPI Platform Error Interfaces (APEI)" — HEST/BERT/ERST/EINJ 사양입니다
- 커널 공식 문서
- Machine Check Exception — The Linux Kernel documentation — x86 MCE 서브시스템의 공식 커널 문서입니다
- APEI Error Injection (EINJ) — The Linux Kernel documentation — APEI EINJ를 통한 하드웨어 에러 주입 방법을 설명합니다
- Reliability, Availability and Serviceability (RAS) — The Linux Kernel documentation — 리눅스 RAS 프레임워크의 전체 구조를 설명합니다
- EDAC — Error Detection And Correction — The Linux Kernel documentation — EDAC 드라이버 API와 메모리 에러 보고 체계를 설명합니다
- Kernel Parameters — The Linux Kernel documentation —
mce=,edac_report=등 MCE/EDAC 관련 부트 파라미터를 확인할 수 있습니다 - 커널 소스 코드 (Elixir Cross Referencer)
- arch/x86/kernel/cpu/mce/ — MCE 핵심 구현 코드입니다 (
core.c,severity.c,intel.c,amd.c등) - mm/memory-failure.c — HWPoison 기반 메모리 페이지 오프라인 처리 코드입니다
- drivers/edac/ — EDAC 드라이버 디렉터리입니다 (
edac_mc.c,skx_edac.c,amd64_edac.c등) - drivers/acpi/apei/ — APEI 구현 코드입니다 (
ghes.c,einj.c,erst.c등) - drivers/ras/cec.c — Corrected Errors Collector 구현 코드로, CE 패턴 기반 페이지 오프라인 결정 로직입니다
- include/linux/ras.h — RAS 프레임워크 헤더로, 트레이스포인트 및 CEC 인터페이스를 정의합니다
- LWN.net 기사
- Machine check handling on Linux (LWN, 2007) — 리눅스 MCE 처리의 기초를 설명하는 초기 심층 기사입니다
- Memory error handling in the kernel (LWN, 2009) — 메모리 에러 감지와 HWPoison 메커니즘의 도입 배경을 다룹니다
- Machine-check exception recovery (LWN, 2015) — UCR(Uncorrected Recoverable) 에러에서의 복구 전략을 설명합니다
- Correctable memory errors should not cause kernel panics (LWN, 2019) — CE 임계값과 커널 패닉 정책에 대한 논의입니다
- Copy-on-write poison recovery (LWN, 2022) — CoW 시 poison 페이지 전파를 방지하는 복구 기법을 다룹니다
- mcelog 및 rasdaemon
- mcelog — Machine Check Event Logger — x86 MCE 이벤트를 사용자 공간(User Space)에서 수집·분석하는 전통적인 도구입니다
- rasdaemon — RAS Error Logging Daemon — 커널 트레이스포인트 기반으로 MCE, EDAC, PCIe AER 등 하드웨어 에러를 기록하는 최신 도구입니다
- 컨퍼런스 발표 및 기술 문서
- Tony Luck — "Linux Kernel Machine Check Recovery" (Linux Plumbers Conference 2019) — Intel MCE 유지보수자의 복구 아키텍처 발표입니다
- Andi Kleen — "Dealing with Machine Check Exceptions in the Linux Kernel" (OLS 2006) — 리눅스 MCE 서브시스템 설계 원칙을 다룬 초기 논문입니다
- Borislav Petkov — "RAS in Linux" (LinuxCon 2018) — 리눅스 RAS 스택 전체 구조와 최신 개선 사항을 설명합니다
- Intel Machine Check Architecture Technical Reference — Intel MCA 에러 코드 디코딩 레퍼런스입니다
- "Linux Memory Error Handling Mechanisms and Recent Progress" (SNIA, 2023) — 메모리 에러 핸들링의 최근 발전 동향을 종합적으로 다룹니다