Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

하드웨어 오류 주입 (HWPoison)

개요 (Overview)

Linux 커널의 하드웨어 오류 주입 메커니즘은 실제 메모리 하드웨어 오류(MCE: Machine Check Exception)를 시뮬레이션하여 테스트하는 기능입니다. hwpoison-inject.c 모듈은 debugfs 인터페이스를 통해 임의의 페이지 프레임 번호(PFN)에 하드웨어 오류를 주입할 수 있게 해주며, 이는 주로 메모리 오류 복구 시스템의 테스트 및 디버깅에 사용됩니다.

이 메커니즘은 실제 하드웨어 오류가 발생했을 때 커널이 어떻게 대응하는지 확인할 수 있는 중요한 도구입니다. memory_failure() 함수는 실제 하드웨어 오류 처리의 핵심으로, 오류가 발생한 페이지를 복구하거나 관련 프로세스를 종료하는 등의 동작을 수행합니다.

corrupt-pfnunpoison-pfn은 하드웨어 상태를 직접 바꾸지 않는 소프트웨어 수준 인터페이스라서, 장치 오류를 건드리지 않고도 주입과 해제를 반복해 볼 수 있습니다.

소스 파일 경로:
├── mm/hwpoison-inject.c      ← 하드웨어 오류 주입 모듈
├── mm/memory-failure.c       ← 실제 오류 처리 코어
├── mm/internal.h             ← 내부 함수 선언
└── include/linux/page-flags.h ← PG_hwpoison 플래그 정의

빠른 점검 명령

# hwpoison 모듈 상태 확인
lsmod | grep hwpoison

# debugfs hwpoison 디렉토리 확인
ls -la /sys/kernel/debug/hwpoison/

# 현재 필터 설정값 확인
cat /sys/kernel/debug/hwpoison/corrupt-filter-enable
cat /sys/kernel/debug/hwpoison/corrupt-filter-dev-major
cat /sys/kernel/debug/hwpoison/corrupt-filter-dev-minor
cat /sys/kernel/debug/hwpoison/corrupt-filter-flags-mask
cat /sys/kernel/debug/hwpoison/corrupt-filter-flags-value
cat /sys/kernel/debug/hwpoison/corrupt-filter-memcg

# 하드웨어 오류 주입 (PFN 0x12345에 오류 주입)
echo 0x12345 > /sys/kernel/debug/hwpoison/corrupt-pfn

# 오류 주입 필터 활성화
echo 1 > /sys/kernel/debug/hwpoison/corrupt-filter-enable

# 디바이스 필터 설정 (메이저 번호 8)
echo 8 > /sys/kernel/debug/hwpoison/corrupt-filter-dev-major

# 메모리 그룹 필터 설정
echo 12345 > /sys/kernel/debug/hwpoison/corrupt-filter-memcg

# 오류 해제 (PFN 0x12345의 오류 해제)
echo 0x12345 > /sys/kernel/debug/hwpoison/unpoison-pfn

# 메모리 오류 이력 확인
dmesg | grep -i "memory failure"

# hwpoison 페이지 수 확인
cat /proc/meminfo | grep -i hwpoison

# 실제 하드웨어 오류 로그 확인
dmesg | grep -i "mce\|machine check"

핵심 자료구조

1. `hwpoison_filter_enable` (mm/hwpoison-inject.c:14)

static u32 hwpoison_filter_enable;
// 하드웨어 오류 필터 활성화 상태
// 0: 필터 비활성화 (모든 페이지에 오류 주입)
// 1: 필터 활성화 (지정된 조건의 페이지에만 오류 주입)

2. `hwpoison_filter_dev_major/minor` (mm/hwpoison-inject.c:15-16)

static u32 hwpoison_filter_dev_major = ~0U;
static u32 hwpoison_filter_dev_minor = ~0U;
// 디바이스 번호 필터 (메이저/마이너 번호)
// ~0U: 필터 미설정 (모든 디바이스 허용)

3. `hwpoison_filter_flags_mask/value` (mm/hwpoison-inject.c:17-18)

static u64 hwpoison_filter_flags_mask;
static u64 hwpoison_filter_flags_value;
// 페이지 플래그 필터
// mask: 검사할 플래그 마스크
// value: 일치해야 하는 플래그 값

4. `hwpoison_filter_memcg` (mm/hwpoison-inject.c:68)

static u64 hwpoison_filter_memcg;
// 메모리 cgroup 필터
// 0: 필터 미설정 (모든 cgroup 허용)
// non-zero: 지정된 cgroup의 페이지에만 오류 주입

5. `PG_hwpoison` (include/linux/page-flags.h:118)

PG_hwpoison,        /* 하드웨어로 손상된 페이지. 손대지 말 것 */
// 페이지 플래그 중 하나로, 하드웨어 오류가 발생한 페이지에 설정됨

6. `PG_has_hwpoisoned` (include/linux/page-flags.h:192)

PG_has_hwpoisoned = PG_active,
// 복합 페이지(THP 등) 내에 hwpoison된 서브페이지가 하나 이상 있음을 나타냄

핵심 함수

1. `hwpoison_inject()` (mm/hwpoison-inject.c:102-142)

static int hwpoison_inject(void *data, u64 val)
{
    unsigned long pfn = val;
    struct page *p;
    struct folio *folio;
    int err;

    if (!capable(CAP_SYS_ADMIN))
        return -EPERM;

    if (!pfn_valid(pfn))
        return -ENXIO;

    p = pfn_to_page(pfn);
    folio = page_folio(p);

    if (!hwpoison_filter_enable)
        goto inject;

    shake_folio(folio);
    /* 비-LRU 페이지는 free page를 제외하고 지원하지 않는다. */
    if (!folio_test_lru(folio) && !folio_test_hugetlb(folio) &&
        !is_free_buddy_page(p))
        return 0;

    /* 대상 소유자만 PG_hwpoison이 잡히도록 먼저 느슨한 검사를 한다. */
    err = hwpoison_filter(&folio->page);
    if (err)
        return 0;

inject:
    pr_info("Injecting memory failure at pfn %#lx\n", pfn);
    err = memory_failure(pfn, MF_SW_SIMULATED);
    return (err == -EOPNOTSUPP) ? 0 : err;
}
// debugfs를 통해 하드웨어 오류를 주입하는 함수
// PFN: 주입할 페이지 프레임 번호
// MF_SW_SIMULATED: 소프트웨어 시뮬레이션 플래그

2. `hwpoison_filter()` (mm/hwpoison-inject.c:83-98)

static int hwpoison_filter(struct page *p)
{
    if (!hwpoison_filter_enable)
        return 0;

    if (hwpoison_filter_dev(p))
        return -EINVAL;

    if (hwpoison_filter_flags(p))
        return -EINVAL;

    if (hwpoison_filter_task(p))
        return -EINVAL;

    return 0;
}
// 페이지가 설정된 필터 조건을 만족하는지 검사
// 0: 필터 통과 (오류 주입 허용)
// -EINVAL: 필터 실패 (오류 주입 거부)

3. `memory_failure()` (mm/memory-failure.c:2388-2558)

try_memory_failure_hugetlb()는 HugeTLB를 먼저 분기하고, take_page_off_buddy()는 free buddy page를 별도로 처리합니다. 일반 페이지는 hwpoison_user_mappings()로 매핑을 정리한 뒤 identify_page_state()로 최종 판정을 내립니다.

try_again:
    res = try_memory_failure_hugetlb(pfn, flags, &hugetlb);
    if (hugetlb)
        goto unlock_mutex;

    if (TestSetPageHWPoison(p)) {
        res = -EHWPOISON;
        if (flags & MF_ACTION_REQUIRED)
            res = kill_accessing_process(current, pfn, flags);
        if (flags & MF_COUNT_INCREASED)
            put_page(p);
        action_result(pfn, MF_MSG_ALREADY_POISONED, MF_FAILED);
        goto unlock_mutex;
    }

    res = get_hwpoison_page(p, flags);
    if (!res) {
        if (is_free_buddy_page(p)) {
            if (take_page_off_buddy(p)) {
                page_ref_inc(p);
                res = MF_RECOVERED;
            } else {
                if (retry) {
                    ClearPageHWPoison(p);
                    retry = false;
                    goto try_again;
                }
                res = MF_FAILED;
            }
            res = action_result(pfn, MF_MSG_BUDDY, res);
        } else {
            res = action_result(pfn, MF_MSG_KERNEL_HIGH_ORDER, MF_IGNORED);
        }
        goto unlock_mutex;
    } else if (res < 0) {
        res = action_result(pfn, MF_MSG_GET_HWPOISON, MF_IGNORED);
        goto unlock_mutex;
    }

    folio = page_folio(p);

    folio_lock(folio);
    if (hwpoison_filter(p)) {
        ClearPageHWPoison(p);
        folio_unlock(folio);
        folio_put(folio);
        res = -EOPNOTSUPP;
        goto unlock_mutex;
    }
    folio_unlock(folio);

    if (folio_test_large(folio)) {
        const int new_order = min_order_for_split(folio);
        int err;

        folio_set_has_hwpoisoned(folio);
        err = try_to_split_thp_page(p, new_order, false);
        if (err || new_order) {
            folio = page_folio(p);
            res = -EHWPOISON;
            kill_procs_now(p, pfn, flags, folio);
            put_page(p);
            action_result(pfn, MF_MSG_UNSPLIT_THP, MF_FAILED);
            goto unlock_mutex;
        }
        VM_BUG_ON_PAGE(!page_count(p), p);
        folio = page_folio(p);
    }

    shake_folio(folio);

    folio_lock(folio);

    WARN_ON(folio_test_large(folio));

    page_flags = folio->flags.f;

    if (!folio_test_lru(folio) && !folio_test_writeback(folio))
        goto identify_page_state;

    folio_wait_writeback(folio);

    if (!hwpoison_user_mappings(folio, p, pfn, flags)) {
        res = action_result(pfn, MF_MSG_UNMAP_FAILED, MF_FAILED);
        goto unlock_page;
    }

    if (folio_test_lru(folio) && !folio_test_swapcache(folio) &&
        folio->mapping == NULL) {
        res = action_result(pfn, MF_MSG_TRUNCATED_LRU, MF_IGNORED);
        goto unlock_page;
    }

identify_page_state:
    res = identify_page_state(pfn, p, page_flags);
    mutex_unlock(&mf_mutex);
    return res;
unlock_page:
    folio_unlock(folio);
unlock_mutex:
    mutex_unlock(&mf_mutex);
    return res;
}

4. `hwpoison_unpoison()` (mm/hwpoison-inject.c:144-150)

static int hwpoison_unpoison(void *data, u64 val)
{
    if (!capable(CAP_SYS_ADMIN))
        return -EPERM;

    return unpoison_memory(val);
}
// hwpoison 상태를 해제하는 함수
// val: 해제할 페이지 프레임 번호

5. `pfn_inject_init()` (mm/hwpoison-inject.c:162-200)

static int __init pfn_inject_init(void)
{
    hwpoison_dir = debugfs_create_dir("hwpoison", NULL);

    /* 아래 poison/unpoison 인터페이스는 하드웨어 상태를 직접 바꾸지 않는다. */
    /* 하드웨어 지원이 없어도 소프트웨어 수준 테스트에 사용할 수 있다. */
    debugfs_create_file("corrupt-pfn", 0200, hwpoison_dir, NULL,
                        &hwpoison_fops);

    debugfs_create_file("unpoison-pfn", 0200, hwpoison_dir, NULL,
                        &unpoison_fops);

    debugfs_create_u32("corrupt-filter-enable", 0600, hwpoison_dir,
                       &hwpoison_filter_enable);

    debugfs_create_u32("corrupt-filter-dev-major", 0600, hwpoison_dir,
                       &hwpoison_filter_dev_major);

    debugfs_create_u32("corrupt-filter-dev-minor", 0600, hwpoison_dir,
                       &hwpoison_filter_dev_minor);

    debugfs_create_u64("corrupt-filter-flags-mask", 0600, hwpoison_dir,
                       &hwpoison_filter_flags_mask);

    debugfs_create_u64("corrupt-filter-flags-value", 0600, hwpoison_dir,
                       &hwpoison_filter_flags_value);

#ifdef CONFIG_MEMCG
    debugfs_create_u64("corrupt-filter-memcg", 0600, hwpoison_dir,
                       &hwpoison_filter_memcg);
#endif

    hwpoison_filter_register(hwpoison_filter);

    return 0;
}
// 모듈 초기화 함수 - debugfs 인터페이스 생성

호출 흐름

1. hwpoison_inject() ← debugfs 쓰기 트리거
   ├── capable(CAP_SYS_ADMIN) 확인
   ├── pfn_valid(pfn) 확인
   ├── pfn_to_page(pfn) → 페이지 변환
   ├── hwpoison_filter_enable 확인
   │   ├── shake_folio(folio) → 페이지 캐시에서 제거
   │   ├── folio_test_lru()/folio_test_hugetlb()/is_free_buddy_page() 검사
   │   └── hwpoison_filter() → 필터 조건 검사
   └── memory_failure(pfn, MF_SW_SIMULATED) → 실제 오류 처리

2. memory_failure() ← 실제 오류 처리
   ├── mutex_lock(&mf_mutex) → 전역 뮤텍스 획득
   ├── pfn_to_online_page(pfn) → 온라인 페이지 확인
   ├── try_memory_failure_hugetlb() → HugeTLB 페이지 처리
   ├── TestSetPageHWPoison(p) → hwpoison 플래그 설정
   ├── get_hwpoison_page() → 페이지 참조 카운트 증가
   ├── hwpoison_filter(p) → 필터 재검사
   ├── folio_test_large(folio) → THP 분할 처리
   ├── shake_folio(folio) → 페이지 캐시에서 제거
   ├── hwpoison_user_mappings() → 사용자 매핑 해제
   └── action_result() → 결과 기록

3. hwpoison_filter() ← 필터 체크
   ├── hwpoison_filter_dev(p) → 디바이스 번호 검사
   ├── hwpoison_filter_flags(p) → 페이지 플래그 검사
   └── hwpoison_filter_task(p) → 메모리 cgroup 검사
HWPoison 호출 흐름

memory_failure()는 현재 페이지 플래그를 먼저 확인하고, 분류가 충분하지 않으면 저장해 둔 page_flags 스냅샷으로 다시 판정합니다.


조건별 비교

1. hwpoison 주입 경로 비교

조건hwpoison_injectmemory_failure비고
트리거debugfs 쓰기하드웨어 MCE소프트웨어 vs 하드웨어
권한CAP_SYS_ADMIN커널 내부사용자 공간 vs 커널 공간
대상임의 PFN오류 감지된 페이지선택적 vs 자동
플래그MF_SW_SIMULATEDMF_ACTION_REQUIRED 등시뮬레이션 vs 실제

2. 필터 동작 비교

필터 타입설정 파일동작비고
디바이스corrupt-filter-dev-major/minor특정 디바이스의 페이지만 오류 주입파일 시스템 관련
플래그corrupt-filter-flags-mask/value특정 플래그를 가진 페이지만 오류 주입페이지 상태 기반
메모리 cgroupcorrupt-filter-memcg특정 cgroup의 페이지만 오류 주입프로세스 격리
전체corrupt-filter-enable필터 전체 활성화/비활성화스위치 역할

3. 페이지 상태별 처리

페이지 상태처리 방식함수비고
LRU 페이지hwpoison 처리hwpoison_user_mappings()일반 메모리 페이지
HugeTLB 페이지특수 처리try_memory_failure_hugetlb()Huge Page 지원
비-LRU 고차 페이지오류 무시action_result(MF_MSG_KERNEL_HIGH_ORDER, MF_IGNORED)커널이 직접 쓰는 페이지
free 페이지buddy에서 제거take_page_off_buddy()빈 페이지 처리

관련 문서

  • 27-debug.html — 디버그 도구 (KASAN, KFENCE, Kmemleak)
  • 08-oom.html — OOM Killer
  • 05-page_reclaim.html — 페이지 회수
  • 22-memory_hotplug.html — 메모리 핫플러그

  • 참고 사항

    1. hwpoison 주입 시나리오

    # 시나리오 1: 특정 페이지에 오류 주입
    echo 0x100000 > /sys/kernel/debug/hwpoison/corrupt-pfn
    
    # 시나리오 2: 디바이스 필터와 함께 오류 주입
    echo 1 > /sys/kernel/debug/hwpoison/corrupt-filter-enable
    echo 8 > /sys/kernel/debug/hwpoison/corrupt-filter-dev-major
    echo 0x100000 > /sys/kernel/debug/hwpoison/corrupt-pfn
    
    # 시나리오 3: 메모리 cgroup 필터와 함께 오류 주입
    echo 1 > /sys/kernel/debug/hwpoison/corrupt-filter-enable
    echo 12345 > /sys/kernel/debug/hwpoison/corrupt-filter-memcg
    echo 0x100000 > /sys/kernel/debug/hwpoison/corrupt-pfn

    2. hwpoison 디버깅 팁

    # hwpoison 관련 로그 상세 확인
    echo 8 > /proc/sys/kernel/printk
    dmesg -w | grep -i "memory failure\|hwpoison\|mce"
    
    # 페이지 플래그 확인 (hwpoison 상태 확인)
    cat /proc/kpageflags | grep -i hwpoison
    
    # 메모리 오류 이력 확인
    cat /sys/devices/system/memory/soft_offline_page
    
    # hwpoison 페이지 수 확인
    cat /proc/vmstat | grep -i hwpoison

    3. hwpoison과 관련된 커널 컨피그 옵션

    # CONFIG_MEMORY_FAILURE: 하드웨어 오류 처리 활성화
    # CONFIG_HWPOISON_INJECT: hwpoison 주입 모듈 활성화
    # CONFIG_MCE: Machine Check Exception 지원
    # CONFIG_X86_MCE: x86 MCE 지원