Huge Pages (2MB/1GB) & THP — 대형 페이지(Page)

리눅스 커널의 Huge Pages는 기본 4KB 페이지 대신 2MB 또는 1GB 크기의 대형 페이지를 사용하여 TLB(Translation Lookaside Buffer) 미스를 획기적으로 줄이고 메모리 접근 성능을 극대화하는 메커니즘입니다. 정적 예약 기반의 hugetlbfs부터 커널이 자동으로 대형 페이지를 생성하는 Transparent Huge Pages(THP), mTHP(Multi-size THP), khugepaged 데몬, PMD 직접 매핑(Mapping), compound page 구조, NUMA 정책, 예약 시스템, per-size THP sysfs, MADV_COLLAPSE, PR_SET_THP_DISABLE, HVO(HugeTLB Vmemmap Optimization), cgroup v2 회계(Accounting)까지 최신 커널 인터페이스를 기준으로 상세히 다룹니다.

전제 조건: MMU & TLB 문서와 페이지 할당자(Page Allocator) 문서를 먼저 읽으세요. 가상 주소(Virtual Address) 해석(Address Translation), 페이지 테이블(Page Table) 계층 구조, Buddy 시스템의 order 개념을 이해해야 Huge Pages의 동작 원리를 정확히 파악할 수 있습니다.
일상 비유: 일반 4KB 페이지는 편의점 봉투에 물건을 하나씩 담는 것과 같고, 2MB Huge Page는 대형 택배 상자에 512개의 물건을 한꺼번에 담는 것과 같습니다. 택배 상자를 사용하면 배송 추적(TLB 조회) 횟수가 512분의 1로 줄어들어 전체 배송 효율이 크게 향상됩니다. 1GB Huge Page는 아예 컨테이너(Container) 트럭으로 262,144개의 물건을 한 번에 운송하는 것에 해당합니다.

핵심 요약

  • Huge Page — 4KB보다 큰 페이지 크기(x86_64에서 2MB 또는 1GB)를 사용하여 TLB 효율을 극대화하는 메모리 관리(Memory Management) 기법. TLB 엔트리 하나당 커버 범위가 2MB(512배) 또는 1GB(262,144배)로 늘어나, 메모리 집약적 워크로드에서 TLB 미스를 90% 이상 줄이고 5~30%의 성능 향상을 얻을 수 있습니다. 단, 내부 단편화·CoW 비용 증가 등의 단점도 존재하므로 워크로드 특성을 고려해 적용해야 합니다.
    확인: grep HugePages /proc/meminfo
  • hugetlbfs — 사용자가 명시적으로 대형 페이지를 예약하고 사용하는 정적 방식의 파일시스템(Filesystem) 인터페이스. nr_hugepages로 사전 예약하면 커널이 Buddy 할당자에서 order-9(2MB) 또는 order-18(1GB) 블록을 미리 확보하고 초기화합니다. PostgreSQL shared_buffers, DPDK 패킷 버퍼, KVM 가상 머신 메모리에 주로 사용합니다. THP와 달리 CoW 비용이나 compaction 지연 없이 안정적인 레이턴시를 제공합니다.
    확인: ls /sys/kernel/mm/hugepages/ — 지원하는 Huge Page 크기 목록
  • THP (Transparent Huge Pages) — 커널이 자동으로 4KB 페이지를 2MB 대형 페이지로 승격/분할하는 투명 메커니즘. 애플리케이션 수정 없이 자동 적용되며, always / madvise / never 세 가지 정책으로 전체 또는 선택적으로 활성화합니다. 단, compaction 레이턴시 스파이크와 fork() 후 CoW 비용 512배 증폭이 발생할 수 있어 Redis·실시간 시스템에서는 비활성화가 권장됩니다.
    확인: cat /sys/kernel/mm/transparent_hugepage/enabled
  • khugepaged — 백그라운드에서 분산된 4KB 페이지를 스캔하여 2MB 대형 페이지로 병합하는 커널 데몬. THP가 활성화되면 자동 기동하며, 페이지 폴트 경로와 달리 비동기로 이미 4KB로 매핑된 페이지들을 사후에 병합합니다. scan_sleep_millisecspages_to_scan으로 스캔 빈도와 부하를 조절합니다. 병합 성공 시 thp_collapse_alloc 카운터가 증가합니다.
    확인: grep thp_collapse /proc/vmstat
  • PMD (Page Middle Directory) — x86_64에서 2MB Huge Page를 위한 페이지 테이블 레벨로, PTE 단계를 건너뛰어 직접 물리 프레임을 가리킴. PMD 엔트리의 비트 7(PS, Page Size Extension)이 1이면 해당 엔트리가 PTE 테이블이 아닌 2MB 물리 프레임을 직접 가리킵니다. 페이지 테이블 워크가 4단계에서 3단계로 줄어들어 TLB 미스 시 DRAM 접근도 4회에서 3회로 감소합니다.
    확인: grep -E 'AnonHugePages|KernelPageSize' /proc/<PID>/smaps | head -20
  • compound page — 연속된 물리 페이지를 하나의 논리적 대형 페이지로 묶는 커널 자료구조. 첫 번째 페이지가 Head(PG_head 플래그, compound_order=9)이고 나머지 511개가 Tail입니다. 각 Tail의 compound_head 포인터가 Head를 가리켜 임의의 Tail에서 O(1)으로 Head를 찾을 수 있습니다. THP와 hugetlbfs 모두 이 구조를 사용합니다.
    확인: cat /proc/pagetypeinfo — Huge 타입 페이지 수 확인

단계별 이해

  1. TLB 미스 비용 이해 — 4KB 페이지로 1GB를 매핑하면 262,144개의 TLB 엔트리가 필요하지만, 2MB로는 512개, 1GB로는 1개면 충분합니다. __handle_mm_fault()가 PMD의 _PAGE_PSE 비트를 확인하여 2MB 직접 매핑 여부를 판별합니다. TLB 미스가 줄어들수록 메모리 집약적 루프에서 유의미한 속도 향상이 나타납니다.
    실제 확인: perf stat -e dTLB-load-misses,dTLB-store-misses -p <PID> sleep 5 — THP 활성화 전후 TLB 미스 횟수를 비교하세요. 90% 이상 감소하면 효과 확인 완료입니다.
  2. 정적 예약 (hugetlbfs)/proc/sys/vm/nr_hugepages로 Huge Page 수를 미리 예약하면 set_max_huge_pages()가 Buddy 할당자에서 order-9(2MB) 연속 블록을 확보합니다. 1GB 페이지는 런타임 예약이 사실상 불가능하여 부팅 파라미터 hugepagesz=1G hugepages=N만 안정적입니다.
    실제 확인: echo 10 > /proc/sys/vm/nr_hugepages && grep HugePages /proc/meminfoHugePages_Total: 10이 나오면 예약 성공. 실제로 할당된 수보다 적으면 단편화 발생 중입니다.
  3. THP 활성화 — top-level enabled와 크기별 sysfs로 정책을 조정하고, MADV_HUGEPAGE / MADV_COLLAPSE로 특정 영역만 선별적으로 유도합니다. 폴트는 do_huge_pmd_anonymous_page()가 처리하며, 성공 시 thp_fault_alloc이 증가합니다.
    실제 확인: echo always > /sys/kernel/mm/transparent_hugepage/enabledgrep AnonHugePages /proc/<PID>/smaps_rollup — 값이 0보다 크면 THP가 실제로 적용된 것입니다.
  4. khugepaged 병합 — 백그라운드 데몬이 collapse_huge_page()로 연속 PTE 512개를 하나의 PMD 매핑으로 치환합니다. 기본적으로 PMD 크기(2MB)만 담당하며, 더 작은 mTHP 크기는 폴트 경로에서 처리됩니다. scan_sleep_millisecs 기본값 10,000ms(10초)마다 스캔하므로 병합에 최대 수십 초가 걸릴 수 있습니다.
    실제 확인: watch -n2 'grep -E "thp_collapse_alloc|thp_collapse_alloc_failed" /proc/vmstat'thp_collapse_alloc가 증가하면 병합 성공 중. failed가 많으면 단편화 해소가 필요합니다.
  5. 모니터링과 튜닝/proc/meminfo, /sys/kernel/mm/hugepages/, transparent_hugepage/에서 현황을 확인하고, hugetlb_report_meminfo()가 HugePages 통계를 /proc/meminfo에 출력합니다. 문제 진단 시에는 THP 통계와 프로세스별 사용량을 함께 살펴보세요.
    종합 체크리스트:
    grep -i huge /proc/meminfo — 전체 Huge Page 풀 상태 (Total/Free/Rsvd/Surp)
    grep -E 'thp_fault|thp_collapse' /proc/vmstat — THP 할당·병합 통계
    grep AnonHugePages /proc/<PID>/smaps_rollup — 프로세스별 THP 사용량
    cat /sys/kernel/mm/hugepages/hugepages-2048kB/free_hugepages — hugetlbfs 여유 풀
# Huge Pages 현황 빠른 확인
grep -i huge /proc/meminfo            # HugePages_Total/Free/Rsvd/Surp, Hugepagesize
cat /sys/kernel/mm/transparent_hugepage/enabled  # THP top-level 정책: always/madvise/never
cat /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled  # 2MB THP 크기별 정책
grep -E 'thp_fault|thp_collapse' /proc/vmstat    # THP 할당/병합 통계
cat /proc/<PID>/smaps_rollup | grep AnonHugePages  # 프로세스별 THP 사용량
ls /sys/kernel/mm/hugepages/          # 사용 가능한 Huge Page 크기 목록

페이지(Page)란 무엇인가 — 기초 다지기

Huge Pages를 정확히 이해하려면 먼저 "페이지"가 무엇이고, 왜 4KB가 표준이 되었으며, TLB가 어떻게 동작하는지를 이해해야 합니다. 이미 MMU & TLB 문서를 읽었다면 이 섹션을 건너뛰어도 됩니다. 그렇지 않다면 아래 내용이 Huge Pages 본론으로 들어가기 위한 최소 기초입니다.

페이지 — OS가 메모리를 다루는 최소 단위

현대 OS는 모든 프로세스(Process)에게 독립적인 가상 주소 공간(Virtual Address Space)을 제공합니다. 프로세스 A가 주소 0x00400000에 데이터를 쓰더라도 프로세스 B의 같은 주소에는 영향을 주지 않습니다. 이 격리는 CPU의 MMU(Memory Management Unit)가 가상 주소(Virtual Address)를 물리 주소(Physical Address)로 변환하는 과정에서 실현됩니다.

물리 메모리를 1바이트 단위로 개별 추적한다면 매핑 정보 자체가 수 기가바이트를 차지합니다. 이를 방지하기 위해 OS는 메모리를 고정 크기 블록인 페이지(Page)로 나누어 관리합니다. 페이지 테이블(Page Table)에는 가상 페이지 번호(VPN, Virtual Page Number)에서 물리 페이지 프레임 번호(PFN, Physical Frame Number)로의 매핑이 저장됩니다.

가상 주소 → 물리 주소 변환 구조 가상 주소(Virtual Address, 64비트) VPN — 어느 페이지? (비트 63~12) Offset (비트 11~0, 12비트 = 4KB) 색인(Index) 그대로 전달 TLB (변환 캐시) 최근 VPN→PFN 결과 저장 히트(Hit): 1~4 사이클 미스(Miss): 페이지 테이블 워크 필요 먼저 확인 페이지 테이블(Page Table) VPN 색인 → PFN + 플래그 (Present / R·W / Dirty / Accessed …) TLB 미스 시 DRAM 4회 접근 필요 (x86_64 기준: PGD→PUD→PMD→PTE) PFN 반환 물리 주소(Physical Address) PFN (비트 63~12) — 실제 물리 메모리 위치 Offset (동일, 비트 11~0) 물리 주소 = (PFN << 12) | Offset / Offset은 페이지 내 0~4095바이트 위치
핵심 구조: 가상 주소는 VPN + Offset 두 부분으로 나뉩니다. VPN은 페이지 테이블에서 PFN을 찾는 색인으로 사용되고, Offset(하위 12비트)은 그대로 물리 주소의 하위 비트가 됩니다. 4KB 페이지에서 Offset은 12비트(2¹² = 4,096바이트)를 사용하며, 이 12비트 값이 곧 4KB 페이지 크기를 결정합니다.

왜 4KB가 표준 페이지 크기인가

4KB는 1990년대 초 Intel 386/486 설계 당시 디스크 섹터 크기(512B~4KB)와의 정합성, 당시 평균 프로세스 메모리 사용량, TLB 엔트리 수 제약을 종합하여 결정된 크기입니다. 이 선택은 "페이지가 너무 작을 때"와 "너무 클 때"의 상충 관계(Trade-off)를 절충한 결과입니다.

페이지 크기 페이지 테이블 크기
(32GB 매핑 기준)
TLB 커버리지
(2,048 엔트리)
평균 내부 단편화 비고
512B ~256 MB (매우 큼) ~1 MB ~256B 페이지 테이블 오버헤드 과다
4KB (표준) ~32 MB ~8 MB ~2KB x86_64, ARM64(4K 모드) 기본값
64KB ~2 MB ~128 MB ~32KB ARM64 선택적 지원 (CONFIG_ARM64_64K_PAGES)
2MB ~64 KB ~4 GB ~1MB x86_64 Huge Page (PMD 레벨)
1GB ~256 B ~2 TB ~512MB x86_64 Huge Page (PUD 레벨)
내부 단편화(Internal Fragmentation): 프로세스가 4,097바이트를 요청하면 커널은 최소 2개의 4KB 페이지(8,192바이트)를 할당합니다. 두 번째 페이지의 4,095바이트는 낭비됩니다. 2MB Huge Page를 사용하는 프로세스가 평균적으로 절반 정도만 실제 사용한다면, 수백~수천 개의 프로세스 환경에서 오히려 메모리 낭비가 대폭 늘어날 수 있습니다. 이것이 Huge Pages가 항상 좋은 것은 아닌 이유입니다.

TLB — 페이지 테이블 조회 캐시

페이지 테이블은 DRAM에 저장됩니다. x86_64에서 가상 주소를 물리 주소로 변환하려면 PGD → PUD → PMD → PTE 4단계를 거쳐야 하고, 각 단계마다 DRAM 접근이 필요합니다. DRAM 한 번 접근에 약 60~100ns가 걸린다면 4단계 변환만으로 240~400ns를 소모합니다. 이를 매 메모리 접근마다 반복하면 프로그램 실행이 불가능해집니다.

이를 해결하는 것이 TLB(Translation Lookaside Buffer)입니다. TLB는 CPU 내부에 있는 고속 변환 캐시로, 최근에 사용한 VPN→PFN 매핑 결과를 저장합니다. TLB에 원하는 변환이 있으면 히트(Hit), 없으면 미스(Miss)가 발생합니다.

상황소요 사이클소요 시간 (3GHz 기준)설명
TLB 히트 1~4 사이클 <1 ns 변환 캐시 적중 — 페이지 테이블 접근 불필요
TLB 미스 + L1/L2 캐시 ~20~50 사이클 ~7~17 ns 페이지 테이블 엔트리가 CPU 캐시에 있는 경우
TLB 미스 + DRAM 접근 ~200~500 사이클 ~67~167 ns 4단계 페이지 테이블 워크, DRAM 최대 4회 접근
TLB 미스 + 원격 NUMA ~400~1,000 사이클 ~133~333 ns 원격 NUMA 노드의 페이지 테이블 접근 시
TLB 구조 상세: L1 DTLB/ITLB, L2 STLB, PCID(Process Context ID), INVLPG/INVPCID 명령, TLB 플러시(TLB Flush) 비용 등 TLB의 전체 내용은 MMU & TLB 문서를 참조하세요.

현대 서버에서 4KB 페이지의 한계

TLB는 용량이 제한적입니다. x86_64 프로세서의 L2 STLB는 보통 2,048~4,096개의 4KB 엔트리를 가집니다. 4KB 페이지만 사용할 때 TLB가 커버할 수 있는 메모리 양을 직접 계산해보면 다음과 같습니다.

# 4KB 페이지 사용 시 TLB 커버리지 계산 (32GB 서버 기준)
L2 STLB 엔트리 수:       2,048개
TLB 커버 가능 메모리:     2,048 × 4KB = 8 MB

서버 전체 메모리:          32 GB = 32,768 MB
필요한 4KB TLB 엔트리:    32,768 MB ÷ 4KB = 8,388,608개

TLB 커버 비율:            2,048 ÷ 8,388,608 = 0.024%
                          → 메모리 접근의 99.976%가 TLB 미스 가능성

데이터베이스 버퍼 풀, 대용량 행렬 연산, 가상 머신(VM) 메모리 등 접근 패턴이 무작위(Random Access)인 메모리 집약적 워크로드에서는 TLB 미스율이 실제로 90% 이상에 달하는 경우가 많습니다. 이것이 Huge Pages가 필요한 근본 이유입니다. 더 큰 페이지를 사용하면 TLB 엔트리 하나가 커버하는 범위가 2MB 또는 1GB로 늘어나 같은 수의 엔트리로 훨씬 더 많은 메모리를 커버합니다.

페이지 크기 TLB 2,048 엔트리 커버리지 32GB 메모리 대비 커버 비율 개선 배율
4KB (기본) 8 MB 0.024% 기준
2MB (Huge Page) 4 GB 12.5% 512배
1GB (Huge Page) 2 TB 100% 초과 262,144배
결론: 4KB 페이지는 수십 MB 규모 워크로드에서 설계되었습니다. 현대 서버의 수십~수백 GB 메모리에서 메모리 집약적 접근이 발생하면 TLB가 사실상 무력화되고 모든 메모리 접근이 페이지 테이블 워크를 동반하게 됩니다. Huge Pages는 이 TLB 커버리지 위기(TLB Coverage Crisis)를 해결하는 가장 직접적인 수단입니다.

개요 — 왜 Huge Pages가 필요한가

TLB 미스의 비용

현대 x86_64 프로세서에서 가상 주소를 물리 주소(Physical Address)로 변환하는 과정은 4단계 페이지 테이블 워크 (PGD → PUD → PMD → PTE)를 거칩니다. TLB에 캐시된 변환이 있으면 1~2 사이클 내에 완료되지만, TLB 미스가 발생하면 최대 4번의 메모리 접근이 필요하여 수십~수백 사이클의 지연(Latency)이 발생합니다.

일반적인 x86_64 프로세서의 TLB 엔트리 수는 다음과 같습니다:

TLB 레벨4KB 엔트리 수2MB 엔트리 수1GB 엔트리 수
L1 DTLB (데이터) 64~72 32 4~8
L1 ITLB (명령어) 128 8
L2 STLB (공유) 1,536~2,048 1,536~2,048 (공유)
커버 가능 메모리 6~8 MB 3~4 GB 4~8 GB
핵심 포인트: 4KB 페이지로는 L2 STLB 2,048개 엔트리를 모두 사용해도 겨우 8MB의 메모리만 TLB로 커버할 수 있습니다. 반면 2MB Huge Pages는 동일한 TLB 엔트리로 약 4GB, 1GB Huge Pages는 8GB를 커버합니다. 메모리 집약적 워크로드에서 TLB 미스 감소 효과는 수십 퍼센트의 성능 향상으로 직결됩니다.

Huge Pages의 두 가지 방식

항목hugetlbfs (정적 Huge Pages)THP (투명 대형 페이지)
할당 방식사전 예약 (부팅 시 또는 런타임)커널 자동 (on-demand)
지원 크기아키텍처별 HugeTLB 크기(예: 2MB, 1GB)PMD 크기 + mTHP의 더 작은 크기들
사용자 인터페이스mmap(MAP_HUGETLB), hugetlbfs 마운트(Mount)투명 + madvise(), prctl(), 크기별 sysfs
OOM 위험낮음 (사전 예약)있음 (compaction 실패 시 fallback)
분할(split) 지원불가가능 (필요 시 4KB로 분할)
스왑(Swap) 지원불가가능 (swap-out 시 분할 후 스왑)
대표 사용처DPDK, 데이터베이스, VM일반 애플리케이션 자동 최적화

x86_64 페이지 크기 계층

x86_64 아키텍처는 3가지 페이지 크기를 하드웨어적으로 지원합니다. 각 크기는 페이지 테이블의 서로 다른 레벨에서 매핑됩니다.

/* x86_64 페이지 크기 계층 */
4KB   = 2^12  →  PTE 레벨 매핑 (기본)
2MB   = 2^21  →  PMD 레벨 매핑 (512 x 4KB)
1GB   = 2^30  →  PUD 레벨 매핑 (512 x 2MB = 262,144 x 4KB)

페이지 크기와 TLB 효과 비교

4KB vs 2MB vs 1GB TLB 커버리지

동일한 TLB 엔트리 수에서 페이지 크기에 따른 커버 가능한 메모리 양의 차이를 비교합니다. 아래 다이어그램은 64개의 DTLB 엔트리를 기준으로 각 페이지 크기별 커버리지를 보여줍니다.

64개 DTLB 엔트리 기준 — 페이지 크기별 메모리 커버리지 64 GB 48 GB 32 GB 16 GB 0 256 KB 4KB 페이지 64 x 4KB 128 MB 2MB 페이지 64 x 2MB 64 GB 1GB 페이지 64 x 1GB x512 x512 4KB (기본) 2MB (PMD) 1GB (PUD)

TLB 미스율 시뮬레이션

아래 표는 연속적으로 접근하는 메모리 영역 크기별 TLB 미스 횟수를 비교합니다. (L1 DTLB 64개, L2 STLB 2,048개 기준)

접근 메모리 크기4KB 페이지 (필요 엔트리)4KB TLB 미스2MB 페이지 (필요 엔트리)2MB TLB 미스
8 MB2,048L2 경계40 (L1 캐시(Cache))
64 MB16,384빈번320 (L1 캐시)
512 MB131,072매우 빈번2560 (L2 캐시)
4 GB1,048,576극심2,048L2 경계
32 GB8,388,608극심16,384빈번
# TLB 미스를 perf로 측정하여 Huge Pages 효과 비교
# 1) 일반 4KB 페이지 환경에서 측정
perf stat -e dTLB-load-misses,dTLB-store-misses,iTLB-load-misses \
    -p <PID> -- sleep 10

# 2) THP 활성화 후 동일 워크로드 측정
echo always > /sys/kernel/mm/transparent_hugepage/enabled
perf stat -e dTLB-load-misses,dTLB-store-misses,iTLB-load-misses \
    -p <PID> -- sleep 10

# 3) 결과 비교: dTLB-load-misses가 90%+ 감소하면 Huge Pages 효과 확인
성능 효과: 실제 벤치마크에서 대용량 메모리를 순회하는 워크로드(데이터베이스 버퍼(Buffer) 풀, 과학 계산, VM 메모리 등)에서 THP 또는 hugetlbfs를 활성화하면 TLB 미스가 90% 이상 감소하고, 전체 성능이 5~30% 향상되는 사례가 보고됩니다.

외부 단편화(External Fragmentation)와 Huge Page 할당의 어려움

Huge Page가 필요한 이유를 이해했다면, 이제 "왜 Huge Page를 얻기가 어려운가"를 알아야 합니다. 단순히 "2MB 페이지를 달라"고 요청하면 커널이 즉시 줄 수 있을 것 같지만, 실제로는 물리 메모리의 연속성 요구사항과 시간이 지남에 따른 단편화가 걸림돌이 됩니다. 이를 이해해야 hugetlbfs가 사전 예약을 고집하는 이유, khugepaged와 compaction의 존재 이유를 납득할 수 있습니다.

Buddy 시스템과 연속 페이지 요구사항

리눅스 커널의 Buddy 할당자(Buddy Allocator)는 물리 메모리를 2의 거듭제곱 크기 블록으로 나누어 관리합니다 (order-0: 4KB×1페이지, order-1: 4KB×2페이지, …, order-9: 4KB×512페이지 = 2MB, order-18: 4KB×262,144페이지 = 1GB). Huge Page를 할당하려면 이 물리적으로 연속된 블록 전체가 비어 있어야 하며, 중간에 단 하나의 4KB 페이지라도 다른 용도로 사용 중이면 해당 영역에서는 Huge Page를 만들 수 없습니다.

Huge Page 크기 Buddy order 연속 4KB 페이지 수 물리 주소 정렬 조건 런타임 할당 가능성
2MB order-9 512개 연속 하위 21비트 = 0 (2MB 경계) 가능 (compaction 후 성공 가능)
1GB order-18 262,144개 연속 하위 30비트 = 0 (1GB 경계) 사실상 불가 — 부팅 파라미터 예약만 안정적
정렬 요구사항의 의미: TLB는 2MB Huge Page를 참조할 때 PMD 엔트리 하나로 처리합니다. MMU 하드웨어는 이 물리 프레임이 2MB 경계(주소 하위 21비트 = 0)에 정렬되어 있다고 가정합니다. 정렬 조건을 만족하지 않으면 대형 페이지 매핑 자체가 불가능합니다.

부팅 직후 vs 장시간 운용 후: 단편화 비교

시스템이 처음 부팅될 때 물리 메모리는 커널 코드/데이터 영역을 제외하면 대부분 연속된 대형 블록으로 구성됩니다. 그러나 프로세스들이 메모리를 할당하고 해제하는 과정이 반복되면, 큰 연속 블록이 중간중간 사용된 페이지들로 인해 잘게 쪼개지는 외부 단편화(External Fragmentation)가 심화됩니다. 총 여유 메모리의 합은 충분하더라도 연속 2MB 블록이 하나도 없을 수 있습니다.

물리 메모리 단편화: 부팅 직후 vs 장시간 운용 후 부팅 직후 커널 큰 연속 빈 공간 (≥ 2MB) ← 512×4KB 연속 블록 확보 가능 → ✓ order-9 블록 즉시 확보 → 2MB Huge Page 할당 성공 장시간 운용 후 커널 최대 빈 블록 ≈ 64KB — 2MB에 훨씬 못 미침 ✗ 연속 2MB 블록 없음 → compaction 또는 4KB 폴백 커널/드라이버 애플리케이션 (사용 중) 빈 공간 (Free)

위 다이어그램에서 주목할 점은, 장시간 운용 후 각 빈 공간 조각의 크기가 수십~수백 KB에 불과하다는 것입니다. 전체 빈 공간의 합은 수 GB에 달할 수 있지만, 2MB 크기의 연속 구간은 전혀 없을 수 있습니다. 이 상황에서 2MB Huge Page를 요청하면 커널은 두 가지 중 하나를 선택합니다.

선택지 동작 비용 성공 보장
Memory Compaction 시도 이동 가능한 페이지들을 한쪽으로 모아 연속 공간 확보 수십 ms CPU·레이턴시 비용 보장 불가 (이동 불가 페이지 존재 시 실패)
4KB 폴백(Fallback) 2MB 할당 포기, 기존 4KB 페이지로 처리 거의 없음 항상 성공 (단, TLB 미스 개선 없음)

Memory Compaction — 단편화 해소 시도

Memory Compaction은 이동 가능한(Movable) 페이지들을 물리 메모리의 한쪽 끝으로 이동시켜 다른 쪽에 연속된 빈 공간을 만드는 기법입니다. 커널은 이동 대상 페이지를 복사하고 매핑을 갱신한 뒤 원래 위치를 해제합니다. THP 할당 실패 시 defrag 정책에 따라 자동으로 시도되며, 백그라운드 데몬 kcompactd가 주기적으로 수행하기도 합니다.

# compaction 수동 실행 (테스트·튜닝 목적)
echo 1 > /proc/sys/vm/compact_memory

# THP defrag 정책 설정 (compaction 시도 방식 결정)
# always        : 폴트 경로에서 직접 compaction (레이턴시 위험)
# defer         : 백그라운드 kcompactd에 위임 (권장)
# defer+madvise : 기본은 defer, MADV_HUGEPAGE 영역은 직접 compaction
# madvise       : MADV_HUGEPAGE 힌트 영역만 직접 compaction
# never         : compaction 시도 안 함 → 무조건 4KB 폴백
echo defer > /sys/kernel/mm/transparent_hugepage/defrag

# compaction 통계 확인
grep 'compact_\|thp_collapse' /proc/vmstat
# compact_success        : compaction 성공 횟수
# compact_fail           : 실패 횟수 (이동 불가 페이지로 인한)
# compact_migrate_scanned: 스캔한 이동 가능 페이지 수
Compaction 레이턴시 주의: defrag=always 또는 defrag=madvise에서 직접 compaction은 수십 ms의 레이턴시 스파이크를 유발합니다. Redis, Nginx, 결제 서비스 등 지연 민감(Latency-sensitive) 워크로드에서 THP를 끄거나 defrag=defer를 사용하는 주된 이유가 이 compaction 지연 문제입니다. 데이터베이스 및 고성능 서비스에서 THP 설정 시 반드시 /proc/vmstatcompact_fail 수치를 모니터링하세요.

왜 1GB Huge Page는 부팅 시에만 안정적으로 할당 가능한가

2MB Huge Page는 order-9(512 연속 페이지)만 필요하므로 compaction으로 확보될 가능성이 있습니다. 그러나 1GB Huge Page는 order-18, 즉 물리 주소 1GB 경계에 정렬된 연속 262,144개의 4KB 페이지가 필요합니다. 시스템이 부팅된 직후에도 커널 코드, 드라이버 초기화, ACPI 테이블 등이 물리 메모리 곳곳에 배치되어 있어 1GB 연속 블록 확보가 쉽지 않습니다. 몇 분만 지나도 DMA 버퍼, 네트워크 드라이버, 파일시스템 캐시 등이 해당 영역을 점거하여 런타임 할당은 사실상 불가능해집니다.

# 1GB Huge Pages — 부팅 파라미터로 안정적 예약 (권장)
# /etc/default/grub 예시:
GRUB_CMDLINE_LINUX_DEFAULT="hugepagesz=1G hugepages=16 hugepagesz=2M hugepages=512"
# → 부팅 초기(메모리 초기화 직후)에 1GB×16개 + 2MB×512개 예약

# 런타임 1GB Huge Page 요청 (불안정 — 실패 가능성 높음)
echo 4 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
# 요청한 4가 아닌 더 적은 수가 나오면 단편화로 인한 할당 실패

# 2MB Huge Pages — 런타임 예약도 가능 (단, 부팅 직후에 예약할수록 유리)
echo 1024 > /proc/sys/vm/nr_hugepages  # 1024×2MB = 2GB 요청
grep HugePages /proc/meminfo         # 실제 할당된 수 확인
실전 권장: 1GB Huge Page가 필요한 워크로드(DPDK 대용량 패킷 처리, 대형 DBMS 버퍼 풀, 대용량 VM)는 반드시 부팅 파라미터로 사전 예약하세요. 2MB Huge Page는 런타임 예약도 가능하지만, 가능하면 nr_hugepages/etc/sysctl.conf에 등록하여 부팅 직후에 예약하는 것이 단편화 회피에 유리합니다.

hugetlbfs 아키텍처 — 예약 기반 정적 Huge Pages

hugetlbfs 개요

hugetlbfs는 커널 2.6부터 도입된 특수 파일시스템으로, 정적으로 예약된 Huge Pages를 사용자 공간에 제공합니다. Buddy 할당자에서 연속된 고차(order-9 또는 order-18) 물리 페이지를 미리 확보하여 전용 풀에 보관하며, 사용자가 hugetlbfs를 마운트하고 파일을 mmap하여 사용합니다.

hugetlbfs 예약 흐름

hugetlbfs 아키텍처: 예약 → 할당 → 매핑 흐름 사용자 공간 프로세스 A (mmap) 프로세스 B (shmget) libhugetlbfs sysctl / 부팅 파라미터 nr_hugepages 설정 커널 공간 hugetlbfs VFS 레이어 예약 관리 (resv_map) subpool 관리 HugeTLB 페이지 풀 free_huge_pages / nr_huge_pages NUMA Node 0 node_huge_pages[] NUMA Node 1 node_huge_pages[] Buddy 할당자 (order-9 / order-18) 물리 메모리(Physical Memory) (연속 페이지 프레임(Page Frame)) 초기 할당

hugetlbfs 페이지 풀 관리 구조체(Struct)

/* mm/hugetlb.c - 핵심 전역 변수 */
struct hstate hstates[HUGE_MAX_HSTATE];
unsigned int default_hstate_idx;

struct hstate {
    struct mutex resize_lock;
    int next_nid_to_alloc;
    int next_nid_to_free;
    unsigned int order;           /* 2MB: order=9, 1GB: order=18 */
    unsigned int demote_order;
    unsigned long mask;
    unsigned long max_huge_pages;
    unsigned long nr_huge_pages;    /* 현재 총 Huge Pages 수 */
    unsigned long free_huge_pages;  /* 미사용 Huge Pages 수 */
    unsigned long resv_huge_pages;  /* 예약된 Huge Pages 수 */
    unsigned long surplus_huge_pages;
    unsigned long nr_overcommit_huge_pages;
    struct list_head hugepage_activelist;
    struct list_head hugepage_freelists[MAX_NUMNODES];
    unsigned int nr_huge_pages_node[MAX_NUMNODES];
    unsigned int free_huge_pages_node[MAX_NUMNODES];
    unsigned int surplus_huge_pages_node[MAX_NUMNODES];
    char name[HSTATE_NAME_LEN];
};
코드 설명
  • 2행 hstates 배열은 시스템이 지원하는 각 Huge Page 크기별 상태를 관리합니다. x86_64에서는 일반적으로 2MB와 1GB 두 가지입니다.
  • 6행 order 필드는 Buddy 할당자에서의 주문 크기입니다. 2MB는 order-9(512개의 4KB 페이지), 1GB는 order-18(262,144개)입니다.
  • 11~13행 핵심 카운터: 전체 수(nr_huge_pages), 여유 수(free_huge_pages), 예약 수(resv_huge_pages)로 풀 상태를 추적합니다.
  • 16~17행 활성 목록과 여유 목록을 NUMA 노드별로 분리하여, NUMA 지역성을 고려한 할당이 가능합니다.

hugetlbfs 마운트와 사용

# hugetlbfs 마운트
mount -t hugetlbfs -o pagesize=2M,size=4G,min_size=1G none /mnt/hugepages

# 2MB Huge Pages 예약 (1024개 = 2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# NUMA 노드별 예약
echo 512 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

# 1GB Huge Pages 예약 (부팅 파라미터로만 안정적 할당 가능)
# 커널 부팅 파라미터: hugepagesz=1G hugepages=16

# 현재 상태 확인
cat /proc/meminfo | grep -i huge
/* 사용자 공간에서 mmap으로 Huge Page 매핑 */
#include <sys/mman.h>

void *alloc_hugepage(size_t size)
{
    void *addr = mmap(NULL, size,
                       PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                       -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap MAP_HUGETLB");
        return NULL;
    }
    return addr;
}

/* 특정 크기 지정: MAP_HUGE_2MB 또는 MAP_HUGE_1GB */
void *alloc_1gb_hugepage(size_t size)
{
    void *addr = mmap(NULL, size,
                       PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB |
                       MAP_HUGE_1GB,
                       -1, 0);
    return (addr == MAP_FAILED) ? NULL : addr;
}

Transparent Huge Pages (THP) 메커니즘

THP 개요

Transparent Huge Pages(THP)는 커널 2.6.38에 도입된 메커니즘으로, 사용자 공간(User Space) 애플리케이션의 수정 없이 커널이 자동으로 대형 페이지를 할당하고 관리합니다. 최신 커널에서는 익명 메모리뿐 아니라 tmpfs/shmem에 대해 mTHP(Multi-size THP)와 크기별 정책 제어를 제공하며, 파일 매핑은 madvise()와 커널 구성 조건을 만족할 때 제한적으로 THP 경로를 사용할 수 있습니다. hugetlbfs와 달리 사전 예약이 필요 없고, 할당 실패 시 자동으로 더 작은 크기 또는 4KB 페이지로 폴백합니다.

THP 동작 모드

모드설정값동작사용 사례
always always 모든 익명 메모리 매핑에 THP 시도 메모리 집약적 서버, 일반 데스크탑
madvise madvise madvise(MADV_HUGEPAGE) 힌트를 준 영역만 THP 적용 데이터베이스, 선택적 최적화
never never THP 완전 비활성화 지연 민감 실시간(Real-time) 시스템
최신 운영 모델: 현재 THP 정책은 top-level enabled 하나로 끝나지 않습니다. 실제 동작은 크기별 hugepages-<size>kB/enabled, shmem용 shmem_enabled, madvise(), prctl(PR_SET_THP_DISABLE), 그리고 부팅 파라미터 thp_anon=/thp_shmem=의 조합으로 결정됩니다.
THP 페이지 폴트 처리 흐름 Anonymous Page Fault THP 활성화? (enabled/madvise) 4KB 일반 할당 No Yes VMA >= 2MB? 정렬 확인 4KB 폴백 할당 No Yes compound page 할당 시도 할당 성공? PMD 직접 매핑 Yes 2MB Huge Page 성공 compaction 시도 후 4KB 폴백 할당 No khugepaged가 나중에 병합

THP 설정 인터페이스

# 1) top-level THP 기본 정책
cat /sys/kernel/mm/transparent_hugepage/enabled
echo always  > /sys/kernel/mm/transparent_hugepage/enabled
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo never   > /sys/kernel/mm/transparent_hugepage/enabled

# 2) 크기별 anon THP 정책 (최신 커널의 핵심 인터페이스)
ls /sys/kernel/mm/transparent_hugepage/hugepages-*
cat /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
# 예: always [inherit] madvise never
echo inherit > /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
echo always  > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled

# 3) shmem/tmpfs hugepage 정책
cat /sys/kernel/mm/transparent_hugepage/shmem_enabled
cat /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/shmem_enabled
# 예: always within_size advise [inherit] never deny force
echo within_size > /sys/kernel/mm/transparent_hugepage/shmem_enabled
echo advise      > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/shmem_enabled

# 4) defrag 정책 (직접 compaction 수행 여부)
echo always        > /sys/kernel/mm/transparent_hugepage/defrag
echo defer         > /sys/kernel/mm/transparent_hugepage/defrag
echo defer+madvise > /sys/kernel/mm/transparent_hugepage/defrag
echo madvise       > /sys/kernel/mm/transparent_hugepage/defrag
echo never         > /sys/kernel/mm/transparent_hugepage/defrag

# 5) huge zero page / underused THP split 정책
echo 0 > /sys/kernel/mm/transparent_hugepage/use_zero_page
echo 1 > /sys/kernel/mm/transparent_hugepage/use_zero_page
echo 0 > /sys/kernel/mm/transparent_hugepage/shrink_underused
echo 1 > /sys/kernel/mm/transparent_hugepage/shrink_underused

# 6) THP/compaction 통계
grep -E 'thp_|compact_' /proc/vmstat
중요 예외: 모든 sysfs THP 정책을 never로 두더라도 madvise(..., MADV_COLLAPSE)는 PMD 크기 collapse를 별도로 시도합니다. 또한 khugepaged는 PMD 크기 THP가 유효할 때만 자동으로 기동합니다.

프로세스 단위 THP 제어

/* prctl()로 프로세스 단위 THP 정책 제어 */
#include <sys/prctl.h>
#include <linux/prctl.h>

/* 1) 프로세스 전체에서 THP 완전 비활성화 */
prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);

/* 2) advised 영역만 허용 (최신 운영에서 유용) */
prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0);

/* 3) 다시 기본 정책으로 복귀 */
prctl(PR_SET_THP_DISABLE, 0, 0, 0, 0);

PR_SET_THP_DISABLE는 프로세스(Process) 전체에 적용되며 fork()execve()를 통해 상속됩니다. 특히 PR_THP_DISABLE_EXCEPT_ADVISED는 전역 THP를 광범위하게 켜지 않고도, MADV_HUGEPAGE 또는 MADV_COLLAPSE를 준 영역만 허용하는 최신 운영 패턴에 적합합니다.

THP 핵심 코드 경로

/* mm/huge_memory.c - THP 페이지 폴트 핸들러(Handler) */
static vm_fault_t __do_huge_pmd_anonymous_page(
    struct vm_fault *vmf, struct page *page,
    gfp_t gfp)
{
    struct vm_area_struct *vma = vmf->vma;
    pgtable_t pgtable;
    unsigned long haddr = vmf->address & HPAGE_PMD_MASK;

    /* 2MB 정렬된 주소 확인 */
    VM_BUG_ON_PAGE(!PageCompound(page), page);
    VM_BUG_ON_PAGE(!PageHead(page), page);

    /* 페이지 초기화 (zeroing) */
    clear_huge_page(page, vmf->address, HPAGE_PMD_NR);

    /* PMD 엔트리를 직접 설정 (PTE 레벨 건너뜀) */
    __SetPageUptodate(page);

    spin_lock(vmf->ptl);
    if (unlikely(!pmd_none(*vmf->pmd))) {
        spin_unlock(vmf->ptl);
        goto out;
    }

    entry = mk_huge_pmd(page, vma->vm_page_prot);
    entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
    page_add_new_anon_rmap(page, vma, haddr);
    lru_cache_add_inactive_or_unevictable(page, vma);
    pgtable_trans_huge_deposit(vma->vm_mm, vmf->pmd, pgtable);
    set_pmd_at(vma->vm_mm, haddr, vmf->pmd, entry);

    spin_unlock(vmf->ptl);
    return VM_FAULT_NOPAGE;

out:
    mem_cgroup_uncharge(page);
    put_page(page);
    return VM_FAULT_FALLBACK;
}
코드 설명
  • 8행 HPAGE_PMD_MASK로 주소를 2MB 경계에 정렬합니다. THP는 반드시 2MB 정렬된 가상 주소에 매핑됩니다.
  • 11~12행 할당된 페이지가 compound page(Head 페이지)인지 검증합니다. THP는 항상 compound page 형태입니다.
  • 15행 clear_huge_page()는 512개의 4KB 페이지를 한꺼번에 0으로 초기화합니다. HPAGE_PMD_NR은 512입니다.
  • 26행 mk_huge_pmd()가 PMD 엔트리를 생성합니다. PTE 레벨을 건너뛰고 PMD에서 직접 2MB 물리 프레임을 가리킵니다.
  • 30행 pgtable_trans_huge_deposit()는 나중에 THP가 분할될 때 사용할 PTE 페이지 테이블을 미리 보관합니다.
  • 31행 set_pmd_at()으로 PMD 엔트리를 원자적(Atomic)으로 설정하여 2MB 매핑을 완성합니다.

khugepaged 데몬 동작

khugepaged 개요

khugepaged는 THP 프레임워크의 백그라운드 병합 데몬입니다. 주기적으로 프로세스의 가상 메모리(Virtual Memory) 영역을 스캔하여, 연속된 512개의 4KB 페이지가 동일한 VMA에 속하고 병합 조건을 만족하면 하나의 2MB THP로 통합(collapse)합니다.

최신 동작 범위: khugepaged는 여전히 PMD 크기 THP만 background collapse합니다. 64KB, 128KB 같은 mTHP 크기는 주로 페이지 폴트 시점에 할당되며, khugepaged가 그 크기로 재병합하는 경로는 일반적인 운영 모델이 아닙니다.
khugepaged 스캔 및 병합(collapse) 흐름 Sleep scan_sleep_ms mm_struct 선택 라운드 로빈(Round Robin) 스캔 VMA 순회 hugepage_vma_check() 2MB 정렬 확인 HPAGE_PMD_MASK 512개 PTE 스캔 __collapse_huge_page_swapin() max_ptes_none, max_ptes_swap 검사 병합 가능? 건너뜀 (다음 VMA) Yes compound page 할당 alloc_charge_hpage(order=9) 512개 페이지 복사 copy_page(hpage + i, pages[i]) PMD 엔트리 교체 set_pmd_at() + TLB flush 병합 완료 thp_collapse_alloc++ 반복

khugepaged 튜닝 파라미터

# khugepaged 스캔 간격 (밀리초, 기본 10000 = 10초)
echo 5000 > /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs

# 한 번의 스캔에서 처리할 최대 페이지 수 (기본 4096)
echo 8192 > /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan

# 비어 있는 PTE 최대 허용 수 (기본 511, 최대 511)
echo 511 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none

# 스왑된 PTE 최대 허용 수 (기본 64)
echo 64 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_swap

# 공유 PTE 최대 허용 수 (기본 256)
echo 256 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_shared

Huge Page 할당 경로 — alloc_hugepage, compound page

compound page 구조

Huge Page는 내부적으로 compound page로 구현됩니다. 연속된 2^order개의 물리 페이지(struct page)를 하나의 논리적 단위로 묶어, 첫 번째 페이지가 Head 페이지, 나머지가 Tail 페이지가 됩니다.

compound page 구조 (order-9, 2MB Huge Page) Head Page PG_head 플래그 _refcount compound_order=9 Tail[0] PG_tail compound_head → Head Tail[1] PG_tail compound_head → Head ... Tail[510] PG_tail compound_head → Head Tail[511] PG_tail compound_head → Head 총 512개 Tail 페이지 (각 4KB) = 512 x 4KB = 2MB compound_dtor free_compound_page() 또는 free_transhuge_page() 소멸자 compound_mapcount 전체 compound page 매핑 횟수 compound_pincount GUP(pin_user_pages) 핀 횟수 각 Tail 페이지의 compound_head 포인터는 Head 페이지를 가리켜 임의의 Tail 페이지에서 O(1)으로 Head 페이지를 찾을 수 있습니다.

Huge Page 할당 코드 경로

/* mm/hugetlb.c - Huge Page 할당 핵심 함수 */
static struct page *dequeue_huge_page_vma(
    struct hstate *h,
    struct vm_area_struct *vma,
    unsigned long address, int avoid_reserve,
    long chg)
{
    struct page *page;
    struct zonelist *zonelist;
    struct zone *zone;
    struct zoneref *z;
    nodemask_t *nodemask;
    int nid;

    /* NUMA 정책에 따른 할당 우선순위 결정 */
    nid = huge_node(vma, address, huge_page_shift(h), &nodemask);
    zonelist = node_zonelist(nid, htlb_alloc_mask(h));

    /* 여유 풀에서 페이지 꺼내기 */
    for_each_zone_zonelist_nodemask(zone, z, zonelist,
                                     MAX_NR_ZONES - 1, nodemask) {
        nid = zone_to_nid(zone);
        if (!list_empty(&h->hugepage_freelists[nid])) {
            page = list_entry(
                h->hugepage_freelists[nid].next,
                struct page, lru);
            list_move(&page->lru, &h->hugepage_activelist);
            set_page_refcounted(page);
            h->free_huge_pages--;
            h->free_huge_pages_node[nid]--;
            return page;
        }
    }
    return NULL;
}

페이지 테이블 구조 — PMD 직접 매핑

4KB vs 2MB 페이지 테이블 비교

일반 4KB 페이지는 4단계(PGD → PUD → PMD → PTE)의 페이지 테이블 워크가 필요하지만, 2MB Huge Page는 PMD에서 직접 물리 프레임을 가리키므로 PTE 레벨이 생략됩니다. 1GB Huge Page는 PUD에서 직접 매핑하여 PMD와 PTE 두 레벨을 모두 건너뜁니다.

x86_64 페이지 테이블 계층: 4KB vs 2MB vs 1GB 매핑 비교 4KB 페이지 (4단계) PGD PUD (P4D) PMD PTE 4KB 물리 페이지 2MB Huge Page (3단계) PGD PUD (P4D) PMD (PSE 비트=1) PTE (생략) 2MB 물리 프레임 1GB Huge Page (2단계) PGD PUD (PSE 비트=1) PMD (생략) PTE (생략) 1GB 물리 프레임 TLB 미스 시 메모리 접근 횟수 (페이지 테이블 워크) 4KB: 4회 PGD → PUD → PMD → PTE 2MB: 3회 PGD → PUD → PMD(직접) 1GB: 2회 PGD → PUD(직접)

PMD 엔트리 구조 (x86_64)

/* arch/x86/include/asm/pgtable_types.h */
/*
 * PMD 엔트리 비트 필드 (2MB Huge Page 매핑 시)
 *
 * [63]    NX (No Execute)
 * [62:52] 소프트웨어 사용
 * [51:21] 물리 프레임 번호 (2MB 정렬)
 * [20:13] PAT, 소프트웨어 예약
 * [12]    PAT (Page Attribute Table)
 * [11:9]  소프트웨어 사용 (linux: _PAGE_SOFT_DIRTY 등)
 * [8]     Global
 * [7]     PS (Page Size) = 1 → 2MB Huge Page 표시
 * [6]     Dirty
 * [5]     Accessed
 * [4]     PCD (Cache Disable)
 * [3]     PWT (Write Through)
 * [2]     U/S (User/Supervisor)
 * [1]     R/W (Read/Write)
 * [0]     Present
 */

#define _PAGE_BIT_PSE     7    /* Page Size Extension: 1=대형 페이지 */
#define _PAGE_PSE         (1UL << _PAGE_BIT_PSE)

/* PMD가 Huge Page인지 확인 */
static inline int pmd_large(pmd_t pmd)
{
    return pmd_flags(pmd) & _PAGE_PSE;
}

/* THP를 위한 PMD 생성 */
static inline pmd_t mk_huge_pmd(struct page *page, pgprot_t pgprot)
{
    return pfn_pmd(page_to_pfn(page),
                   __pgprot(pgprot_val(pgprot) | _PAGE_PSE));
}

Huge Page와 NUMA

NUMA 토폴로지(Topology)와 Huge Page 할당

NUMA(Non-Uniform Memory Access) 시스템에서 Huge Page의 할당 위치는 성능에 큰 영향을 미칩니다. 원격 NUMA 노드에서 할당된 Huge Page는 로컬 노드 대비 30~50% 더 높은 접근 지연시간을 보입니다. 커널은 NUMA 정책(mbind, set_mempolicy)과 연계하여 Huge Page 할당 노드를 결정합니다.

NUMA 시스템의 Huge Page 풀 구조 NUMA Node 0 CPU 0~15 (소켓 0) 메모리 컨트롤러 0 2MB HugePage 풀 (Node 0) hugepage_freelists[0]: 512개 예약 1GB HugePage 풀 (Node 0) hugepage_freelists[0]: 8개 예약 일반 4KB 페이지 + Buddy 시스템 NUMA Node 1 CPU 16~31 (소켓 1) 메모리 컨트롤러 1 2MB HugePage 풀 (Node 1) hugepage_freelists[1]: 512개 예약 1GB HugePage 풀 (Node 1) hugepage_freelists[1]: 8개 예약 일반 4KB 페이지 + Buddy 시스템 QPI/UPI 인터커넥트 (원격 접근 시 지연 증가) 로컬 접근: ~70ns 원격 접근: ~130ns

NUMA 정책과 Huge Page

/* NUMA 정책을 적용한 Huge Page 할당 예제 */
#include <numaif.h>
#include <sys/mman.h>

void *alloc_numa_hugepage(size_t size, int node)
{
    void *addr;
    unsigned long nodemask = 1UL << node;

    /* NUMA 바인드 정책 설정 */
    set_mempolicy(MPOL_BIND, &nodemask, sizeof(nodemask) * 8);

    /* Huge Page 할당 */
    addr = mmap(NULL, size,
                PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                -1, 0);

    /* 기본 정책 복원 */
    set_mempolicy(MPOL_DEFAULT, NULL, 0);

    return (addr == MAP_FAILED) ? NULL : addr;
}
# NUMA 노드별 Huge Page 예약 상태 확인
cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages
cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/free_hugepages

# numactl을 사용한 Huge Page 바인딩
numactl --membind=0 --hugepage ./my_application

HugeTLB 예약 시스템 — resv_map, subpool

예약(Reservation) 메커니즘

hugetlbfs에서 mmap()을 호출하면 즉시 물리 페이지를 할당하지 않고, 예약만 수행합니다. 이는 실제 페이지 폴트(Page Fault)가 발생할 때까지 물리 할당을 지연하되, 할당 실패가 발생하지 않도록 여유 페이지 수를 미리 확보하는 방식입니다.

/* include/linux/hugetlb.h - 예약 맵 구조체 */
struct resv_map {
    struct kref refs;
    spinlock_t lock;
    struct list_head regions;        /* 예약된 영역 리스트 */
    long adds_in_progress;            /* 진행 중인 추가 수 */
    struct list_head region_cache;    /* 영역 캐시 */
    long region_cache_count;
};

/* 예약 영역 단위 */
struct file_region {
    struct list_head link;
    long from;                        /* 시작 인덱스 (Huge Page 단위) */
    long to;                          /* 끝 인덱스 */
};

/* subpool: 마운트별 Huge Page 제한 */
struct hugepage_subpool {
    spinlock_t lock;
    long count;                       /* 현재 사용 중인 페이지 수 */
    long max_hpages;                  /* 최대 허용 페이지 수 (size= 옵션) */
    long used_hpages;
    struct hstate *hstate;
    long min_hpages;                  /* 최소 보장 페이지 수 (min_size= 옵션) */
    long rsv_hpages;                  /* 예약 중인 페이지 수 */
};
코드 설명
  • 3~8행 resv_map은 파일(inode)당 하나 생성됩니다. regions 리스트로 예약된 인덱스 범위를 추적합니다.
  • 12~15행 file_region은 연속된 예약 범위를 [from, to) 형태로 표현합니다. 범위가 겹치면 병합됩니다.
  • 19~26행 hugepage_subpool은 hugetlbfs 마운트 포인트별로 Huge Page 사용량을 제한합니다. mount -o size=4G로 설정합니다.

예약 흐름

  1. mmap(MAP_HUGETLB) 호출
  2. 커널이 hugetlb_reserve_pages()를 호출하여 필요한 Huge Page 수 계산
  3. resv_map에 예약 범위 추가
  4. 전역 풀에서 resv_huge_pages 카운터 증가
  5. subpool이 있으면 subpool 카운터도 증가
  6. 실제 페이지 폴트 시 alloc_huge_page()가 예약된 풀에서 할당
  7. munmap() 시 미사용 예약분 반환

Huge Page 마이그레이션과 compaction

THP 분할 (Split)

THP는 필요에 따라 512개의 4KB 페이지로 분할될 수 있습니다. 분할이 발생하는 주요 상황:

/* mm/huge_memory.c - THP 분할 핵심 */
int split_huge_page_to_list(struct page *page,
                            struct list_head *list)
{
    struct page *head = compound_head(page);
    struct deferred_split *ds_queue;
    int ret;

    /* Head 페이지의 참조 카운트(Reference Count) 확인 */
    if (!PageCompound(page))
        return 0;

    /* anon_vma 잠금 (역방향 매핑 보호) */
    anon_vma_lock_write(head->mapping);

    /* PMD 엔트리를 512개 PTE로 교체 */
    ret = __split_huge_page(page, list, end);

    /* 통계 업데이트 */
    if (!ret)
        count_vm_event(THP_SPLIT_PAGE);

    return ret;
}

Memory Compaction과 Huge Page

THP 할당이 실패하면 커널은 memory compaction을 시도합니다. Compaction은 사용 중인 4KB 페이지를 한쪽으로 이동시켜 연속된 빈 영역을 만들고, 이 영역에서 2MB compound page를 할당합니다.

# compaction 관련 통계
cat /proc/vmstat | grep compact
# compact_stall: compaction 대기 횟수
# compact_success: compaction 성공 횟수
# compact_fail: compaction 실패 횟수

# 수동 compaction 트리거
echo 1 > /proc/sys/vm/compact_memory

# proactive compaction 설정 (커널 5.9+)
echo 20 > /proc/sys/vm/compaction_proactiveness
compaction 비용: Memory compaction은 페이지 이동(Page Migration)을 수반하므로 CPU 시간과 I/O를 소비합니다. 실시간 시스템이나 지연 민감 워크로드에서는 defrag=never로 설정하여 compaction을 비활성화하고, 대신 hugetlbfs 사전 예약을 사용하는 것이 바람직합니다.

사용자 공간 인터페이스 — mmap, shmget, madvise

mmap을 통한 Huge Page 할당

/* MAP_HUGETLB를 사용한 Huge Page 할당 */
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>

#define HUGEPAGE_SIZE  (2 * 1024 * 1024)  /* 2MB */
#define NUM_PAGES      64

int main(void)
{
    size_t total_size = HUGEPAGE_SIZE * NUM_PAGES;  /* 128MB */

    /* 익명 Huge Page 할당 */
    void *addr = mmap(NULL, total_size,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB |
                      MAP_HUGE_2MB,    /* 2MB 명시 */
                      -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    /* 메모리 사용 */
    memset(addr, 0xAB, total_size);
    printf("Huge Pages 할당 성공: %p, 크기: %zu MB\n",
           addr, total_size / (1024 * 1024));

    /* 해제 */
    munmap(addr, total_size);
    return 0;
}

shmget을 통한 공유 Huge Page

/* System V 공유 메모리로 Huge Page 사용 */
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE  (256 * 1024 * 1024)  /* 256MB */

int shmid = shmget(IPC_PRIVATE, SHM_SIZE,
                   IPC_CREAT | SHM_HUGETLB | 0666);
if (shmid < 0) {
    perror("shmget SHM_HUGETLB");
    return 1;
}

void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) {
    perror("shmat");
    return 1;
}

/* 사용 후 해제 */
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);

madvise를 통한 THP 힌트

최신 사용자 공간 인터페이스에서 가장 중요한 변화는 MADV_COLLAPSE입니다. 이것은 해당 범위를 즉시 PMD 크기로 collapse하도록 요청하는 동기식 힌트이며, 일반적인 sysfs never 정책보다 우선합니다. 다만 MADV_NOHUGEPAGE가 설정된 영역에서는 거부되며, 파일 매핑은 hugepage 경계 정렬과 파일 정렬 같은 추가 조건을 만족해야 합니다.

/* THP madvise 모드에서 선택적 활성화 */
#include <sys/mman.h>

void *addr = mmap(NULL, size,
                  PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS,
                  -1, 0);

/* 이 영역에 THP를 적용해달라고 커널에 힌트 */
madvise(addr, size, MADV_HUGEPAGE);

/* THP를 비활성화하려면 */
madvise(addr, size, MADV_NOHUGEPAGE);

/* 즉시 PMD-sized collapse 시도 (커널 6.1+) */
madvise(addr, size, MADV_COLLAPSE);

/* THP 분할을 유도할 수 있는 reclaim 힌트 */
madvise(addr, size, MADV_PAGEOUT);   /* 스왑 아웃 힌트 (분할 수반) */
madvise(addr, size, MADV_COLD);      /* 비활성 페이지로 표시 */

커널 설정과 부팅 파라미터

Kconfig 옵션

# Huge Pages 기본 지원
CONFIG_HUGETLBFS=y           # hugetlbfs 파일시스템 활성화
CONFIG_HUGETLB_PAGE=y        # Huge Page 인프라 (자동 선택됨)

# Transparent Huge Pages
CONFIG_TRANSPARENT_HUGEPAGE=y          # THP 지원 활성화
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y   # 기본 모드: always
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y  # 기본 모드: madvise

# Huge Page 관련 고급 옵션
CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y # HVO: vmemmap 최적화 (커널 6.1+)
CONFIG_ARCH_HAS_HUGEPD=y               # 아키텍처별 HugePD 지원
CONFIG_HUGETLB_PAGE_SIZE_VARIABLE=y    # 가변 Huge Page 크기 지원

# compaction 관련
CONFIG_COMPACTION=y          # 메모리 compaction (THP에 필수)
CONFIG_MIGRATION=y           # 페이지 마이그레이션 지원

부팅 파라미터

파라미터설명예제
hugepagesz= Huge Page 크기 지정 hugepagesz=2M, hugepagesz=1G
hugepages= 지정된 크기의 예약 페이지 수 hugepages=1024
default_hugepagesz= 기본 Huge Page 크기 default_hugepagesz=2M
hugepage_alloc_threads= 부팅 중 비-gigantic HugeTLB 페이지 할당에 사용할 스레드(Thread) 수 hugepage_alloc_threads=8
hugetlb_free_vmemmap= HVO(HugeTLB Vmemmap Optimization) on/off hugetlb_free_vmemmap=on
transparent_hugepage= top-level THP 기본 모드 transparent_hugepage=madvise
thp_anon= 익명 메모리 THP를 크기별로 always/madvise/never/inherit 지정 thp_anon=64K:always;2M:inherit
transparent_hugepage_shmem= 내부 shmem 마운트의 전역 hugepage 정책 transparent_hugepage_shmem=within_size
transparent_hugepage_tmpfs= tmpfs 기본 hugepage 정책 transparent_hugepage_tmpfs=within_size
thp_shmem= shmem hugepage를 크기별로 always/within_size/advise/never/inherit 지정 thp_shmem=64K:within_size;2M:inherit
hugepages=0:512,1:512 NUMA 노드별 HugeTLB 예약 Node0에 512개, Node1에 512개
# GRUB 부팅 파라미터 예제 (/etc/default/grub)
GRUB_CMDLINE_LINUX="default_hugepagesz=2M hugepagesz=2M hugepages=0:1024,1:1024 hugepagesz=1G hugepages=8 transparent_hugepage=madvise thp_anon=64K:always;2M:inherit transparent_hugepage_shmem=within_size thp_shmem=64K:within_size;2M:inherit hugepage_alloc_threads=8 hugetlb_free_vmemmap=on"

# 효과: 2MB HugeTLB를 NUMA 노드별로 사전 예약하고, THP는 madvise 기본 + 64KB mTHP 우선
주의: thp_anon= 또는 thp_shmem=를 한 번이라도 명시하면, 명령줄에서 따로 지정하지 않은 크기들은 암묵적으로 never가 됩니다. 따라서 2MB PMD 크기를 계속 쓰려면 해당 크기도 명시적으로 포함해야 합니다.

Huge Pages의 단점과 주의사항

앞에서 살펴본 TLB 효율 향상은 분명히 실재하지만, Huge Pages가 모든 워크로드에서 항상 유리한 것은 아닙니다. 잘못 설정하면 오히려 메모리를 낭비하거나 레이턴시(Latency) 스파이크(Spike)를 유발할 수 있습니다. 이 섹션에서는 Huge Pages의 구조적 단점 네 가지를 원리 수준에서 살펴보고, 워크로드별 사용 결정 기준을 정리합니다.

내부 단편화 — 큰 페이지가 초래하는 메모리 낭비

메모리 할당에서 내부 단편화(Internal Fragmentation)란 할당된 블록의 일부가 실제로 사용되지 않아 낭비되는 현상입니다. 4KB 페이지에서는 요청 크기가 4KB 경계에 올림(반올림)될 때 평균 약 2KB의 낭비가 발생합니다. 그러나 2MB Huge Page에서는 이 낭비가 최대 약 2MB(1,999,999바이트)까지 커집니다.

# 내부 단편화 계산 예시 — 1,024개의 소규모 프로세스 환경
프로세스당 실제 사용 메모리: 4.1 MB

[4KB 페이지 사용 시]
  필요 페이지 수: ceil(4.1MB / 4KB) = 1,026개
  실제 할당량:    1,026 × 4KB    = 4.102 MB/프로세스
  전체 할당량:    1,024 × 4.102  ≈ 4,200 MB ≈ 4.1 GB  (낭비: ~2MB 전체)

[2MB Huge Page 사용 시]
  필요 Huge Page: ceil(4.1MB / 2MB) = 3개
  실제 할당량:    3 × 2MB        = 6 MB/프로세스
  전체 할당량:    1,024 × 6MB   = 6,144 MB ≈ 6.0 GB  (낭비: ~1.9GB 추가)

결론: 소규모 프로세스 1,024개 실행 시 Huge Page 사용으로 메모리가 오히려 ~1.9 GB 더 소모됨
실전 영향: 수백~수천 개의 컨테이너(Container)가 동시에 실행되는 환경, 또는 짧은 수명의 소규모 프로세스가 많은 서버에서 THP를 always로 설정하면 시스템 전체 메모리 사용량이 예상보다 20~50% 이상 늘어날 수 있습니다. /proc/meminfoAnonHugePages를 정기 모니터링하세요.

페이지 폴트 레이턴시 스파이크

새로운 익명 페이지가 최초 접근될 때 커널은 해당 물리 페이지를 0으로 초기화합니다 (보안상 이전 프로세스의 데이터 노출 방지). 4KB 페이지 하나를 0으로 초기화하는 데는 약 1µs가 소요되지만, 2MB THP는 512개 페이지를 한꺼번에 초기화하므로 최대 약 500µs(0.5ms)까지 걸릴 수 있습니다. 이 차이는 평균 응답 시간보다 p99·p999 분위수 지연(Tail Latency)에 크게 나타납니다.

페이지 크기초기화 크기폴트 레이턴시레이턴시 영향
4KB4KB~1µs무시 가능
2MB THP2MB (512×)~200~500µsp99 지연 증가 가능
1GB HugeTLB (미리 예약)사전 초기화 완료~0µs (예약 시 완료)없음
hugetlbfs의 이점: nr_hugepages로 미리 예약한 HugeTLB 페이지는 예약 시점에 0으로 초기화되므로, 이후 mmap(MAP_HUGETLB) 폴트 시 초기화 비용이 발생하지 않습니다. 레이턴시 민감 워크로드에서 THP 대신 hugetlbfs가 선호되는 이유 중 하나입니다.

fork()와 Copy-on-Write — 복사 비용 512배 증폭

fork()는 부모 프로세스의 모든 페이지를 자녀와 공유합니다. 자녀가 공유 중인 페이지에 쓰기를 시도하면 Copy-on-Write(CoW)가 발생하여 해당 페이지를 별도 복사본으로 만듭니다. 4KB 페이지에서는 4KB만 복사되지만, 2MB THP에서는 compound page 전체(2MB)를 복사해야 합니다. 단 1바이트 쓰기도 2MB 복사를 유발하는 것입니다.

페이지 크기 CoW 발생 시 복사량 소요 시간 대표 영향 사례
4KB 일반 페이지 4KB ~1µs 이하 무시 가능
2MB THP 2MB (512×) ~200~500µs Redis BGSAVE fork 후 p99 지연 수십 ms 급등
1GB hugetlbfs 1GB (262,144×) ~50~200ms 대형 VM fork 시 장시간 응답 없음

Redis가 BGSAVE(Background Save) 중 fork()를 호출하면 부모(메인 서버)는 계속 클라이언트 요청을 처리하면서 메모리를 변경합니다. THP가 활성화된 경우 변경이 발생할 때마다 2MB 단위 CoW가 연속 발생하여 메모리 사용량이 급증하고 p99 레이턴시가 수십 ms 급등할 수 있습니다. HugeTLB Copy-on-Write 상세 섹션에서 커널 구현을 확인할 수 있습니다.

khugepaged 백그라운드 CPU 소모

khugepaged는 주기적으로 전체 프로세스의 가상 메모리를 스캔하여 조건을 충족하는 4KB 페이지들을 2MB THP로 병합합니다. 이 스캔 자체가 CPU를 소모하며, 기본 설정(pages_to_scan=4096, scan_sleep_millisecs=10000)에서 수백~수천 개의 프로세스가 있는 서버에서 CPU 사용률이 5~10%까지 오르는 경우가 있습니다.

# khugepaged CPU 사용 확인
pidstat -p $(pgrep khugepaged) 1 10
top -b -n1 | grep khugepaged

# khugepaged 스캔 부하 완화
echo 1024  > /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan   # 기본 4096 → 줄임
echo 30000 > /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs  # 기본 10000 → 늘림

# THP 자체를 madvise로만 제한하면 khugepaged 스캔 범위도 축소됨
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

워크로드별 Huge Pages 사용 결정 가이드

아래 표는 대표적인 워크로드에서 THP 및 hugetlbfs의 권장 설정을 정리합니다. "무조건 켜면 빠르다"는 오해를 피하고 워크로드 특성에 맞게 선택해야 합니다.

워크로드 권장 설정 이유
데이터베이스 버퍼 풀 (PostgreSQL, MySQL) hugetlbfs 정적 예약 대용량 연속 메모리, CoW 없음, TLB 효과 최대, 레이턴시 안정
고성능 패킷 처리 (DPDK) 1GB hugetlbfs 부팅 파라미터 DMA 패킷 버퍼가 물리적 연속성과 핀(pin) 요구
KVM/QEMU 가상 머신 THP 또는 hugetlbfs 모두 유효 VM 메모리가 크고 연속적, TLB 효과 큼
과학 계산 / HPC / 행렬 연산 THP always 또는 hugetlbfs 대용량 연속 배열 순차 접근, CoW 없음, TLB 효과 극대
Redis / Memcached THP never fork() 기반 BGSAVE/AOF rewrite 중 CoW로 메모리 급증·지연 스파이크
JVM / Java (GC 기반) THP never 또는 madvise로 제한 가비지 컬렉터(Garbage Collector)가 4KB 단위로 해제 → THP 2MB 단위 관리와 충돌, 단편화 심화
Nginx / 다수의 단기 연결 서버 THP never 권장 단기 할당·해제 반복 → 내부 단편화 심각, khugepaged 스캔 낭비
실시간(Real-time) 시스템 THP never + hugetlbfs 사전 예약 compaction·khugepaged 비결정적 지연이 실시간 데드라인(Deadline)을 위반할 수 있음
컨테이너 다수 실행 (수백~수천 개) THP madvise로 제한 컨테이너별 소규모 메모리 → 내부 단편화 및 khugepaged 오버헤드
# Redis — THP 비활성화 (운영 필수 설정)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# /etc/rc.local 또는 systemd ExecStartPost로 영구 적용

# Java 애플리케이션 — 전역 또는 프로세스 단위 제어
# 전역 비활성화:
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
# 특정 Java 프로세스만 비활성화 (PR_SET_THP_DISABLE syscall):
cat <<'EOF' > /tmp/thp_disable.c
#include <sys/prctl.h>
int main() {
    prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);
    execvp(...);  /* Java 실행 */
}
EOF

# 실시간 시스템 — THP 완전 비활성화 + hugetlbfs 사전 예약
echo never  > /sys/kernel/mm/transparent_hugepage/enabled
echo never  > /sys/kernel/mm/transparent_hugepage/defrag
echo 256    > /proc/sys/vm/nr_hugepages  # 512MB 사전 예약

성능 벤치마크와 튜닝

TLB 미스율 비교 벤치마크

아래 차트는 대규모 메모리 접근 워크로드에서 4KB 페이지와 2MB THP, 1GB hugetlbfs의 상대적 TLB 미스율과 처리량(Throughput) 차이를 보여줍니다.

16GB 메모리 랜덤 접근 벤치마크: TLB 미스율 및 처리량 비교 TLB 미스율 (상대값 %) 100% 75% 50% 25% 0% 100% 4KB 기준값 8% 2MB THP 6% 2MB hugetlbfs 0.4% 1GB hugetlbfs 상대 처리량 1.0x 4KB 1.15x THP 1.20x 2MB 1.30x 1GB 4KB 일반 2MB THP 2MB hugetlbfs 1GB hugetlbfs

perf를 활용한 TLB 미스 측정

# TLB 미스 이벤트 측정
perf stat -e dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads \
    -e dTLB-store-misses,dTLB-stores \
    ./my_application

# 출력 예시:
#  12,345,678  dTLB-load-misses     # 0.45% of all dTLB loads
#  2,741,234,567  dTLB-loads
#  1,234,567  iTLB-load-misses

# THP 활성화 후 동일 측정으로 미스율 비교
echo always > /sys/kernel/mm/transparent_hugepage/enabled
perf stat -e dTLB-load-misses,dTLB-loads ./my_application

# 페이지 워크 사이클 측정 (Intel 프로세서)
perf stat -e cpu/event=0x08,umask=0x01,name=dtlb_load_misses_walk_completed/ \
    ./my_application

주요 튜닝 가이드라인

워크로드추천 설정이유
데이터베이스 (PostgreSQL, MySQL) THP=madvise, defrag=madvise DB가 공유 버퍼에만 선택적으로 THP 적용
Redis / 인메모리 캐시 THP=never (hugetlbfs 사용 권장) THP 분할/병합의 지연 스파이크 방지
DPDK 패킷(Packet) 처리 hugetlbfs 1GB 전용 예약 최대 TLB 효율, 고정 할당 보장
KVM/QEMU VM THP=always 또는 hugetlbfs 백엔드 VM 메모리의 대부분이 대형 페이지 활용 가능
과학 계산 / HPC hugetlbfs 2MB/1GB 사전 예약 대규모 배열 순회 시 TLB 미스 최소화
일반 데스크탑 THP=always, defrag=defer+madvise 자동 최적화, 사용자 개입 불필요

모니터링과 디버깅(Debugging)

/proc/meminfo Huge Page 항목

# Huge Page 관련 /proc/meminfo 항목
cat /proc/meminfo | grep -i huge

# 출력 예시:
# AnonHugePages:    524288 kB    ← THP로 매핑된 익명 메모리
# ShmemHugePages:        0 kB    ← THP로 매핑된 공유 메모리
# FileHugePages:         0 kB    ← THP로 매핑된 파일 캐시
# HugePages_Total:    1024       ← 예약된 총 Huge Pages 수
# HugePages_Free:      512       ← 미사용 Huge Pages 수
# HugePages_Rsvd:      256       ← 예약되었지만 아직 할당 안 된 수
# HugePages_Surp:        0       ← surplus (초과 할당) 수
# Hugepagesize:       2048 kB    ← 기본 Huge Page 크기
# Hugetlb:          2097152 kB   ← HugeTLB에 사용 중인 총 메모리

/sys 파일시스템 인터페이스

# hugetlbfs 풀 상태
ls /sys/kernel/mm/hugepages/
# hugepages-1048576kB/  hugepages-2048kB/

# 2MB Huge Page 상세 정보
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages_mempolicy
cat /sys/kernel/mm/hugepages/hugepages-2048kB/free_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/surplus_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/demote_size

# THP top-level 상태 및 설정
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never

# THP 크기별 정책
ls /sys/kernel/mm/transparent_hugepage/hugepages-*
cat /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
cat /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
cat /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/shmem_enabled

cat /sys/kernel/mm/transparent_hugepage/defrag
cat /sys/kernel/mm/transparent_hugepage/shmem_enabled
cat /sys/kernel/mm/transparent_hugepage/shrink_underused
cat /sys/kernel/mm/transparent_hugepage/use_zero_page
cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size
# 2097152 (= 2MB)

# khugepaged 통계
cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
cat /sys/kernel/mm/transparent_hugepage/khugepaged/full_scans

프로세스별 Huge Page 사용 현황

# 특정 프로세스의 smaps에서 Huge Page 매핑 확인
cat /proc/<pid>/smaps | grep -E "(AnonHugePages|ShmemPmd|FilePmd)"

# 간략한 요약
cat /proc/<pid>/smaps_rollup | grep -i huge
# AnonHugePages:    262144 kB

# 프로세스의 THP 사용 비율 계산
# AnonHugePages / (Anonymous 총 메모리) x 100 = THP 활용률

# 모든 프로세스의 Huge Page 사용량 정렬
for pid in /proc/[0-9]*; do
    hp=$(grep AnonHugePages "$pid/smaps_rollup" 2>/dev/null | awk '{print $2}')
    [ -n "$hp" ] && [ "$hp" -gt 0 ] && \
        echo "$hp kB  $(cat $pid/comm 2>/dev/null)  (PID: $(basename $pid))"
done | sort -rn | head -20

/proc/vmstat THP 통계

# THP 관련 vmstat 카운터
grep thp /proc/vmstat

# 주요 카운터 의미:
# thp_fault_alloc         - 페이지 폴트 시 THP 할당 성공
# thp_fault_fallback      - 페이지 폴트 시 THP 할당 실패 (4KB 폴백)
# thp_collapse_alloc      - khugepaged 병합 성공
# thp_collapse_alloc_failed - khugepaged 병합 실패
# thp_split_page          - THP 분할 (page 단위)
# thp_split_pmd           - PMD 분할 (매핑 단위)
# thp_zero_page_alloc     - 제로 THP 할당
# thp_deferred_split_page - 분할 지연 대기 중인 THP
# thp_swpout             - 스왑 아웃된 THP 수
# thp_swpout_fallback    - THP 스왑 아웃 실패 (분할 후 스왑)

실전 사용 사례

데이터베이스 (PostgreSQL)

PostgreSQL의 공유 버퍼(shared_buffers)는 대규모 메모리를 사용하므로 Huge Pages 적용 시 상당한 성능 향상을 얻을 수 있습니다.

# PostgreSQL Huge Pages 설정

# 1. 필요한 Huge Pages 수 계산
# shared_buffers = 8GB일 때: 8GB / 2MB = 4096개
# 여유분 포함: 4096 + 100 = 4196개
echo 4196 > /proc/sys/vm/nr_hugepages

# 2. PostgreSQL 설정 (postgresql.conf)
# huge_pages = try   # 또는 on (실패 시 시작 불가)
# shared_buffers = 8GB

# 3. postgres 사용자에게 huge page 권한 부여
# /etc/sysctl.conf:
# vm.hugetlb_shm_group = <postgres GID>

# 4. 확인
grep -i huge /proc/meminfo

DPDK 고성능 패킷 처리

DPDK(Data Plane Development Kit)는 커널을 우회하는 사용자 공간 패킷 처리 프레임워크로, 1GB Huge Pages를 사용하여 TLB 미스를 최소화하고 패킷 버퍼 접근 속도를 극대화합니다.

# DPDK용 1GB Huge Pages 설정

# 1. 부팅 파라미터 설정
# hugepagesz=1G hugepages=16 default_hugepagesz=1G

# 2. 또는 런타임에 2MB Huge Pages 예약
echo 8192 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# 3. hugetlbfs 마운트
mkdir -p /dev/hugepages-1G
mount -t hugetlbfs -o pagesize=1G none /dev/hugepages-1G

mkdir -p /dev/hugepages-2M
mount -t hugetlbfs -o pagesize=2M none /dev/hugepages-2M

# 4. DPDK EAL 파라미터
./dpdk-app --socket-mem 4096,4096 --huge-dir /dev/hugepages-1G

# 5. DPDK 메모리 정보 확인
dpdk-proc-info -- --stats

KVM/QEMU 가상 머신

KVM 가상 머신의 메모리를 Huge Pages로 백엔드하면 VM 내부의 메모리 접근 성능이 크게 향상됩니다. 특히 중첩 페이지 테이블(EPT/NPT)을 사용하는 환경에서 Huge Pages는 2단계 변환의 TLB 미스를 함께 줄여줍니다.

# QEMU에서 hugetlbfs 백엔드 사용
qemu-system-x86_64 \
    -m 16G \
    -mem-path /dev/hugepages-2M \
    -mem-prealloc \
    -smp 8 \
    -enable-kvm \
    ...

# libvirt XML 설정 (hugepages 사용)
# <memoryBacking>
#   <hugepages>
#     <page size="2048" unit="KiB"/>
#   </hugepages>
# </memoryBacking>

# NUMA별 Huge Page 크기 지정 (libvirt)
# <memoryBacking>
#   <hugepages>
#     <page size="1048576" unit="KiB" nodeset="0"/>
#     <page size="2048" unit="KiB" nodeset="1"/>
#   </hugepages>
# </memoryBacking>
워크로드별 Huge Page 추천 구성 Huge Pages 2MB / 1GB 대형 페이지 데이터베이스 hugetlbfs + THP=madvise DPDK / SmartNIC hugetlbfs 1GB 전용 KVM / QEMU hugetlbfs + THP=always HPC / 과학 계산 hugetlbfs 2MB/1GB Redis / 캐시 THP=never, hugetlbfs 일반 서버/데스크탑 THP=always, defrag=defer THP의 분할/병합 지연이 문제되는 지연 민감 워크로드는 hugetlbfs 정적 예약을 우선 고려
Redis와 THP 경고: Redis는 공식적으로 THP 비활성화를 권장합니다. THP의 분할/병합 과정에서 발생하는 지연 스파이크가 Redis의 밀리초 단위 응답 시간에 악영향을 미칩니다. Redis 서버에서는 반드시 echo never > /sys/kernel/mm/transparent_hugepage/enabled를 실행하세요. Redis 시작 시 이 설정을 경고하는 로그도 출력됩니다.

HVO — HugeTLB Vmemmap Optimization

HugeTLB 페이지는 실제 데이터 페이지 외에도 메타데이터(struct page) 배열이 필요합니다. 1GB Huge Page 하나는 4KB 페이지 262,144개로 구성되므로, 기본 방식에서는 struct page도 같은 수만큼 필요합니다. 커널 6.x의 HVO(HugeTLB Vmemmap Optimization)는 Tail 페이지의 vmemmap을 공유/재활용(Recycling)하여 메타데이터 메모리 오버헤드(Overhead)를 크게 줄입니다.

HugeTLB vmemmap 오버헤드와 HVO 최적화 기본 방식 (최적화 없음) 1GB Huge Page = 262,144개의 struct page Head + 모든 Tail 페이지 각각 vmemmap 엔트리 유지 메타데이터 메모리 소비: 262,144 x sizeof(struct page) (대략 수 MB) Huge Page를 많이 쓰는 시스템에서 vmemmap 자체가 의미 있는 메모리 압박 요인 HVO 활성화 Tail 페이지 vmemmap 중복분 제거/재활용 필수 엔트리만 남기고 나머지는 재매핑/해제 효과: HugeTLB 메타데이터 오버헤드 감소 대규모 1GB Huge Page 풀에서 체감 큼 조건: CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y
# HVO 지원 여부 점검
grep HUGETLB_PAGE_OPTIMIZE_VMEMMAP /boot/config-$(uname -r)
grep -w hugetlb_free_vmemmap /proc/cmdline

# 커널 로그에서 HugeTLB/VMEMMAP 관련 메시지 확인
dmesg | grep -Ei 'hugetlb|vmemmap|hvo'
최신 HVO 포인트: 현재 커널 파라미터 hugetlb_free_vmemmap=on으로 HVO를 명시적으로 활성화할 수 있으며, 문서 기준으로 2MB HugeTLB 페이지당 7 * PAGE_SIZE 정도의 메타데이터 메모리를 절약할 수 있습니다.

cgroup v2와 컨테이너 Huge Pages

컨테이너 환경에서는 Huge Pages를 일반 메모리와 별도로 제한해야 합니다. cgroup v2는 페이지 크기별 hugepage 한도를 제공하며, overcommit 정책과 결합해 특정 워크로드가 HugeTLB 풀을 독점하지 않도록 제어합니다. 기본적으로 HugeTLB 사용량은 hugetlb.<size>.current로 별도 회계되지만, 최신 커널에서는 memory_hugetlb_accounting 마운트 옵션을 통해 memory.current에도 함께 반영할 수 있습니다.

cgroup v2 HugeTLB 제어 계층 시스템 HugeTLB 풀 2MB / 1GB 전역 페이지 cgroup A (DB) hugetlb.2MB.max=8G hugetlb.1GB.max=4G cgroup B (DPDK) hugetlb.2MB.max=2G hugetlb.1GB.max=8G 핵심 포인트 기본적으로 별도 회계, memory_hugetlb_accounting 시 memory.current에도 반영 hugetlb.max 초과는 즉시 실패, memory controller charge 실패는 SIGBUS 가능
# cgroup v2 HugeTLB 한도 예시
CG=/sys/fs/cgroup/mydb
mkdir -p $CG
echo $((8*1024*1024*1024))  > $CG/hugetlb.2MB.max
echo $((4*1024*1024*1024))  > $CG/hugetlb.1GB.max

# 사용량/실패 통계
cat $CG/hugetlb.2MB.current
cat $CG/hugetlb.2MB.events
cat $CG/hugetlb.2MB.events.local

# cgroup를 memory_hugetlb_accounting으로 마운트했다면
cat $CG/memory.current
grep '^hugetlb ' $CG/memory.stat

THP 분할과 NUMA demotion 경로

THP는 항상 유지되는 것이 아니라, 메모리 압박·NUMA 재배치(Relocation)·mprotect/munmap 이벤트로 분할될 수 있습니다. 특히 자동 NUMA balancing이 켜진 시스템에서는 원격 노드 접근 패턴에 따라 페이지 이동이 발생하고, 이 과정에서 PMD 단위 매핑이 PTE 단위로 강등될 수 있습니다.

THP 생애주기: 할당 → 유지 → 분할/재병합 PMD THP 생성 thp_fault_alloc++ 정상 실행 TLB 효율 최대 분할 이벤트 split_huge_page PTE 512개 4KB 모드 khugepaged 조건 충족 시 재병합 분할 유발 주요 원인 mprotect/munmap 부분 범위, swap, GUP pin, NUMA migration, 메모리 회수 분할이 잦으면 thp_split_* 카운터가 빠르게 증가
# THP 분할/병합 상태 추적
grep -E 'thp_split|thp_collapse|thp_fault' /proc/vmstat

# numa balancing과 함께 확인
grep -E 'numa_hint_faults|numa_pages_migrated' /proc/vmstat

Huge Pages 장애 대응 플레이북

Huge Pages 문제는 대부분 "할당 실패", "예상보다 낮은 THP 활용률", "지연 스파이크"로 나타납니다. 아래 절차로 원인을 분류하면 대응이 빨라집니다.

Huge Pages 문제 분석 절차 1) 증상 확인 mmap 실패/지연 스파이크 2) 풀 상태 점검 HugePages_Free/Rsvd 3) THP 통계 확인 thp_fault_fallback 4) 정책 조정 예약/defrag/모드 판정 기준 예시 MAP_HUGETLB 실패 + HugePages_Free=0: 예약 부족 또는 경쟁 사용 thp_fault_fallback 급증: compaction 실패/분산 메모리 단편화 thp_split_page 급증: workload 패턴상 분할 유발 (권한 변경/부분 unmap) 가능성
# 1) HugeTLB/THP 상태 요약
grep -i huge /proc/meminfo
grep -E 'thp_fault|thp_split|thp_collapse' /proc/vmstat

# 2) THP 모드/defrag 확인
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag

# 3) compaction 압력 확인
grep compact /proc/vmstat

# 4) 프로세스별 HugePages 사용 확인
cat /proc/<pid>/smaps_rollup | grep -i huge
운영 권장: 지연 민감 서비스는 THP를 전역 madvise로 두고, 필요한 프로세스에만 MADV_HUGEPAGE를 적용하는 방식이 가장 예측 가능성이 높습니다.

khugepaged collapse 실패 분석

THP 성능을 기대했는데 실제로는 일반 페이지로 남는 경우, 핵심은 khugepaged의 collapse 실패 원인을 분리하는 것입니다. 연속 가상 주소가 있어도 물리 단편화(Fragmentation), 참조 상태, 페이지 핀 고정 상태에 따라 collapse가 반복 실패할 수 있습니다.

khugepaged collapse 실패 분기 후보 VMA 스캔 khugepaged PMD 범위 512개 PTE 점검 mapcount/refcount/dirty 상태 collapse 성공/실패 다음 스캔 주기 주요 실패 원인 1) compaction 실패로 연속 물리 메모리 확보 불가 2) 장기 pin(FOLL_PIN/RDMA) 페이지 존재 3) 자주 split되는 접근 패턴 (부분 mprotect/munmap) 4) 정책 미스매치: madvise/always/never 설정 부적합
# khugepaged collapse 실패 원인 진단
# 1) collapse 성공/실패 통계 확인
grep -E 'thp_collapse_alloc|thp_collapse_alloc_failed' /proc/vmstat
# thp_collapse_alloc_failed가 높으면 물리 단편화 문제

# 2) compaction 상태 확인 — collapse는 연속 512페이지가 필요
grep -E 'compact_success|compact_fail|compact_stall' /proc/vmstat

# 3) 특정 프로세스의 THP 사용 현황
grep AnonHugePages /proc/<PID>/smaps_rollup  # 실제 THP 매핑 크기
grep -c 'AnonHugePages:        0' /proc/<PID>/smaps  # THP 미적용 VMA 수

# 4) khugepaged 스캔 속도 튜닝
cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan   # 스캔당 페이지 수
cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs  # 스캔 간격
echo 4096 > /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan  # 더 공격적 스캔
/* khugepaged collapse 핵심 경로 (mm/khugepaged.c) */
static int collapse_huge_page(struct mm_struct *mm,
                              unsigned long address, int referenced)
{
    /* 1단계: order-9 compound page 할당 시도 */
    new_page = khugepaged_alloc_page(hpage, gfp, node);
    if (!new_page)
        return SCAN_ALLOC_HUGE_PAGE_FAIL;  /* compaction 실패 */

    /* 2단계: 512개 PTE를 순회하며 복사 가능 여부 점검 */
    for (_address = address; _address < address + HPAGE_PMD_SIZE;
         _address += PAGE_SIZE) {
        /* 핀 고정, swap, 특수 페이지 등 점검 */
        if (page_count(page) != 1 + page_mapcount(page))
            return SCAN_PAGE_COUNT;  /* 참조 카운트 불일치 */
    }

    /* 3단계: 4KB 페이지 → 2MB compound page 복사 후 PMD 교체 */
    copy_user_highpage(new_page + i, page, _address, vma);
    set_pmd_at(mm, address, pmd, _pmd);  /* PMD 엔트리 갱신 */
}
코드 설명

collapse_huge_page()는 khugepaged가 512개의 연속 4KB 페이지를 하나의 2MB THP로 병합하는 핵심 함수입니다. 먼저 order-9 compound page를 할당하고, 대상 영역의 모든 PTE를 순회하며 복사 가능 여부(참조 카운트(Reference Count), 핀 상태, swap 여부)를 점검합니다. 모든 조건을 충족하면 데이터를 복사하고 PMD 엔트리를 갱신하여 직접 매핑으로 전환합니다.

HugeTLB 풀 운영 정책 (예약/버스(Bus)트/격리(Isolation))

HugeTLB는 사전 예약 방식이라 서비스별 용량 계획이 중요합니다. 버스트 트래픽이 있는 서비스는 최소 예약과 버스트 여유를 분리하고, 컨테이너 환경에서는 cgroup 제한과 함께 운영해야 안정적입니다.

운영 시나리오권장 정책실패 시 신호
고정 워크로드 DB정적 hugepages 예약 + 재부팅 시 일관 적용MAP_HUGETLB 실패 없음, 지연 안정
버스트 분석 작업기본 예약 + 버스트 한도 별도HugePages_Free 급락 후 복구 지연
컨테이너 다중 테넌트cgroup huge limit로 테넌트 격리일부 테넌트 과점유/기아(Starvation)
# HugeTLB 풀 상태 확인
grep -i huge /proc/meminfo

# THP collapse/split 동향
grep -E 'thp_fault|thp_collapse|thp_split' /proc/vmstat

# 서비스별 smaps_rollup에서 huge 사용량 확인
cat /proc/<pid>/smaps_rollup | grep -Ei 'AnonHuge|FileHuge|Hugetlb'

File THP와 워크로드 적합성 판단

최신 커널에서는 익명 메모리와 tmpfs/shmem이 여전히 THP의 주력 대상입니다. 다만 MADV_HUGEPAGEMADV_COLLAPSE를 통해 파일 기반 매핑에도 THP 효과를 노릴 수 있는 경로가 확대되고 있습니다. 하지만 모든 파일 매핑이 대상은 아니며, 비-tmpfs 파일은 hugepage 경계 정렬, 파일 내부 정렬, 그리고 read-only executable 매핑 같은 추가 조건을 만족해야 합니다.

적용 범위 주의: 자동 background collapse 설명의 중심은 여전히 익명 메모리와 tmpfs/shmem입니다. 일반 파일 매핑은 워크로드별 선별 적용과 계측이 필요하며, read-mostly 코드/데이터 매핑처럼 지역성이 높은 경우에 더 적합합니다.
File THP 적용 판단 흐름 파일 매핑 워크로드 read-mostly / random fault/reclaim 패턴 계측 file fault locality 평가 THP 정책 결정 적용/제외 분리 적합/비적합 예시 적합: 대형 순차 스캔 + 높은 재사용 지역성 + fault 집중 비적합: 랜덤 접근 + 잦은 split 유발 + reclaim 압력 높은 환경 결론: 전역 강제보다 워크로드별 선택 적용이 안정적 성능/메모리 효율/지연을 함께 측정해 의사결정
패턴기대 효과주의점
순차 읽기 중심 분석TLB miss 완화, fault 수 감소reclaim 시 큰 단위 회수 비용
랜덤 조회 캐시효과 제한적split/compaction 오버헤드 가능
혼합 워크로드프로세스/영역별 선택 적용 필요정책 일괄 적용 시 회귀 위험
# File THP 적합성 판단을 위한 계측
# 1) 파일 매핑의 THP 사용 현황 (최신 커널의 large folio/mTHP 포함)
grep -E 'FilePmdMapped|FileHugePages' /proc/meminfo

# 2) fault 패턴 분석 — 순차 vs 랜덤 판단
perf stat -e page-faults,dTLB-load-misses -p <PID> -- sleep 10

# 3) 프로세스별 madvise로 선택적 THP 적용
# 코드에서: madvise(addr, len, MADV_HUGEPAGE)  — 해당 영역만 THP 활성
# 코드에서: madvise(addr, len, MADV_NOHUGEPAGE) — 해당 영역 THP 비활성

# 4) 전역 THP를 madvise 모드로 전환 (워크로드별 선택 적용)
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
/* File THP 페이지 폴트 경로 (mm/filemap.c, 최신 커널 large folio 경로) */
static vm_fault_t filemap_map_folio_range(struct vm_fault *vmf,
                                          struct folio *folio,
                                          pgoff_t start, pgoff_t end)
{
    /* folio order에 따라 PTE 매핑 크기 결정
     * order-0: 4KB, order-4: 64KB (mTHP), order-9: 2MB
     */
    unsigned int nr_pages = folio_nr_pages(folio);
    /* 연속된 PTE에 folio 내 페이지를 매핑 */
    set_pte_range(vmf, folio, page, nr_pages, vmf->address);
}
코드 설명

filemap_map_folio_range()는 파일 매핑의 페이지 폴트 시 folio 단위로 PTE 매핑을 수행합니다. 최신 large folio/mTHP 경로에서는 folio의 order에 따라 64KB, 2MB 등 다양한 크기의 연속 매핑이 가능하지만, 실제로 파일 THP를 안정적으로 활용할 수 있는지는 파일 시스템, 커널 구성, 정렬 조건, 매핑 속성에 따라 달라집니다. 순차 접근 패턴의 워크로드에서는 큰 folio로 fault 수가 감소하여 성능이 향상되지만, 랜덤 접근에서는 불필요한 메모리 소비가 발생할 수 있습니다.

hugetlb_fault() 호출 체인

사용자 공간에서 MAP_HUGETLB로 매핑한 영역에 처음 접근하면 페이지 폴트가 발생합니다. 일반 페이지 폴트와 달리 Huge Page 폴트는 전용 처리 경로를 타고, 최종적으로 alloc_huge_page()에서 풀(Pool) 또는 Buddy 할당자로부터 대형 페이지를 획득합니다.

hugetlb_fault() 호출 체인 handle_mm_fault() mm/memory.c — 폴트 진입점 is_vm_hugetlb? No → __handle_mm_fault() Yes hugetlb_fault() mm/hugetlb.c — PTE 확인, 락 획득 huge_pte_none? No → hugetlb_wp() (CoW) Yes hugetlb_no_page() 신규 Huge Page 할당 + 매핑 alloc_huge_page() 예약 풀 우선 → surplus → buddy fallback dequeue_huge_page_vma() alloc_buddy_huge_page() freelist에서 꺼냄 (빠름) buddy에서 order-9/18 할당 (느림)

handle_mm_fault()는 VMA 플래그에서 VM_HUGETLB를 확인하면 일반 __handle_mm_fault() 대신 hugetlb_fault()를 호출합니다. 이 함수는 huge PTE를 조회한 뒤, 항목이 비어 있으면 hugetlb_no_page()로 분기하여 새 Huge Page를 할당하고 PTE에 기록합니다. 이미 PTE가 존재하면서 쓰기 폴트인 경우 hugetlb_wp()가 Copy-on-Write를 처리합니다.

/* mm/memory.c — 페이지 폴트 진입점 (간략화) */
vm_fault_t handle_mm_fault(struct vm_area_struct *vma,
                           unsigned long address,
                           unsigned int flags,
                           struct pt_regs *regs)
{
    /* ... */
    if (is_vm_hugetlb_page(vma))
        return hugetlb_fault(vma->vm_mm, vma, address, flags);

    return __handle_mm_fault(vma, address, flags);
}

/* mm/hugetlb.c — Huge Page 폴트 핸들러 (간략화) */
vm_fault_t hugetlb_fault(struct mm_struct *mm,
                         struct vm_area_struct *vma,
                         unsigned long address,
                         unsigned int flags)
{
    struct hstate *h = hstate_vma(vma);
    pte_t *ptep, entry;

    ptep = huge_pte_alloc(mm, vma, address, huge_page_size(h));
    if (!ptep)
        return VM_FAULT_OOM;

    entry = huge_ptep_get(ptep);

    if (huge_pte_none(entry))
        return hugetlb_no_page(mm, vma, address, ptep, flags);

    if (flags & FAULT_FLAG_WRITE && !huge_pte_write(entry))
        return hugetlb_wp(mm, vma, address, ptep, flags, ...);

    return 0;
}
코드 설명
  • 8행 is_vm_hugetlb_page()는 VMA의 vm_flagsVM_HUGETLB가 설정되어 있는지 확인합니다. hugetlbfs 위에 생성된 매핑이면 항상 참입니다.
  • 9행 Huge Page 전용 폴트 핸들러(Handler)로 분기합니다. 일반 __handle_mm_fault()와 완전히 다른 경로입니다.
  • 21행 hstate_vma()는 VMA에 연결된 hstate를 찾아 해당 Huge Page 크기(2MB/1GB)의 상태 정보를 반환합니다.
  • 23행 huge_pte_alloc()는 페이지 테이블에서 해당 가상 주소에 대응하는 huge PTE 슬롯을 확보합니다. 2MB의 경우 PMD 엔트리, 1GB의 경우 PUD 엔트리입니다.
  • 28행 PTE가 비어 있으면 최초 접근이므로 hugetlb_no_page()에서 새 Huge Page를 할당하고 매핑합니다.
  • 30~31행 쓰기 폴트인데 PTE가 읽기 전용(Read-Only)이면 Copy-on-Write(CoW)가 필요합니다. hugetlb_wp()가 새 Huge Page를 할당하고 내용을 복사합니다.

struct hstate 필드별 해설

struct hstate는 Huge Page 크기별 풀(Pool) 전체 상태를 관리하는 핵심 구조체입니다. include/linux/hugetlb.h에 정의되며, 시스템에서 지원하는 각 Huge Page 크기마다 하나의 hstate 인스턴스가 전역 배열 hstates[]에 존재합니다.

/* include/linux/hugetlb.h — struct hstate 전체 필드 (v6.x 기준) */
struct hstate {
    struct mutex resize_lock;              /* 풀 크기 변경 보호 */
    int next_nid_to_alloc;                  /* 다음 할당 대상 NUMA 노드 */
    int next_nid_to_free;                   /* 다음 해제 대상 NUMA 노드 */
    unsigned int order;                     /* buddy order (2MB=9, 1GB=18) */
    unsigned int demote_order;               /* 디모트 시 목표 order */
    unsigned long mask;                     /* 주소 정렬 마스크 */
    unsigned long max_huge_pages;            /* 허용 최대 페이지 수 */
    unsigned long nr_huge_pages;             /* 현재 총 Huge Pages 수 */
    unsigned long free_huge_pages;           /* 미사용 (freelist) 수 */
    unsigned long resv_huge_pages;           /* mmap 예약분 (guarantee) */
    unsigned long surplus_huge_pages;        /* 초과 할당분 (overcommit) */
    unsigned long nr_overcommit_huge_pages;  /* surplus 허용 한도 */
    struct list_head hugepage_activelist;    /* 사용 중인 페이지 목록 */
    struct list_head hugepage_freelists[MAX_NUMNODES]; /* 노드별 여유 목록 */
    unsigned int nr_huge_pages_node[MAX_NUMNODES];    /* 노드별 총 수 */
    unsigned int free_huge_pages_node[MAX_NUMNODES];  /* 노드별 여유 수 */
    unsigned int surplus_huge_pages_node[MAX_NUMNODES]; /* 노드별 surplus */
    char name[HSTATE_NAME_LEN];              /* "hugepages-2048kB" 등 */
};
필드타입역할관련 sysfs / procfs
orderunsigned intBuddy 할당자 order. 2MB = 9 (2^9 * 4KB), 1GB = 18 (2^18 * 4KB)-
nr_huge_pagesunsigned long현재 시스템에 존재하는 해당 크기 Huge Page 총 수/proc/meminfo HugePages_Total
free_huge_pagesunsigned longfreelist에 있는 미사용 Huge Page 수/proc/meminfo HugePages_Free
resv_huge_pagesunsigned longmmap 호출 시 예약되었으나 아직 폴트가 발생하지 않은 수/proc/meminfo HugePages_Rsvd
surplus_huge_pagesunsigned longovercommit 정책으로 부팅 예약 외에 추가 할당된 수/proc/meminfo HugePages_Surp
nr_overcommit_huge_pagesunsigned longsurplus 허용 상한. 이 수까지 buddy에서 동적 할당 허용nr_overcommit_hugepages
hugepage_freelists[]list_head[MAX_NUMNODES]NUMA 노드별 여유 Huge Page 리스트. dequeue_huge_page_vma()가 여기서 꺼냄-
hugepage_activelistlist_head현재 매핑되어 사용 중인 Huge Page 리스트-
resize_lockmutex풀 크기 변경(nr_hugepages 쓰기) 시 직렬화(Serialization)-
next_nid_to_alloc/freeint라운드 로빈(Round Robin) NUMA 노드 인덱스. 노드 간 균등 분배에 사용-
demote_orderunsigned int1GB를 2MB로 분할(demote)할 때 목표 order. v5.18+에서 지원demote_size
maskunsigned long주소 정렬 마스크. 2MB: ~(2MB-1), 1GB: ~(1GB-1)-
핵심 불변식(Invariant): nr_huge_pages = free_huge_pages + 사용중(active) + surplus_huge_pages에서의 사용분
free_huge_pages ≥ resv_huge_pages — 예약분은 항상 여유 풀에서 보장됩니다. 이 불변식이 깨지면 alloc_huge_page()-ENOSPC를 반환합니다.

struct hugetlbfs_inode_info 주요 필드

hugetlbfs 파일시스템에서 각 파일(inode)은 struct hugetlbfs_inode_info로 확장됩니다. 이 구조체는 Huge Page 예약 맵(Reservation Map), 정책 플래그, shared 매핑의 공유 상태 등을 inode 단위로 관리합니다. fs/hugetlbfs/inode.c에 정의되어 있습니다.

/* fs/hugetlbfs/inode.c — hugetlbfs inode 확장 정보 (간략화) */
struct hugetlbfs_inode_info {
    struct shared_policy policy;           /* NUMA 메모리 정책 */
    struct inode vfs_inode;                /* VFS inode (컨테이너 패턴) */
    unsigned int seals;                    /* F_SEAL_* 플래그 (memfd) */
};

/* include/linux/hugetlb.h — 파일별 예약 맵 */
struct resv_map {
    struct kref refs;                     /* 참조 카운트 */
    spinlock_t lock;                       /* 동시 접근 보호 */
    struct list_head regions;              /* file_region 리스트 */
    long adds_in_progress;                 /* 진행 중인 예약 추가 수 */
    struct list_head region_cache;         /* 미리 할당된 region 캐시 */
    long region_cache_count;               /* 캐시 내 region 수 */
    struct page_counter *reservation_counter; /* cgroup 예약 카운터 */
    unsigned long pages_per_hpage;         /* Huge Page당 base page 수 */
};
코드 설명
  • 3행 shared_policymbind()로 설정한 NUMA 정책을 구간별로 저장합니다. 프로세스가 아닌 파일 단위로 관리되므로 shared 매핑 시 모든 프로세스에 동일 정책이 적용됩니다.
  • 4행 vfs_inode는 컨테이너 패턴의 핵심입니다. container_of() 매크로(Macro)로 VFS inode 포인터에서 hugetlbfs_inode_info를 역참조(Dereference)할 수 있습니다.
  • 5행 sealsmemfd_create()로 생성한 hugetlbfs 파일에 F_SEAL_SHRINK, F_SEAL_GROW 등의 변경 제한을 설정합니다.
  • 9~17행 resv_map은 inode의 i_mapping->private_data에 저장됩니다. regions 리스트가 예약된 파일 오프셋(Offset) 범위를 file_region 구조체로 관리하며, mmap 시 예약을 추가하고 munmap 시 해제합니다.
  • 14행 region_cache는 예약 추가 시 메모리 할당 실패를 방지하기 위해 file_region을 미리 할당해 캐싱합니다. 원자적 컨텍스트에서도 안전하게 예약을 추가할 수 있습니다.
구조체.필드역할생성 시점
hugetlbfs_inode_info.policyNUMA 정책 트리 (red-black tree)alloc_inode()
hugetlbfs_inode_info.sealsmemfd seal 비트마스크memfd_create(MFD_HUGETLB)
resv_map.regions예약된 [from, to) 범위 리스트resv_map_alloc()
resv_map.adds_in_progress동시 예약 추가 트래킹 (race 방지)region_add() 진입 시
resv_map.region_cache미리 할당된 file_region 풀region_chg() 호출 시

hugetlb_no_page() 함수 구현 분석

hugetlb_no_page()는 Huge Page 폴트에서 가장 핵심적인 함수로, PTE가 비어 있는 상태(최초 접근)에서 새 Huge Page를 할당하고 페이지 테이블에 매핑하는 전체 과정을 수행합니다. mm/hugetlb.c에 위치하며, 약 200줄의 복잡한 함수이지만 핵심 흐름은 아래와 같이 요약할 수 있습니다.

/* mm/hugetlb.c — hugetlb_no_page() 핵심 흐름 (간략화) */
static vm_fault_t hugetlb_no_page(
    struct mm_struct *mm, struct vm_area_struct *vma,
    unsigned long address, pte_t *ptep,
    unsigned int flags)
{
    struct hstate *h = hstate_vma(vma);
    struct page *page;
    pte_t new_pte;
    bool new_page = false;

    /* 1단계: 페이지 캐시에서 기존 페이지 검색 (shared 매핑) */
    page = find_lock_page(vma->vm_file->f_mapping,
                           vma_hugecache_offset(h, vma, address));

    if (!page) {
        /* 2단계: 새 Huge Page 할당 */
        page = alloc_huge_page(vma, address, 0);
        if (IS_ERR(page))
            return VM_FAULT_SIGBUS;  /* 할당 실패 → SIGBUS */

        /* 3단계: 페이지 초기화 (zero fill) */
        clear_huge_page(page, address, pages_per_huge_page(h));
        __SetPageUptodate(page);
        new_page = true;

        /* shared 매핑이면 페이지 캐시에 삽입 */
        if (vma->vm_flags & VM_SHARED)
            hugetlb_add_to_page_cache(page, vma->vm_file->f_mapping, ...);
    }

    /* 4단계: PTE 구성 및 설정 */
    new_pte = make_huge_pte(vma, page, vma->vm_flags & VM_WRITE);
    set_huge_pte_at(mm, address, ptep, new_pte);

    /* 5단계: rmap 등록 (역매핑 정보) */
    if (new_page)
        page_add_new_anon_rmap(page, vma, address);
    else
        page_add_file_rmap(page, vma, true);

    return 0;
}
코드 설명
  • 7행 hstate_vma()로 이 VMA가 사용하는 Huge Page 크기(2MB/1GB)의 hstate를 가져옵니다.
  • 13~14행 shared 매핑의 경우 다른 프로세스가 이미 같은 파일 오프셋에 Huge Page를 할당했을 수 있으므로, 페이지 캐시(Page Cache)를 먼저 검색합니다.
  • 18행 alloc_huge_page()는 예약 풀 → surplus → buddy 순서로 Huge Page를 할당합니다. 실패 시 ERR_PTR(-ENOSPC)를 반환합니다.
  • 20행 할당 실패 시 VM_FAULT_SIGBUS를 반환하여 프로세스에 SIGBUS 시그널(Signal)을 전달합니다. 일반 페이지 폴트의 OOM killer 호출과는 다른 처리입니다.
  • 23행 clear_huge_page()는 보안상 필수입니다. 이전 사용자의 데이터가 남아있을 수 있으므로 전체 2MB/1GB를 0으로 채웁니다.
  • 28~29행 shared 매핑이면 페이지 캐시에 삽입하여 다른 프로세스가 같은 파일 오프셋에 접근할 때 이 페이지를 재사용할 수 있게 합니다.
  • 32행 make_huge_pte()는 물리 페이지 프레임(Page Frame) 번호(PFN)와 보호 비트를 결합하여 huge PTE 값을 생성합니다.
  • 33행 set_huge_pte_at()는 아키텍처별 함수로, PMD(2MB) 또는 PUD(1GB) 엔트리에 huge PTE를 기록합니다.
  • 36~39행 rmap(역매핑)을 등록하여 나중에 페이지 회수나 마이그레이션 시 이 페이지를 매핑한 모든 VMA를 찾을 수 있게 합니다.

THP do_huge_pmd_anonymous_page() 및 khugepaged collapse 경로

THP(Transparent Huge Pages)는 hugetlbfs와 전혀 다른 경로로 대형 페이지를 생성합니다. 두 가지 주요 진입점(Entry Point)이 있습니다:

  1. 즉시 할당: 익명 페이지(Anonymous Page) 폴트 시 do_huge_pmd_anonymous_page()가 직접 2MB를 할당
  2. 지연 병합: khugepaged 데몬이 기존 4KB 페이지 512개를 하나의 THP로 collapse
THP 생성 경로: 즉시 할당 vs khugepaged collapse 경로 1: 즉시 할당 (페이지 폴트) handle_pte_fault() do_huge_pmd_anonymous_page() alloc_pages_vma(order=9, GFP_TRANSHUGE) set_pmd_at() → 2MB PMD 매핑 완성 실패 시: VM_FAULT_FALLBACK → 일반 4KB do_anonymous_page() 경로 2: 지연 병합 (khugepaged) khugepaged_scan_mm_slot() hpage_collapse_scan_pmd() collapse_huge_page() 512개 PTE → 1개 PMD 교체 기존 4KB 페이지 내용 복사 후 해제 실패 조건: 혼합 NUMA, mlock, 핀, shared → max_ptes_none/shared 임계값 초과 시 skip 결과: PMD 엔트리가 2MB 물리 프레임을 직접 가리킴

do_huge_pmd_anonymous_page() 핵심 흐름

/* mm/huge_memory.c — THP 익명 페이지 폴트 (간략화) */
vm_fault_t do_huge_pmd_anonymous_page(struct vm_fault *vmf)
{
    struct vm_area_struct *vma = vmf->vma;
    gfp_t gfp;
    struct page *page;
    unsigned long haddr = vmf->address & HPAGE_PMD_MASK;

    /* VMA가 2MB 정렬 범위를 충분히 커버하는지 검증 */
    if (haddr < vma->vm_start || haddr + HPAGE_PMD_SIZE > vma->vm_end)
        return VM_FAULT_FALLBACK;

    /* THP 정책 확인: always / madvise / never */
    if (transparent_hugepage_flags == TRANSPARENT_HUGEPAGE_NEVER)
        return VM_FAULT_FALLBACK;

    /* zero page 최적화: 읽기 전용(Read-Only) 폴트면 huge zero page 매핑 */
    if (!(vmf->flags & FAULT_FLAG_WRITE) &&
        !mm_forbids_zeropage(vma->vm_mm)) {
        pgtable_t pgtable = pte_alloc_one(vma->vm_mm);
        page = mm_get_huge_zero_page(vma->vm_mm);
        if (page) {
            set_huge_zero_page(pgtable, vma->vm_mm, vma, haddr, vmf->pmd, page);
            return VM_FAULT_NOPAGE;
        }
    }

    /* 2MB compound page 할당 시도 */
    gfp = vma_thp_gfp_mask(vma);
    page = vma_alloc_folio(gfp, HPAGE_PMD_ORDER, vma, haddr, true);
    if (unlikely(!page)) {
        count_vm_event(THP_FAULT_FALLBACK);
        return VM_FAULT_FALLBACK;    /* 할당 실패 → 4KB fallback */
    }

    prep_transhuge_page(page);
    /* ... PMD 설정, rmap 등록, LRU 추가 ... */
    count_vm_event(THP_FAULT_ALLOC);
    return VM_FAULT_NOPAGE;
}
코드 설명
  • 7행 HPAGE_PMD_MASK로 폴트 주소를 2MB 경계로 내림 정렬합니다. THP는 반드시 2MB 정렬된 가상 주소에 매핑되어야 합니다.
  • 10~11행 VMA 범위가 2MB 영역을 완전히 포함하지 않으면 THP를 생성할 수 없으므로 VM_FAULT_FALLBACK으로 4KB 경로로 전환합니다.
  • 17~25행 읽기 전용 폴트이면 커널 전역의 huge zero page(물리 메모리(Physical Memory) 2MB를 하나만 공유)를 매핑합니다. 프로세스 시작 시 BSS 영역 등에서 메모리를 크게 절약합니다.
  • 28~29행 GFP_TRANSHUGE__GFP_NORETRY | __GFP_COMP 등을 포함하여, 할당 실패 시 과도한 재시도 없이 빠르게 폴백합니다.
  • 30~33행 order-9 compound page 할당이 실패하면 THP_FAULT_FALLBACK vmstat 카운터를 증가시키고 일반 4KB 경로로 전환합니다. 이것이 THP의 "투명성"의 핵심입니다.
  • 35행 prep_transhuge_page()는 compound page에 THP 전용 소멸자(free_transhuge_page)를 설정하고 deferred split 큐에 등록할 준비를 합니다.

khugepaged collapse_huge_page() 핵심 흐름

/* mm/khugepaged.c — collapse 핵심 (간략화) */
static int collapse_huge_page(
    struct mm_struct *mm, unsigned long address,
    struct collapse_control *cc)
{
    struct page *new_page;
    pmd_t *pmd, _pmd;

    /* 1단계: 새 2MB 페이지 할당 */
    new_page = khugepaged_alloc_page(cc, HPAGE_PMD_ORDER);
    if (!new_page)
        return SCAN_ALLOC_HUGE_PAGE_FAIL;

    /* 2단계: mmap_write_lock으로 mm 보호 */
    mmap_write_lock(mm);

    /* 3단계: 512개 PTE 스캔 — 각 4KB 페이지를 new_page에 복사 */
    __collapse_huge_page_copy(pte, new_page, vma, address, ptl);

    /* 4단계: PTE 테이블을 PMD 엔트리로 교체 */
    _pmd = mk_huge_pmd(new_page, vma->vm_page_prot);
    _pmd = maybe_pmd_mkwrite(pmd_mkdirty(_pmd), vma);
    pmdp_collapse_flush(vma, address, pmd);
    set_pmd_at(mm, address, pmd, _pmd);

    /* 5단계: 기존 4KB 페이지들 해제 */
    __collapse_huge_page_release(...);

    mmap_write_unlock(mm);
    return SCAN_SUCCEED;
}
코드 설명
  • 10행 khugepaged_alloc_page()는 order-9 compound page를 할당합니다. 여기서 실패하면 SCAN_ALLOC_HUGE_PAGE_FAIL로 이번 스캔을 건너뜁니다.
  • 15행 mmap_write_lock()으로 mm 전체를 잠급니다. collapse 중에 다른 스레드(Thread)가 같은 영역을 수정하면 안 되기 때문입니다. 이것이 khugepaged의 주요 지연 원인입니다.
  • 18행 __collapse_huge_page_copy()가 512개의 4KB 페이지 내용을 새 2MB 페이지에 복사합니다. 비어 있는 PTE(아직 폴트가 발생하지 않은 슬롯)는 0으로 채웁니다.
  • 23행 pmdp_collapse_flush()는 기존 PMD(PTE 테이블을 가리키던)를 무효화(Invalidation)하고 TLB 플러시(Flush)를 수행합니다. 이 순간 다른 CPU에서 해당 영역 접근 시 일시적으로 폴트가 발생합니다.
  • 24행 set_pmd_at()으로 새 PMD 엔트리를 설정하면 2MB 직접 매핑이 완성됩니다. 이후 TLB 미스 시 PMD에서 바로 물리 주소를 얻어 PTE 레벨을 건너뜁니다.
collapse 실패 조건:
  • 512개 PTE 중 max_ptes_none(기본 511)개를 초과하는 빈 슬롯이 있으면 skip
  • 페이지가 mlock(), GUP pin, 또는 다른 NUMA 노드에 분산되어 있으면 skip
  • max_ptes_shared(기본 256)개를 초과하는 파일 페이지(File-backed Page)가 있으면 skip
  • compaction으로도 order-9 연속 프레임을 확보하지 못하면 SCAN_ALLOC_HUGE_PAGE_FAIL
/proc/vmstatthp_collapse_alloc_failedthp_scan_exceed_* 카운터로 실패 원인을 진단할 수 있습니다.

mTHP — Multi-size THP

기존 THP는 x86_64에서 오직 2MB(PMD 레벨) 크기만 지원했습니다. 최신 커널 6.19 계열 기준의 mTHP(Multi-size THP)는 2MB보다 작은 다양한 크기 (16KB, 32KB, 64KB, 128KB, 256KB, 512KB, 1MB 등)의 대형 폴리오(Folio)를 지원합니다. 이로써 2MB 전체를 확보하기 어려운 단편화 환경에서도 중간 크기의 THP 이점을 얻을 수 있습니다.

mTHP 동작 원리

mTHP는 PTE 레벨에서 여러 개의 연속 PTE를 하나의 폴리오에 매핑하는 방식으로 동작합니다. 2MB PMD 매핑과 달리, PTE 테이블은 그대로 유지하면서 물리적으로 연속된 페이지를 논리적 단위로 묶어 관리합니다. ARM64의 Contiguous PTE 힌트와 유사한 개념이지만 아키텍처 독립적으로 구현되어 있습니다.

mTHP 크기 계층: PTE 레벨 다중 크기 폴리오 기존 THP (커널 2.6.38~): PMD 레벨 2MB 단일 크기만 지원 할당 실패 시 바로 4KB로 폴백 → 중간 크기 부재로 단편화 환경에서 비효율 mTHP: PTE 레벨 다중 크기 지원 4KB order-0 1 PTE 16KB order-2 4 PTE 32KB order-3 8 PTE 64KB order-4 16 PTE 128KB order-5 32 PTE 256KB order-6 64 PTE 512KB order-7 128 PTE 1MB order-8 256 PTE 2MB order-9 PMD mTHP 대상: order-2 ~ order-8 (PTE 레벨 batched folio) 할당 폴백 체인 2MB (PMD) 시도 실패 1MB 시도 실패 64KB 시도 실패 ... 16KB 시도 실패 4KB (최종) mTHP 이점 단편화 환경에서도 중간 크기 폴리오로 TLB 미스 부분 완화 + 페이지 폴트 횟수 감소 2MB 전체 할당 대비 compaction 부담 감소, swap/split 시 세분화된 단위로 처리 가능

mTHP sysfs 설정

# mTHP 크기별 활성화 상태 확인
ls /sys/kernel/mm/transparent_hugepage/hugepages-*

# 특정 크기 활성화/비활성화
# hugepages-64kB 예시
cat /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
# [inherit] always madvise never
cat /sys/kernel/mm/transparent_hugepage/hugepages-64kB/shmem_enabled
# [inherit] always within_size advise never deny force

echo always > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
echo never  > /sys/kernel/mm/transparent_hugepage/hugepages-16kB/enabled

# mTHP 통계 (크기별)
cat /sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/anon_fault_alloc
cat /sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/anon_fault_fallback

# 전체 mTHP 통계 확인
for d in /sys/kernel/mm/transparent_hugepage/hugepages-*; do
    size=$(basename $d)
    alloc=$(cat $d/stats/anon_fault_alloc 2>/dev/null)
    fallback=$(cat $d/stats/anon_fault_fallback 2>/dev/null)
    echo "$size: alloc=$alloc fallback=$fallback"
done
크기orderPTE 수Buddy 할당 난이도적합 워크로드
16KB24매우 쉬움일반 애플리케이션, 낮은 단편화 이점
64KB416쉬움ARM64 기본 페이지와 동일, 균형적
256KB664보통중간 규모 버퍼, 파일 캐시
1MB8256어려움대규모 힙(Heap), DB 버퍼 풀
2MB9 (PMD)512 / PMD 직접매우 어려움메모리 집약적 워크로드
mTHP 핵심 이점: 기존 THP에서 2MB 할당 실패 시 바로 4KB로 폴백하던 것이, mTHP에서는 64KB → 16KB 등 점진적으로 작은 크기를 시도합니다. 이를 통해 단편화된 메모리에서도 페이지 폴트 횟수 감소(64KB 폴리오 하나 = 4KB 폴트 16회 절약)와 부분적 TLB 효율 개선을 동시에 얻습니다.
기본값 기억: 최신 문서 기준으로 익명 메모리의 PMD-sized THP는 보통 inherit가 기본이고, 그보다 작은 크기들은 기본적으로 never입니다. 따라서 mTHP 효과를 기대한다면 크기별 sysfs 또는 부팅 파라미터를 명시적으로 확인해야 합니다.

HugeTLB Copy-on-Write (CoW) 상세

fork() 이후 부모·자식 프로세스가 Huge Page를 공유하다가 한쪽이 쓰기를 시도하면 hugetlb_wp()가 호출되어 Copy-on-Write를 수행합니다. 일반 4KB CoW와 달리 2MB 또는 1GB 전체를 복사해야 하므로 비용이 매우 높습니다.

HugeTLB Copy-on-Write 흐름 단계 1: fork() 직후 부모 PTE (R/O) 자식 PTE (R/O) 2MB Huge Page mapcount=2, refcount=2 자식 쓰기 단계 2: hugetlb_wp() CoW 부모 PTE → 원본 자식 PTE → 복사본 원본 2MB mapcount=1 복사본 2MB mapcount=1, R/W 복사! CoW 비용 분석 2MB Huge Page CoW 512개 x 4KB 복사 ~0.2~0.5ms 지연 1GB Huge Page CoW 262,144개 x 4KB 복사 ~50~200ms 지연 4KB 일반 CoW 1개 x 4KB 복사 ~1μs 이하 fork() 후 exec()까지 사이에 쓰기가 발생하면 불필요한 대형 복사 비용 발생 Redis fork 기반 BGSAVE에서 THP가 문제를 일으키는 핵심 원인 핵심 함수: hugetlb_wp() → alloc_huge_page() → copy_huge_page() → set_huge_pte_at()
/* mm/hugetlb.c — Huge Page Copy-on-Write (간략화) */
static vm_fault_t hugetlb_wp(
    struct mm_struct *mm, struct vm_area_struct *vma,
    unsigned long address, pte_t *ptep,
    unsigned int flags, struct page *pagecache_page)
{
    struct hstate *h = hstate_vma(vma);
    struct page *old_page, *new_page;
    int outside_reserve = 0;

    old_page = pte_page(huge_ptep_get(ptep));

    /* mapcount == 1이면 자신만 매핑 중 → CoW 불필요, 쓰기 허용 */
    if (page_mapcount(old_page) == 1) {
        huge_ptep_set_wrprotect(mm, address, ptep);  /* R/W로 전환 */
        return 0;
    }

    /* 새 Huge Page 할당 */
    new_page = alloc_huge_page(vma, address, outside_reserve);
    if (IS_ERR(new_page))
        return VM_FAULT_SIGBUS;

    /* 2MB/1GB 전체 복사 — 가장 비용이 큰 단계 */
    copy_huge_page(new_page, old_page);

    /* PTE를 새 페이지로 교체 + 쓰기 가능 설정 */
    huge_ptep_clear_flush(vma, address, ptep);
    set_huge_pte_at(mm, address, ptep,
                    make_huge_pte(vma, new_page, 1));  /* writable=1 */
    page_remove_rmap(old_page, vma, true);
    put_page(old_page);

    return 0;
}
코드 설명
  • 13~16행 page_mapcount()가 1이면 다른 프로세스가 이 페이지를 공유하지 않으므로 복사 없이 쓰기 허용으로 전환합니다. fork 후 exec를 바로 호출하면 이 최적화가 적용됩니다.
  • 24행 copy_huge_page()는 2MB(512 x 4KB)를 memcpy하는 것과 동일합니다. 1GB 페이지라면 262,144 x 4KB를 복사하므로 수십~수백 밀리초가 소요됩니다.
  • 27~29행 기존 PTE를 TLB flush와 함께 제거하고 새 페이지를 가리키는 쓰기 가능한 PTE를 설정합니다.
fork + Huge Page 주의: fork 기반 스냅샷(Redis BGSAVE, PostgreSQL checkpoint 등)에서 Huge Page는 CoW 비용이 극도로 높습니다. 2MB 페이지 하나의 쓰기도 전체 2MB 복사를 유발하므로, 쓰기가 빈번한 Huge Page 영역이 많으면 fork 직후 메모리 사용량이 급증하고 지연 스파이크가 발생합니다. Redis에서 THP를 비활성화해야 하는 핵심 이유입니다.

THP Deferred Split 메커니즘

THP가 부분적으로 unmap되거나 일부 PTE만 변경되면 즉시 분할하지 않고 deferred split 큐에 등록합니다. 실제 분할은 메모리 압박이 발생할 때 shrinker가 큐를 순회하며 처리합니다. 이 지연 전략은 불필요한 분할을 방지하여 THP의 성능 이점을 최대한 유지합니다.

THP Deferred Split 메커니즘 분할 트리거 이벤트 • 부분 munmap • 부분 mprotect • NUMA balancing 이동 즉시 분할 필요? 대부분 No Deferred Split 큐 등록 deferred_split_huge_page() Yes (드문 경우) split_huge_page() 즉시 실행 대기 큐에서 대기 중 thp_deferred_split_page 카운터 추적 메모리 압박 Shrinker 실행 deferred_split_scan() → split_huge_page() 2MB → 512개 4KB 분할 완료, thp_split_page++ pgdat별 큐 구조 pgdat->deferred_split_queue split_queue_lock (spinlock) split_queue_len (대기 수)
/* mm/huge_memory.c — deferred split 등록 */
void deferred_split_huge_page(struct page *page)
{
    struct pglist_data *pgdata = page_pgdat(page);
    unsigned long flags;

    /* 이미 큐에 있으면 중복 등록 방지 */
    if (!list_empty(page_deferred_list(page)))
        return;

    spin_lock_irqsave(&pgdata->deferred_split_queue.split_queue_lock, flags);
    list_add_tail(page_deferred_list(page),
                  &pgdata->deferred_split_queue.split_queue);
    pgdata->deferred_split_queue.split_queue_len++;
    spin_unlock_irqrestore(&pgdata->deferred_split_queue.split_queue_lock, flags);
}

/* mm/huge_memory.c — shrinker에 의한 실제 분할 */
static unsigned long deferred_split_scan(
    struct shrinker *shrink,
    struct shrink_control *sc)
{
    struct pglist_data *pgdata;
    struct page *page;
    unsigned long freed = 0;

    /* 큐에서 페이지를 꺼내 분할 시도 */
    list_for_each_entry_safe(page, ..., &list, ...) {
        if (!trylock_page(page))
            continue;
        if (!split_huge_page(page))
            freed++;
        unlock_page(page);
    }
    return freed;
}
# deferred split 대기 중인 페이지 수 확인
grep thp_deferred_split_page /proc/vmstat

# 이 값이 지속적으로 증가하면:
# → 부분 unmap/mprotect가 빈번한 워크로드
# → 메모리 압박 시 대량 분할로 지연 스파이크 가능

Huge Zero Page 최적화

THP 익명 페이지 폴트에서 읽기 전용 접근인 경우, 커널은 2MB의 물리 메모리를 새로 할당하지 않고 시스템 전체에서 하나만 존재하는 huge zero page를 매핑합니다. 프로세스가 BSS 영역이나 calloc()으로 할당한 대규모 메모리에 처음 읽기 접근할 때 적용되며, 쓰기가 발생하면 그때 실제 2MB 페이지를 할당합니다 (CoW 방식).

Huge Zero Page 최적화 읽기 폴트 프로세스 A 프로세스 B Huge Zero Page 시스템 전역 1개, 물리 2MB 프로세스 A 쓰기 발생! 쓰기 폴트 → CoW 프로세스 A 새 2MB 할당 (실제 물리 메모리) zero 초기화 + 쓰기 데이터 기록 PMD R/W 설정 프로세스 B 여전히 Huge Zero Page 공유 물리 메모리 추가 소비 없음 PMD R/O 유지 메모리 절약: 읽기만 하는 2MB 영역마다 물리 2MB 절약 BSS, calloc, mmap 초기 접근 시 THP 폴트 비용도 최소화 관련 통계 및 설정 thp_zero_page_alloc (vmstat) | /sys/kernel/mm/transparent_hugepage/use_zero_page (활성화 여부, 기본 1)
# huge zero page 활성화 확인
cat /sys/kernel/mm/transparent_hugepage/use_zero_page
# 1 (활성화, 기본값)

# huge zero page 할당 통계
grep thp_zero_page /proc/vmstat
# thp_zero_page_alloc         1   ← 시스템 전체에서 1회만 할당됨
# thp_zero_page_alloc_failed  0

# 비활성화 (드물지만 특수한 보안 요구 시)
echo 0 > /sys/kernel/mm/transparent_hugepage/use_zero_page

HugeTLB Demote — 1GB를 2MB로 분할 (커널 5.18+)

커널 5.18에서 도입된 HugeTLB demote 기능은 1GB Huge Page를 512개의 2MB Huge Page로 분할합니다. 이는 일반 4KB로의 분할이 아닌 Huge Page 간 크기 변환입니다. 1GB 풀의 유연성을 높이면서도 TLB 효율을 유지하는 데 유용합니다.

HugeTLB Demote: 1GB → 512 x 2MB 분할 1GB Huge Page order-18, PUD 레벨 매핑 compound page 262,144개 x 4KB 물리 페이지 = 512개 x 2MB 영역 hstate: order=18 demote sysfs 트리거 512개 x 2MB Huge Pages order-9, PMD 레벨 매핑 2MB 2MB 2MB ... 2MB 2MB 2MB 512개의 독립적인 2MB compound page 각각 hugepage_freelists[]에 추가됨 hstate: order=9, 2MB 풀 카운터 증가 1GB 풀 nr_huge_pages 감소 사용 사례 1GB 예약이 과도한 경우 일부를 2MB로 분할하여 2MB 풀 확보 (미사용 1GB 페이지만 가능) 역방향(2MB → 1GB promote)은 불가 — 물리 연속성 보장 불가
# HugeTLB demote 사용법 (커널 5.18+)

# 현재 demote 대상 크기 확인
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/demote_size
# 2048kB (기본: 1GB → 2MB)

# 1GB Huge Page 1개를 2MB로 분할
echo 1 > /sys/kernel/mm/hugepages/hugepages-1048576kB/demote

# 결과 확인
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages    # 1 감소
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages       # 512 증가

# NUMA 노드별 demote
echo 1 > /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/demote

# 주의: 사용 중인(매핑된) 1GB 페이지는 demote 불가
# free_hugepages > 0인 경우에만 동작

ARM64 및 기타 아키텍처 Huge Page 지원

x86_64 외에도 ARM64, RISC-V, POWER 등 다양한 아키텍처가 각자의 방식으로 Huge Page를 지원합니다. 특히 ARM64는 x86_64와 달리 Contiguous PTE/PMD 힌트라는 고유한 메커니즘으로 다양한 크기의 대형 페이지를 하드웨어 수준에서 지원합니다.

아키텍처별 Huge Page 크기와 매핑 메커니즘 x86_64 기본 페이지: 4KB 2MB — PMD 레벨 (PSE 비트) 1GB — PUD 레벨 (PSE 비트) 특징 • 2가지 크기만 하드웨어 지원 • mTHP로 PTE 레벨 다중 크기 • TLB에 4KB/2MB/1GB 전용 엔트리 ARM64 (AArch64) 기본 페이지: 4KB / 16KB / 64KB 선택 4KB 기본 페이지 모드: 64KB — Contiguous PTE (16 x 4KB) 2MB — PMD 블록 매핑 32MB — Contiguous PMD (16 x 2MB) 1GB — PUD 블록 매핑 특징 • Contiguous 비트로 TLB 효율 극대화 • 다양한 중간 크기 하드웨어 지원 • mTHP와 자연스럽게 연동 RISC-V / POWER RISC-V (Sv48): 2MB — PMD 매핑 (megapage) 1GB — PUD 매핑 (gigapage) POWER (Radix MMU): 2MB — PMD 레벨 1GB — PUD 레벨 특징 • x86_64와 유사한 2단계 크기 • 커널 공통 코드 공유

ARM64 Contiguous PTE/PMD 상세

ARM64의 Contiguous 비트는 연속된 16개의 PTE 또는 PMD 엔트리가 물리적으로 연속된 메모리를 가리킬 때 설정됩니다. TLB는 이 16개 엔트리를 하나의 큰 TLB 엔트리로 병합하여 저장할 수 있어, 실질적으로 중간 크기의 Huge Page 효과를 얻습니다.

ARM64 (4KB 기본) 페이지 크기매핑 레벨ContiguousTLB 엔트리 효과
4KBPTE비트 없음1 TLB 엔트리 = 4KB
64KBContiguous PTE16 x PTE1 TLB 엔트리 = 64KB
2MBPMD 블록비트 없음1 TLB 엔트리 = 2MB
32MBContiguous PMD16 x PMD1 TLB 엔트리 = 32MB
1GBPUD 블록비트 없음1 TLB 엔트리 = 1GB
/* arch/arm64/include/asm/pgtable.h — Contiguous 관련 정의 */
#define CONT_PTE_SHIFT    (PAGE_SHIFT + 4)       /* 4KB x 16 = 64KB */
#define CONT_PTE_SIZE     (1UL << CONT_PTE_SHIFT)  /* 64KB */
#define CONT_PTES         (1 << (CONT_PTE_SHIFT - PAGE_SHIFT))  /* 16 */

#define CONT_PMD_SHIFT    (PMD_SHIFT + 4)        /* 2MB x 16 = 32MB */
#define CONT_PMD_SIZE     (1UL << CONT_PMD_SHIFT)  /* 32MB */
#define CONT_PMDS         (1 << (CONT_PMD_SHIFT - PMD_SHIFT))  /* 16 */

/* Contiguous 비트 확인 */
#define PTE_CONT          (1UL << 52)  /* ARMv8.0 Contiguous bit */
ARM64와 mTHP: ARM64의 Contiguous PTE 메커니즘은 mTHP와 자연스럽게 결합됩니다. mTHP가 64KB(order-4) 폴리오를 할당하면, ARM64에서는 16개의 연속 PTE에 Contiguous 비트를 설정하여 TLB가 이를 하나의 64KB 엔트리로 캐시합니다. 이는 x86_64에서의 mTHP(소프트웨어만 최적화)보다 TLB 효율이 더 높습니다.

흔한 실수와 해결책

실수증상원인해결책
부팅 후 1GB Huge Page 예약 실패 nr_hugepages에 쓰기 해도 값 변경 안 됨 부팅 후에는 1GB 연속 메모리 확보가 거의 불가능 부팅 파라미터 hugepagesz=1G hugepages=N으로 설정
mmap(MAP_HUGETLB) 실패 (ENOMEM) mmap: Cannot allocate memory nr_hugepages 미설정 또는 풀 고갈 사전 예약 확인: grep Huge /proc/meminfo
THP 활성화했는데 효과 없음 AnonHugePages 값이 0에 가까움 enabled=madvise인데 앱이 madvise() 호출 안 함 always로 변경하거나 앱에서 MADV_HUGEPAGE 호출
Redis 지연 스파이크 p99 지연 수십 ms 급증 THP always + fork 기반 BGSAVE → CoW 비용 echo never > /sys/.../transparent_hugepage/enabled
THP 할당 실패율 높음 thp_fault_fallback 급증 메모리 단편화로 order-9 연속 프레임 확보 불가 defrag=defer+madvise, compaction_proactiveness 조정
hugetlbfs 마운트 후 권한 오류 mmap: Permission denied 비특권 사용자가 Huge Page 사용 불가 vm.hugetlb_shm_group sysctl 설정 또는 CAP_IPC_LOCK 부여
Huge Page 예약이 일반 메모리 압박 시스템 메모리 부족, OOM 발생 과도한 nr_hugepages 설정으로 일반 메모리 영역 부족 예약량 재계산, 실제 사용량 대비 10~20% 여유만 유지
컨테이너에서 Huge Page 사용 불가 Pod 내 MAP_HUGETLB 실패 Kubernetes hugepages 리소스 미설정 Pod spec에 hugepages-2Mi 리소스 요청/제한 추가
khugepaged가 CPU를 과도하게 사용 khugepaged 프로세스 CPU 5~10% pages_to_scan 과다 또는 scan_sleep_millisecs 과소 pages_to_scan 감소, scan_sleep_millisecs 증가
NUMA 불균형 Huge Page 할당 원격 NUMA 접근으로 성능 저하 전역 nr_hugepages만 설정하여 한쪽 노드에 집중 할당 노드별 개별 예약: /sys/devices/system/node/nodeN/hugepages/...

Kubernetes에서 Huge Page 사용

# Pod spec에서 Huge Page 요청 예시
apiVersion: v1
kind: Pod
metadata:
  name: hugepage-app
spec:
  containers:
  - name: app
    image: myapp:latest
    resources:
      requests:
        memory: "1Gi"
        hugepages-2Mi: "512Mi"     # 2MB Huge Pages 512MB 요청
      limits:
        memory: "1Gi"
        hugepages-2Mi: "512Mi"     # 제한도 동일하게 설정
    volumeMounts:
    - mountPath: /hugepages
      name: hugepage
  volumes:
  - name: hugepage
    emptyDir:
      medium: HugePages-2Mi       # hugetlbfs 자동 마운트
# 노드에서 Huge Page 가용량 확인
kubectl describe node <node-name> | grep hugepages
# hugepages-2Mi: 4Gi
# hugepages-1Gi: 16Gi

# 노드에서 Huge Page 예약 (DaemonSet 또는 initContainer로 자동화)
echo 2048 > /proc/sys/vm/nr_hugepages
운영 체크리스트:
  • Huge Page 예약량 = 실제 사용량 + 10~20% 여유. 과다 예약은 일반 메모리 부족 유발
  • THP defrag 설정이 always면 지연 민감 서비스에서 스파이크 발생 가능
  • NUMA 시스템에서는 반드시 노드별 균등 예약
  • /proc/vmstatthp_fault_fallback, thp_split_page 추이를 주기적으로 모니터링
  • 컨테이너 환경에서는 cgroup hugepage 제한 + Kubernetes 리소스 요청을 함께 설정

mTHP(multi-size Transparent Huge Pages)가 6.8에서 anon 메모리에 대해 도입된 이후, 2024-2025년에는 shmem·파일 매핑·swap까지 대형 folio 경로가 확장되었습니다. HugeTLB vmemmap 최적화도 기본 활성화 방향으로 정리되어 대형 머신의 memmap 오버헤드가 크게 줄었습니다. 6.17 이후에는 장치 메모리(device-private memory) THP 지원, 기가급 huge page 오버커밋(overcommit) 허용, shmem/swap mTHP 경로 안정화가 주요 방향입니다.

커널릴리스Huge Pages 주요 변경실무 시사점
6.12 (LTS)2024-11Gigantic folio 할당 경로 추가, mTHP swap cluster 할당자 개선, /sys/kernel/mm/transparent_hugepage/hugepages-<size>/ 세분화 통계ARM64 64KB 페이지 환경에서도 2MB 미만 mTHP를 선택적으로 사용 가능
6.132025-01hugetlb_vmemmap 최적화의 아키텍처별 기본 활성화 진행, mTHP anon 경로의 split 동작 안정화1TB 이상 메모리 장비에서 memmap 절감이 수십 GB 수준
6.142025-03mTHP shmem 지원 확대, partial unmap 시 THP split 조건 완화, THP swap-in 경로 정리tmpfs/shmfs를 큰 버퍼로 쓰는 DB/미디어 파이프라인(Pipeline)에서 대형 folio 사용률 상승
6.152025-05파일 기반 매핑의 large folio readahead 개선, proactive compaction의 mTHP 크기 인식대용량 파일 읽기 성능 향상. khugepaged와 충돌 없이 컴팩션 수행
6.162025-07mTHP anon/shmem 경로 통합 개선, large folio partial unmap 경로 정리, THP deferred split 경로 재편장기 구동 서비스에서 THP split 누적으로 인한 메모리 단편화가 감소
6.172025-09khugepaged anon collapse 범위 확대, shmem/swap mTHP swap-in 경로 개선, hugetlb 폴트(fault) 경로 재작성(faulting path rework), memfd hugetlb folio 예약 선행 할당 방식 도입, large folio에 대한 mprotect() 3배 이상 성능 향상(folio_pte_batch() 활용)64kB mTHP 환경에서 시스템 시간 절감. memfd+hugetlb 조합의 예약 실패율 감소. 대형 folio를 사용하는 DB/JVM에서 mprotect() 변경 비용 대폭 절감
6.18 (LTS)2025-11khugepaged anon collapse 범위 추가 확대, 64kB mTHP 시스템 시간 절반 수준 감소 및 할당 실패율 0% 달성, Huge Zero Folio(영구 huge zero 페이지) 도입으로 zero-fill 오버헤드 감소, 커맨드라인 옵션으로 hugetlb 예약 수 증가 지원, PR_SET_THP_DISABLE 확장(advised 모드만 THP 허용)64kB mTHP 기반 고성능 워크로드에서 메모리 할당 안정성 향상. zero 페이지 참조가 많은 스파스(sparse) 할당에서 메모리 절감. PR_SET_THP_DISABLE 세분화로 스레드별 THP 정책 세밀 제어 가능
6.192026-02장치 전용 메모리(device-private memory)에 대한 THP 지원 추가, 기가급 huge page 오버커밋 허용(overcommit gigantic hugepages), 메모리 오류(memory failure) 시 folio split 최적화GPU/가속기 장치 메모리에서도 THP 경로 활성화 가능. 기가급 huge page를 실제 물리 메모리 이상 예약 가능하여 할당 유연성 증가
핵심 요약: (1) 2026년 기준 THP는 "켠다/끈다"가 아니라 "어떤 size를 켜고 끄는가"의 문제입니다. /sys/kernel/mm/transparent_hugepage/hugepages-<size>/enabled를 개별 튜닝하세요. (2) HugeTLB 1GB는 여전히 정적 예약이 안정적이며, mTHP는 런타임 최적화에 적합합니다. (3) /proc/meminfoAnonHugePages·ShmemPmdMapped·FileHugePages를 함께 보는 것이 기본 진단입니다. (4) 6.19부터 장치 메모리 THP와 gigantic hugetlb 오버커밋이 가능하므로 GPU 워크로드와 대형 NUMA 서버에서 새로운 튜닝 기회가 생깁니다.

참고자료

다음 학습:
  • MMU & TLB — 가상 주소 해석과 TLB 동작 원리
  • 페이지 할당자 — 물리 페이지 할당 메커니즘
  • NUMA — Non-Uniform Memory Access 아키텍처
  • 메모리 관리 개요 — 리눅스 커널 메모리 관리 전체 그림
  • VMA/mmap — 가상 메모리 영역과 mmap 매핑
  • Folio — 폴리오 기반 메모리 관리와 mTHP 연동
  • Memory Compaction — 메모리 압축(Memory Compaction)과 Huge Page 할당 연동