Linux 커널의 하드웨어 오류 주입 메커니즘은 실제 메모리 하드웨어 오류(MCE: Machine Check Exception)를 시뮬레이션하여 테스트하는 기능입니다. hwpoison-inject.c 모듈은 debugfs 인터페이스를 통해 임의의 페이지 프레임 번호(PFN)에 하드웨어 오류를 주입할 수 있게 해주며, 이는 주로 메모리 오류 복구 시스템의 테스트 및 디버깅에 사용됩니다.
이 메커니즘은 실제 하드웨어 오류가 발생했을 때 커널이 어떻게 대응하는지 확인할 수 있는 중요한 도구입니다. memory_failure() 함수는 실제 하드웨어 오류 처리의 핵심으로, 오류가 발생한 페이지를 복구하거나 관련 프로세스를 종료하는 등의 동작을 수행합니다.
corrupt-pfn과 unpoison-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"
static u32 hwpoison_filter_enable;
// 하드웨어 오류 필터 활성화 상태
// 0: 필터 비활성화 (모든 페이지에 오류 주입)
// 1: 필터 활성화 (지정된 조건의 페이지에만 오류 주입)
static u32 hwpoison_filter_dev_major = ~0U;
static u32 hwpoison_filter_dev_minor = ~0U;
// 디바이스 번호 필터 (메이저/마이너 번호)
// ~0U: 필터 미설정 (모든 디바이스 허용)
static u64 hwpoison_filter_flags_mask;
static u64 hwpoison_filter_flags_value;
// 페이지 플래그 필터
// mask: 검사할 플래그 마스크
// value: 일치해야 하는 플래그 값
static u64 hwpoison_filter_memcg;
// 메모리 cgroup 필터
// 0: 필터 미설정 (모든 cgroup 허용)
// non-zero: 지정된 cgroup의 페이지에만 오류 주입
PG_hwpoison, /* 하드웨어로 손상된 페이지. 손대지 말 것 */
// 페이지 플래그 중 하나로, 하드웨어 오류가 발생한 페이지에 설정됨
PG_has_hwpoisoned = PG_active,
// 복합 페이지(THP 등) 내에 hwpoison된 서브페이지가 하나 이상 있음을 나타냄
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: 소프트웨어 시뮬레이션 플래그
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: 필터 실패 (오류 주입 거부)
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;
}
static int hwpoison_unpoison(void *data, u64 val)
{
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
return unpoison_memory(val);
}
// hwpoison 상태를 해제하는 함수
// val: 해제할 페이지 프레임 번호
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 검사
memory_failure()는 현재 페이지 플래그를 먼저 확인하고, 분류가 충분하지 않으면 저장해 둔 page_flags 스냅샷으로 다시 판정합니다.
| 조건 | hwpoison_inject | memory_failure | 비고 |
|---|---|---|---|
| 트리거 | debugfs 쓰기 | 하드웨어 MCE | 소프트웨어 vs 하드웨어 |
| 권한 | CAP_SYS_ADMIN | 커널 내부 | 사용자 공간 vs 커널 공간 |
| 대상 | 임의 PFN | 오류 감지된 페이지 | 선택적 vs 자동 |
| 플래그 | MF_SW_SIMULATED | MF_ACTION_REQUIRED 등 | 시뮬레이션 vs 실제 |
| 필터 타입 | 설정 파일 | 동작 | 비고 |
|---|---|---|---|
| 디바이스 | corrupt-filter-dev-major/minor | 특정 디바이스의 페이지만 오류 주입 | 파일 시스템 관련 |
| 플래그 | corrupt-filter-flags-mask/value | 특정 플래그를 가진 페이지만 오류 주입 | 페이지 상태 기반 |
| 메모리 cgroup | corrupt-filter-memcg | 특정 cgroup의 페이지만 오류 주입 | 프로세스 격리 |
| 전체 | corrupt-filter-enable | 필터 전체 활성화/비활성화 | 스위치 역할 |
| 페이지 상태 | 처리 방식 | 함수 | 비고 |
|---|---|---|---|
| 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() | 빈 페이지 처리 |
# 시나리오 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
# 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
# CONFIG_MEMORY_FAILURE: 하드웨어 오류 처리 활성화
# CONFIG_HWPOISON_INJECT: hwpoison 주입 모듈 활성화
# CONFIG_MCE: Machine Check Exception 지원
# CONFIG_X86_MCE: x86 MCE 지원