페이지 테이블 검증 (Page Table Check)

개요 (Overview)

Linux 커널의 페이지 테이블 검증(Page Table Check)은 사용자 페이지 테이블에 대한 잘못된 매핑을 실시간으로 감지하는 디버깅 메커니즘입니다. 이 기능은 anonymous 페이지의 이중 쓰기 매핑(Double Write Mapping)과 anonymous/file 페이지의 부당한 공유를 탐지하여, 메모리 누수, 데이터 손상, 무결성 문제를 동기적으로 발견합니다.

페이지 테이블 검증은 page_table_check.c에서 구현되며, 페이지 테이블 엔트리(PTE/PMD/PUD)가 설정되거나 해제될 때마다 호출되어 카운터 기반의 일관성 검사를 수행합니다. 이 메커니즘은 CONFIG_PAGE_TABLE_CHECK 커널 컨피그 옵션으로 제어되며, x86_64, ARM64, RISC-V, PowerPC 아키텍처를 지원합니다. page_table_check=on 부트 파라미터 또는 CONFIG_PAGE_TABLE_CHECK_ENFORCED 옵션으로 활성화할 수 있습니다.

소스 파일 경로:
├── mm/page_table_check.c           ← 검증 코어 로직
├── include/linux/page_table_check.h ← 외부 인터페이스
├── include/linux/page_ext.h         ← 페이지 확장 프레임워크
├── include/linux/leafops.h          ← 소프트 리프 엔트리 연산
└── mm/Kconfig.debug                 ← 커널 컨피그 옵션

빠른 점검 명령

# 페이지 테이블 검증 활성화 상태 확인
cat /proc/cmdline | grep page_table_check

# 커널 컨피그에서 PAGE_TABLE_CHECK 확인
grep PAGE_TABLE_CHECK /boot/config-$(uname -r)

# 페이지 테이블 검증 관련 커널 로그 확인
dmesg | grep -i "page_table_check"

# page_ext 메모리 사용량 확인
cat /proc/meminfo | grep -i "page_tables"

# 페이지 테이블 관련 통계 확인
cat /proc/vmstat | grep -i "pgfault\|pgmajfault"

# 페이지 테이블 검증 활성화 (부트 파라미터)
# GRUB 설정에 추가: page_table_check=on

# 현재 페이지 테이블 검증 상태 확인 (커널 빌드 시)
grep CONFIG_PAGE_TABLE_CHECK /boot/config-$(uname -r)

# 페이지 확장 메모리 사용량 확인
cat /sys/kernel/debug/page_ext/

# 페이지 테이블 엔트리 수 확인
cat /proc/vmstat | grep -i "nr_pte\|nr_pmd\|nr_pud"

핵심 자료구조

1. `struct page_table_check` (mm/page_table_check.c:16)

struct page_table_check {
	atomic_t anon_map_count;   // anonymous 페이지 매핑 카운터
	atomic_t file_map_count;   // 파일 기반 페이지 매핑 카운터
};
// 각 페이지마다 page_ext에 연결되어 관리됨
// anon과 file은 서로 배타적이어야 함 (동시 증가 시 BUG_ON)

2. `struct page_ext_operations` (include/linux/page_ext.h:25)

struct page_ext_operations {
	size_t offset;             // page_ext 내 클라이언트 데이터 오프셋
	size_t size;               // 클라이언트 데이터 크기
	bool (*need)(void);        // page_ext 필요 여부 함수 포인터
	void (*init)(void);        // 초기화 함수 포인터 (선택)
	bool need_shared_flags;    // 공유 플래그 필드 사용 여부
};
// page_table_check_ops는 이 구조체를 통해 page_ext 프레임워크에 등록됨

3. `struct page_ext` (include/linux/page_ext.h:52)

struct page_ext {
	unsigned long flags;       // 페이지 확장 플래그
};
// 모든 페이지에 대해 page_ext가 할당되며, pfn에 대응됨
// page_table_check는 이 구조체의 확장 데이터로 저장됨

4. `struct page_ext_iter` (include/linux/page_ext.h:113)

struct page_ext_iter {
	unsigned long index;       // 현재 순회 인덱스
	unsigned long start_pfn;   // 시작 페이지 프레임 번호
	struct page_ext *page_ext; // 현재 page_ext 포인터
};
// for_each_page_ext 매크로에서 사용되는 이터레이터

5. `page_table_check_disabled` (mm/page_table_check.c:24)

DEFINE_STATIC_KEY_TRUE(page_table_check_disabled);
// 정적 키 기반 분기 최적화
// 기본값: true (검증 비활성화)
// init_page_table_check()에서 false로 변경 시 검증 활성화

6. `__page_table_check_enabled` (mm/page_table_check.c:21)

static bool __page_table_check_enabled __initdata =
				IS_ENABLED(CONFIG_PAGE_TABLE_CHECK_ENFORCED);
// 커널 부팅 시 초기화되는 활성화 플래그
// CONFIG_PAGE_TABLE_CHECK_ENFORCED: 항상 활성화
// page_table_check=on 부트 파라미터: 선택적 활성화

핵심 함수

1. `page_table_check_clear()` (mm/page_table_check.c:63)

static void page_table_check_clear(unsigned long pfn, unsigned long pgcnt)
{
	struct page_ext_iter iter;
	struct page_ext *page_ext;
	struct page *page;
	bool anon;

	if (!pfn_valid(pfn))
		return;

	page = pfn_to_page(pfn);
	BUG_ON(PageSlab(page));          // Slab 페이지는 검증 대상 아님
	anon = PageAnon(page);           // anonymous/file 구분

	rcu_read_lock();
	for_each_page_ext(page, pgcnt, page_ext, iter) {
		struct page_table_check *ptc = get_page_table_check(page_ext);

		if (anon) {
			BUG_ON(atomic_read(&ptc->file_map_count));      // anonymous인데 file 카운터 존재 시 BUG
			BUG_ON(atomic_dec_return(&ptc->anon_map_count) < 0); // 음수 카운터 시 BUG
		} else {
			BUG_ON(atomic_read(&ptc->anon_map_count));      // file인데 anon 카운터 존재 시 BUG
			BUG_ON(atomic_dec_return(&ptc->file_map_count) < 0); // 음수 카운터 시 BUG
		}
	}
	rcu_read_unlock();
}
// 역할: PTE/PMD/PUD 제거 시 해당 페이지의 매핑 카운터를 감소시키고 검증
// 호출자: __page_table_check_pte_clear, __page_table_check_pmd_clear, __page_table_check_pud_clear

2. `page_table_check_set()` (mm/page_table_check.c:97)

static void page_table_check_set(unsigned long pfn, unsigned long pgcnt,
				 bool rw)
{
	struct page_ext_iter iter;
	struct page_ext *page_ext;
	struct page *page;
	bool anon;

	if (!pfn_valid(pfn))
		return;

	page = pfn_to_page(pfn);
	BUG_ON(PageSlab(page));
	anon = PageAnon(page);

	rcu_read_lock();
	for_each_page_ext(page, pgcnt, page_ext, iter) {
		struct page_table_check *ptc = get_page_table_check(page_ext);

		if (anon) {
			BUG_ON(atomic_read(&ptc->file_map_count));      // anonymous인데 file 카운터 존재 시 BUG
			BUG_ON(atomic_inc_return(&ptc->anon_map_count) > 1 && rw); // anonymous 쓰기 이중 매핑 시 BUG
		} else {
			BUG_ON(atomic_read(&ptc->anon_map_count));      // file인데 anon 카운터 존재 시 BUG
			BUG_ON(atomic_inc_return(&ptc->file_map_count) < 0); // 음수 카운터 시 BUG
		}
	}
	rcu_read_unlock();
}
// 역할: PTE/PMD/PUD 설정 시 해당 페이지의 매핑 카운터를 증가시키고 검증
// anonymous 페이지의 쓰기 이중 매핑 감지 (anon_map_count > 1 && rw)

3. `__page_table_check_zero()` (mm/page_table_check.c:131)

void __page_table_check_zero(struct page *page, unsigned int order)
{
	struct page_ext_iter iter;
	struct page_ext *page_ext;

	BUG_ON(PageSlab(page));

	rcu_read_lock();
	for_each_page_ext(page, 1 << order, page_ext, iter) {
		struct page_table_check *ptc = get_page_table_check(page_ext);

		BUG_ON(atomic_read(&ptc->anon_map_count));   // 매핑 카운터가 0이 아니면 BUG
		BUG_ON(atomic_read(&ptc->file_map_count));   // 매핑 카운터가 0이 아니면 BUG
	}
	rcu_read_unlock();
}
// 역할: 페이지 할당 시 또는 해제 시 카운터가 0인지 확인
// 호출자: page_table_check_alloc, page_table_check_free

4. `__page_table_check_ptes_set()` (mm/page_table_check.c:202)

void __page_table_check_ptes_set(struct mm_struct *mm, unsigned long addr,
				 pte_t *ptep, pte_t pte, unsigned int nr)
{
	unsigned int i;

	if (&init_mm == mm)           // init_mm는 검증 대상 아님
		return;

	page_table_check_pte_flags(pte);  // uffd_wp 플래그 검증

	for (i = 0; i < nr; i++)
		__page_table_check_pte_clear(mm, addr + PAGE_SIZE * i, ptep_get(ptep + i));
	if (pte_user_accessible_page(pte, addr))
		page_table_check_set(pte_pfn(pte), nr, pte_write(pte));
}
// 역할: PTE 배치 시 기존 PTE 해제 및 새 PTE 설정 검증
// 페이지 폴트 처리, mmap, fork 등에서 호출됨

5. `__page_table_check_pmds_set()` (mm/page_table_check.c:231)

void __page_table_check_pmds_set(struct mm_struct *mm, unsigned long addr,
		pmd_t *pmdp, pmd_t pmd, unsigned int nr)
{
	unsigned long stride = PMD_SIZE >> PAGE_SHIFT;
	unsigned int i;

	if (&init_mm == mm)
		return;

	page_table_check_pmd_flags(pmd);  // uffd_wp 플래그 검증

	for (i = 0; i < nr; i++)
		__page_table_check_pmd_clear(mm, addr + PMD_SIZE * i, *(pmdp + i));
	if (pmd_user_accessible_page(pmd, addr))
		page_table_check_set(pmd_pfn(pmd), stride * nr, pmd_write(pmd));
}
// 역할: PMD 배치 시 기존 PMD 해제 및 새 PMD 설정 검증
// THP(Transparent Huge Page) 할당 시 호출됨

6. `__page_table_check_pte_clear_range()` (mm/page_table_check.c:265)

void __page_table_check_pte_clear_range(struct mm_struct *mm,
					unsigned long addr,
					pmd_t pmd)
{
	if (&init_mm == mm)
		return;

	if (!pmd_bad(pmd) && !pmd_leaf(pmd)) {
		pte_t *ptep = pte_offset_map(&pmd, addr);
		unsigned long i;

		if (WARN_ON(!ptep))
			return;
		for (i = 0; i < PTRS_PER_PTE; i++) {
			__page_table_check_pte_clear(mm, addr, ptep_get(ptep));
			addr += PAGE_SIZE;
			ptep++;
		}
		pte_unmap(ptep - PTRS_PER_PTE);
	}
}
// 역할: PMD에 속한 모든 PTE를 범위 단위로 해제 검증
// 호출자: khugepaged (THP 병합/해제 시)

호출 흐름

PTE 설정 시 검증 흐름

페이지 폴트 / mmap / fork
│
▼
__page_table_check_ptes_set()
│
├── page_table_check_pte_flags()     ← uffd_wp 플래그 검증
│
├── __page_table_check_pte_clear()   ← 기존 PTE 해제 검증
│       │
│       ▼
│   page_table_check_clear()         ← anon/file 카운터 감소
│       │
│       ▼
│   atomic_dec_return()              ← 음수 카운터 검증
│
└── page_table_check_set()           ← 새 PTE 설정 검증
│
▼
atomic_inc_return()              ← 이중 매핑 검증

페이지 할당/해제 시 검증 흐름

__alloc_pages() / __free_pages()
│
▼
page_table_check_alloc() / page_table_check_free()
│
▼
__page_table_check_zero()
│
▼
atomic_read(anon_map_count) == 0?  ── 아니면 → BUG_ON
atomic_read(file_map_count) == 0?  ── 아니면 → BUG_ON

THP 병합/해제 시 검증 흐름

khugepaged (THP 병합/해제)
│
▼
page_table_check_pte_clear_range()
│
▼
PMD 유효성 검사 (!pmd_bad && !pmd_leaf)
│
▼
PTRS_PER_PTE 순회
│
├── __page_table_check_pte_clear()   ← 각 PTE 해제 검증
│       │
│       ▼
│   page_table_check_clear()         ← 카운터 감소
│
└── 완료

조건별 비교

1. 검증 대상별 비교

구분PTE (4KB)PMD (2MB)PUD (1GB)
**검증 함수**`__page_table_check_ptes_set``__page_table_check_pmds_set``__page_table_check_puds_set`
**플래그 검증**`page_table_check_pte_flags``page_table_check_pmd_flags`없음
**페이지 수 계산**`nr` (직접)`stride * nr` (PMD_SIZE >> PAGE_SHIFT)`stride * nr` (PUD_SIZE >> PAGE_SHIFT)
**사용 시나리오**일반 페이지 폴트, mmapTHP 할당HugeTLB 할당
**호출 빈도**가장 높음중간낮음

2. 활성화 방식 비교

방식컨피그 옵션부트 파라미터동작
**기본 비활성화**`CONFIG_PAGE_TABLE_CHECK=y`없음필요 시 `page_table_check=on`으로 활성화
**강제 활성화**`CONFIG_PAGE_TABLE_CHECK_ENFORCED=y`불필요항상 활성화
**동적 활성화**`CONFIG_PAGE_TABLE_CHECK=y``page_table_check=on`부팅 시 동적 활성화

3. 아키텍처 지원 비교

아키텍처지원 여부컨피그 위치비고
**x86_64**지원`arch/x86/Kconfig`64비트만 지원
**ARM64**지원`arch/arm64/Kconfig`전체 지원
**RISC-V**지원`arch/riscv/Kconfig`MMU 필요
**PowerPC**조건부 지원`arch/powerpc/Kconfig`HugeTLB 미사용 시 지원
**32비트**미지원--

4. 플래그 검증 조건 비교

플래그PTE 검증PMD 검증동작
**pte_uffd_wp**`WARN_ON_ONCE(pte_uffd_wp && pte_write)`-uffd-wp와 쓰기 동시 설정 시 경고
**pte_swp_uffd_wp**`WARN_ON_ONCE(cached_writable)`-스왑된 uffd-wp에 쓰기 가능 시 경고
**pmd_uffd_wp**-`WARN_ON_ONCE(pmd_uffd_wp && pmd_write)`PMD 수준 uffd-wp 검증
**pmd_swp_uffd_wp**-`WARN_ON_ONCE(cached_writable)`PMD 스왑 uffd-wp 검증

카운터 기반 검증 원리

anonymous/file 배타성 검증

모든 페이지는 다음 중 하나여야 함:
1. anonymous 매핑만 있음 (anon_map_count >= 0, file_map_count == 0)
2. file 매핑만 있음 (anon_map_count == 0, file_map_count >= 0)

위반 시 BUG_ON() 발생:
- anon인데 file_map_count > 0 → BUG
- file인데 anon_map_count > 0 → BUG

쓰기 이중 매핑 감지

anonymous 페이지의 경우:
- anon_map_count > 1 && rw == true → BUG_ON 발생
- 즉, 하나의 anonymous 페이지를 두 프로세스가 동시에 쓰기로 매핑하는 것 방지

file 페이지의 경우:
- file_map_count 증가만 검증 (쓰기 이중 매핑 검증 없음)
- file 페이지는 COW로 보호되므로 이중 쓰기 매핑이 발생하지 않음

빈 카운터 검증 (할당/해제 시)

페이지가 free list에 있거나 할당되는 시점:
- anon_map_count == 0 이어야 함
- file_map_count == 0 이어야 함
- 위반 시 BUG_ON() 발생 (메모리 누수 또는 초기화 실패 감지)

관련 문서

  • 00 — 메모리 관리 개요
  • 03 — VMA / mmap
  • 10 — Huge Pages / THP
  • 12 — Folio / Page Cache
  • 27 — 디버그 도구
  • 38 — Page Table Walk

  • SVG 다이어그램

    자료구조 관계도

    페이지 테이블 검증 자료구조 관계도

    호출 흐름

    페이지 테이블 검증 호출 흐름