Ryotta's Linux 7.0 MM

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

# HugeTLB (HugeTLBfs)

관련 소스: mm/hugetlb.c, include/linux/hugetlb.h

개요 (Overview)

HugeTLB는 리눅스 커널에서 사전 할당된 거대 페이지(Huge Page)를 관리하는 서브시스템입니다. THP(Transparent Huge Pages)와 달리 HugeTLB는 사용자가 명시적으로 거대 페이지 풀(Huge Page Pool)을 구성하고, hugetlbfs 파일 시스템이나 MAP_HUGETLB 플래그를 통해 거대 페이지를 직접 할당합니다. 2MB(기본) 또는 1GB 크기의 페이지를 사용하여 TLB 미스를 줄이고 대용량 메모리 접근 성능을 향상시킵니다.

핵심 설계 원칙은 사전 할당(Pre-allocation)예약(Reservation)입니다. 부팅 시 또는 런타임에 hugepages= 커널 파라미터로 미리 확보한 거대 페이지 풀에서 할당이 이루어지며, hugetlb_reserve_pages()가 VMA 생성 시 예약을 수행하고 alloc_hugetlb_folio()가 실제 할당을 처리합니다. subpool은 hugetlbfs 파일 시스템별, memcg는 cgroup별로 풀을 분리하여 관리합니다.

// 소스 파일 경로
mm/hugetlb.c                    /* 핵심 할당/관리 로직 */
mm/hugetlb_vmemmap.c            /* vmemmap 최적화 */
mm/hugetlb_cma.c                /* CMA를 통한 거대 페이지 할당 */
include/linux/hugetlb.h         /* 핵심 구조체/함수 선언 */
include/linux/hugetlb_inline.h  /* 인라인 헬퍼 */
fs/hugetlbfs/inode.c            /* hugetlbfs 파일 시스템 */
HugeTLB 자료구조 관계도
HugeTLB 호출 흐름

빠른 점검 명령

# HugeTLB 풀 상태 확인
cat /proc/meminfo | grep -i huge
# HugePages_Total: 할당된 거대 페이지 수
# HugePages_Free:  풀에서 사용 가능한 수
# HugePages_Rsvd:  예약되었으나 아직 매핑 안 된 수
# HugePages_Surplus: 오버커밋으로 초과 할당된 수
# Hugepagesize:    거대 페이지 크기 (보통 2048 kB)

# 거대 페이지 풀 크기 확인 (아키텍처별)
grep -i huge /proc/meminfo

# hugetlbfs 마운트 확인
mount | grep hugetlb

# NUMA 노드별 거대 페이지 분포
cat /sys/devices/system/node/node*/hugepages/hugepages-2048kB/*

# 프로세스 거대 페이지 사용량
cat /proc/$$/status | grep -i hugetlb

# 거대 페이지 할당/해제 추적
cat /proc/vmstat | grep -i huge

# 거대 페이지 풀 리사이즈 (런타임)
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# hugetlbfs 디렉토리 마운트
mkdir -p /mnt/huge
mount -t hugetlbfs -o pagesize=2M none /mnt/huge

# memcg 거대 페이지 제한 확인
cat /sys/fs/cgroup/memory hugetlb.max 2>/dev/null

핵심 자료구조

struct hstate — 거대 페이지 상태 관리

하나의 거대 페이지 크기(예: 2MB, 1GB)를 대표하는 전역 상태 구조체입니다. hstates[] 배열에 HUGE_MAX_HSTATE개까지 존재하며, for_each_hstate() 매크로로 순회합니다.

/* include/linux/hugetlb.h:666-687 */
struct hstate {
    struct mutex resize_lock;             /* 리사이즈 직렬화 */
    struct lock_class_key resize_key;
    int next_nid_to_alloc;                /* 다음 할당 대상 노드 */
    int next_nid_to_free;                 /* 다음 해제 대상 노드 */
    unsigned int order;                   /* 거대 페이지 order (1=2MB, 3=1GB) */
    unsigned int demote_order;            /* 디모트 시 target order */
    unsigned long mask;                   /* 페이지 정렬 마스크 ~(size-1) */
    unsigned long max_huge_pages;         /* 목표 풀 크기 (sysctl 설정) */
    unsigned long nr_huge_pages;          /* 현재 풀에 있는 전체 거대 페이지 수 */
    unsigned long free_huge_pages;        /* 사용 가능한 거대 페이지 수 */
    unsigned long resv_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 max_huge_pages_node[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];           /* "hugepages-2048kB" 등 */
};
/*出处: mm/hugetlb.c:57 — 전역 배열 */
struct hstate hstates[HUGE_MAX_HSTATE];

struct hugepage_subpool — 서브풀 관리

hugetlbfs 파일 시스템이나 memcg에 거대 페이지 풀을 분리하여 관리하는 구조체입니다. max_hpages로 상한, min_hpages로 하한을 설정합니다.

/* include/linux/hugetlb.h:37-47 */
struct hugepage_subpool {
    spinlock_t lock;          /* 잠금 */
    long count;               /* 참조 카운트 */
    long max_hpages;          /* 최대 거대 페이지 수 또는 -1 (무제한) */
    long used_hpages;         /* 사용 중인 수 (할당+예약 포함) */
    struct hstate *hstate;    /* 연결된 hstate */
    long min_hpages;          /* 최소 거대 페이지 수 또는 -1 */
    long rsv_hpages;          /* 글로벌 풀에서 예약한 수 */
};

struct resv_map / file_region — 예약 영역 추적

거대 페이지 예약(reservation)을 구간(region) 단위로 추적합니다. file_region[from, to) 구간을 나타내는 interval 노드이고, resv_map은 이들의 연결 리스트와 잠금을 관리합니다.

/* include/linux/hugetlb.h:49-67 */
struct resv_map {
    struct kref refs;                 /* 참조 카운트 */
    spinlock_t lock;                  /* 잠금 */
    struct list_head regions;         /* file_region 리스트 */
    long adds_in_progress;           /* 진행 중인 추가 수 */
    struct list_head region_cache;   /* 재사용 캐시 */
    long region_cache_count;
    struct rw_semaphore rw_sema;     /* 읽기/쓰기 세마포어 */
    /* CONFIG_CGROUP_HUGETLB */
    struct page_counter *reservation_counter; /* memcg 예약 카운터 */
    unsigned long pages_per_hpage;
    struct cgroup_subsys_state *css;
};

/* include/linux/hugetlb.h:88-101 */
struct file_region {
    struct list_head link;   /* 리스트 연결 */
    long from;               /* 시작 인덱스 (포함) */
    long to;                 /* 종료 인덱스 (불포함) — [from, to) */
    /* CONFIG_CGROUP_HUGETLB */
    struct page_counter *reservation_counter;
    struct cgroup_subsys_state *css;
};

struct hugetlb_vma_lock — VMA별 잠금

공유 매핑(VM_MAYSHARE)에서 PMD 셰어링과 폴트/트런케이션 동기화를 위해 VMA별 rwsem을 제공합니다.

/* include/linux/hugetlb.h:103-107 */
struct hugetlb_vma_lock {
    struct kref refs;               /* 참조 카운트 */
    struct rw_semaphore rw_sema;    /* 읽기/쓰기 세마포어 */
    struct vm_area_struct *vma;     /* 연결된 VMA */
};

HUGETLB 페이지 플래그 (HPG_*)

거대 페이지의 특수 상태를 folio->private 비트에 저장합니다.

/* include/linux/hugetlb.h:593-602 */
enum hugetlb_page_flags {
    HPG_restore_reserve = 0,  /* 할당 시 예약 소비 → 오류 시 복원 필요 */
    HPG_migratable,           /* 마이그레이션 후보 */
    HPG_temporary,            /* Buddy에서 임시 할당 (이전용 타겟) */
    HPG_freed,                /* 프리 리스트에 있음 */
    HPG_vmemmap_optimized,    /* vmemmap 메모리 해제됨 */
    HPG_raw_hwp_unreliable,   /* hwpoison 서브페이지 미추적 */
    HPG_cma,                  /* CMA 영역에서 할당됨 */
    __NR_HPAGEFLAGS,
};

struct hugetlbfs_sb_info — 파일 시스템 정보

hugetlbfs의 superblock에 연결된 거대 페이지 파일 시스템 정보입니다.

/* include/linux/hugetlb.h:503-512 */
struct hugetlbfs_sb_info {
    long max_inodes;              /* 허용 inode 수 */
    long free_inodes;             /* 여유 inode 수 */
    spinlock_t stat_lock;
    struct hstate *hstate;        /* 연결된 hstate */
    struct hugepage_subpool *spool; /* 서브풀 */
    kuid_t uid;                   /* 소유자 UID */
    kgid_t gid;                   /* 소유자 GID */
    umode_t mode;                 /* 권한 */
};

핵심 함수

hugetlb_fault() — 폴트 진입점

HugeTLB 페이지 폴트의 메인 진입점입니다. 페이지 테이블에 거대 페이지 PTE가 없으면 hugetlb_no_page()로, COW가 필요하면 hugetlb_wp()로 분기합니다.

/* mm/hugetlb.c:5972-6161 */
vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
            unsigned long address, unsigned int flags)
{
    // 1. fault mutex로 동일 페이지 할당 직렬화
    hash = hugetlb_fault_mutex_hash(mapping, vmf.pgoff);
    mutex_lock(&hugetlb_fault_mutex_table[hash]);

    // 2. VMA lock 획득 후 PTE 할당
    hugetlb_vma_lock_read(vma);
    vmf.pte = huge_pte_alloc(mm, vma, vmf.address, huge_page_size(h));

    // 3. PTE 상태에 따른 분기
    vmf.orig_pte = huge_ptep_get(mm, vmf.address, vmf.pte);

    if (huge_pte_none(vmf.orig_pte))
        return hugetlb_no_page(mapping, &vmf);  /* 새 페이지 할당 */

    if (softleaf_is_migration(softleaf))
        migration_entry_wait_huge(...);          /* 마이그레이션 대기 */

    if (softleaf_is_hwpoison(softleaf))
        return VM_FAULT_HWPOISON_LARGE;         /* 하드웨어 오류 */

    // 4. 기존 페이지에 대한 COW 처리
    if ((flags & (FAULT_FLAG_WRITE|FAULT_FLAG_UNSHARE)) && ...)
        ret = hugetlb_wp(&vmf);                 /* Copy-on-Write */

    // 5. accessed bit 갱신
    vmf.orig_pte = pte_mkyoung(vmf.orig_pte);
    huge_ptep_set_access_flags(...);
}

hugetlb_no_page() — 새 거대 페이지 할당 및 매핑

PTE가 비어있을 때 새 거대 페이지를 할당하고 페이지 테이블에 매핑합니다. 파일 기반 매핑은 page cache에, 익명 매핑은 anon_rmap에 연결합니다.

/* mm/hugetlb.c:5722-5946 */
static vm_fault_t hugetlb_no_page(struct address_space *mapping,
                struct vm_fault *vmf)
{
    // 1. 페이지 캐시에서 기존 folio 검색
    folio = filemap_lock_hugetlb_folio(h, mapping, vmf->pgoff);

    if (IS_ERR(folio)) {
        // 2. 없으면 새 folio 할당
        folio = alloc_hugetlb_folio(vma, vmf->address, false);
        folio_zero_user(folio, vmf->real_address);
        __folio_mark_uptodate(folio);

        // 3. 공유 매핑: page cache에 추가
        if (vma->vm_flags & VM_MAYSHARE)
            hugetlb_add_to_page_cache(folio, mapping, vmf->pgoff);
        else
            new_anon_folio = true;  /* 익명: rmap에 추가 */
    }

    // 4. PTE에 새 거대 페이지 매핑
    new_pte = make_huge_pte(vma, folio, vma->vm_flags & VM_SHARED);
    set_huge_pte_at(mm, vmf->address, vmf->pte, new_pte, huge_page_size(h));

    // 5. COW가 필요한 사유 매핑이면 즉시 처리
    if ((vmf->flags & FAULT_FLAG_WRITE) && !(vma->vm_flags & VM_SHARED))
        ret = hugetlb_wp(vmf);
}

alloc_hugetlb_folio() — 거대 페이지 할당 핵심

VMA의 서브풀, memcg, 글로벌 풀에서 순차적으로 할당 가능 여부를 확인하고 folio를 할당합니다.

/* mm/hugetlb.c:2908-3078 */
struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
                    unsigned long addr, bool cow_from_owner)
{
    // 1. VMA 예약 확인 (map_chg: REUSE/NEEDED/ENFORCED)
    retval = vma_needs_reservation(h, vma, addr);

    // 2. 서브풀 글로벌 할당 확인
    if (map_chg)
        gbl_chg = hugepage_subpool_get_pages(spool, 1);

    // 3. memcg 예약 충전
    if (map_chg)
        hugetlb_cgroup_charge_cgroup_rsvd(idx, ...);

    // 4. memcg 일반 충전
    hugetlb_cgroup_charge_cgroup(idx, ...);

    // 5. 글로벌 풀에서 dequeue 또는 buddy 할당
    spin_lock_irq(&hugetlb_lock);
    folio = dequeue_hugetlb_folio_vma(h, vma, addr, gbl_chg);
    if (!folio)
        folio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr);

    // 6. 예약 소비 마크
    if (!gbl_chg)
        folio_set_hugetlb_restore_reserve(folio);

    // 7. memcg 커밋
    hugetlb_cgroup_commit_charge(idx, ...);

    // 8. VMA 예약 커밋
    if (map_chg != MAP_CHG_ENFORCED)
        vma_commit_reservation(h, vma, addr);
}

hugetlb_reserve_pages() — 예약 처리

mmap() 시 호출되어 거대 페이지 예약을 설정합니다. 공유 매핑은 resv_map의 region으로, 사유 매핑은 별도 resv_map으로 관리합니다.

/* mm/hugetlb.c:6570-6749 */
long hugetlb_reserve_pages(struct inode *inode,
        long from, long to,
        struct vm_area_desc *desc, vma_flags_t vma_flags)
{
    // 1. VM_NORESERVE이면 예약 없음
    if (vma_flags_test(&vma_flags, VMA_NORESERVE_BIT))
        return 0;

    // 2. 공유 매핑: inode의 resv_map에 region_chg
    if (!desc || vma_desc_test_flags(desc, VMA_MAYSHARE_BIT)) {
        resv_map = inode_resv_map(inode);
        chg = region_chg(resv_map, from, to, &regions_needed);
    } else {
        // 3. 사유 매핑: 새 resv_map 할당
        resv_map = resv_map_alloc();
        chg = to - from;
        set_vma_desc_resv_map(desc, resv_map);
    }

    // 4. memcg 예약 충전
    hugetlb_cgroup_charge_cgroup_rsvd(...);

    // 5. 서브풀 할당 확인
    gbl_reserve = hugepage_subpool_get_pages(spool, chg);

    // 6. 글로벌 풀 여유 확인
    hugetlb_acct_memory(h, gbl_reserve);

    // 7. 공유: region_add로 예약 영역 확정
    if (공유 매핑)
        add = region_add(resv_map, from, to, regions_needed, h, h_cg);
}

dequeue_hugetlb_folio_vma() — VMA 기반 프리 리스트 dequeue

VMA의 NUMA 정책에 따라 적절한 노드의 프리 리스트에서 folio를 꺼내옵니다.

/* mm/hugetlb.c:1373-1412 */
static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h,
                struct vm_area_struct *vma, unsigned long addr, bool gbl_chg)
{
    // 1. NUMA 정책에 따른 노드 결정
    int nid = huge_node(vma, addr, htlb_alloc_mask(h), &mpol, &nodemask);

    // 2. 지정 노드에서 dequeue 시도
    folio = dequeue_hugetlb_folio_node_exact(h, nid, gfp_mask, gbl_chg);

    // 3. 실패 시 nodemask 내 다른 노드로 fallback
    if (!folio)
        folio = dequeue_hugetlb_folio_nodemask(h, gfp_mask, nodemask, gbl_chg);

    return folio;
}

호출 흐름

mmap(MAP_HUGETLB) ──→ hugetlb_file_setup() ──→ hugetlb_reserve_pages()
    │                                              │
    │                                    region_chg() / resv_map_alloc()
    │                                    hugepage_subpool_get_pages()
    │                                    hugetlb_acct_memory()
    │
    ▼
첫 접근 시 Page Fault
    │
    ▼
hugetlb_fault()
    │
    ├── huge_pte_none() ──→ hugetlb_no_page()
    │       │                    │
    │       │                    ├── filemap_lock_hugetlb_folio() [캐시 검색]
    │       │                    ├── alloc_hugetlb_folio() [할당]
    │       │                    │       │
    │       │                    │       ├── vma_needs_reservation()
    │       │                    │       ├── hugepage_subpool_get_pages()
    │       │                    │       ├── hugetlb_cgroup_charge_cgroup()
    │       │                    │       ├── dequeue_hugetlb_folio_vma()
    │       │                    │       │       └── dequeue_hugetlb_folio_node_exact()
    │       │                    │       └── alloc_buddy_hugetlb_folio_with_mpol()
    │       │                    │
    │       │                    ├── hugetlb_add_to_page_cache() [공유]
    │       │                    └── hugetlb_add_new_anon_rmap() [익명]
    │       │
    │       └── set_huge_pte_at() ──→ COW 필요 시 hugetlb_wp()
    │
    ├── migration_entry_wait_huge() [마이그레이션 대기]
    │
    └── hugetlb_wp() [COW 처리]
            │
            ├── folio_ref_count==1 → wp_page_reuse() [단독 사용 → 재사용]
            └── folio_ref_count>1  → wp_page_copy() [공유 → 복사]

조건별 비교

거대 페이지 할당 경로 비교

조건할당 경로설명
VMA 예약 있음 (REUSE)`dequeue_hugetlb_folio_vma()`프리 리스트에서 즉시 할당
VMA 예약 없음 (NEEDED)`subpool → buddy`서브풀 확인 후 buddy 할당
COW from_owner`MAP_CHG_ENFORCED`예약 체크 건너뛰고 buddy 할당
Buddy 할당 실패`alloc_surplus_hugetlb_folio()`오버커밋으로 초과 할당
NUMA 정책 적용`huge_node()` → 노드 지정mpol에 따라 특정 노드 우선

매핑 유형별 처리 비교

매핑 유형예약 관리페이지 캐시rmapCOW
공유 (VM_MAYSHARE)inode resv_map의 region`__filemap_add_folio()``hugetlb_add_file_rmap()`없음 (공유)
사유 (private)VMA별 별도 resv_map없음`hugetlb_add_new_anon_rmap()``hugetlb_wp()`
MAP_NORESERVE예약 없음--할당 시 예약 불필요
memcg 적용reservation_counter--memcg 충전

거대 페이지 풀 소비자 비교

소비자역할잠금
`hstate.max_huge_pages`목표 풀 크기`hugetlb_lock`
`hstate.nr_huge_pages`현재 할당 수`hugetlb_lock`
`hstate.free_huge_pages`사용 가능 수`hugetlb_lock`
`hstate.resv_huge_pages`예약 대기 수`hugetlb_lock`
`hstate.surplus_huge_pages`오버커밋 초과`hugetlb_lock`
`hugepage_subpool.used_hpages`서브풀 사용 수`spool->lock`
memcg `reservation_counter`memcg 예약 수page_counter lock

HPG 플래그별 동작 비교

플래그설정 시점해제 시점의미
`HPG_restore_reserve`예약 소비 할당 시페이지 완전 인스턴스화 시오류 시 예약 복원 필요
`HPG_migratable`page cache/tab에 추가 후마이그레이션 격리 시마이그레이션 후보
`HPG_temporary`buddy에서 임시 할당 시프리 시 즉시 buddy 반환풀 페이지 아님
`HPG_freed`프리 리스트 enqueue 시dequeue 시프리 상태 표시
`HPG_vmemmap_optimized`vmemmap 해제 시-vmemmap 최적화 적용

minzkn.com 비교 참고

minzkn.com의 메모리 관리 개요 페이지는 HugeTLB/THP 섹션에서 다음 개념을 다룹니다:

  • 일상 비유: 거대 페이지는 도서관의 "대형 사전 전용 코너"와 비슷합니다. 자주 찾는 대형 사전을 일반 책장이 아닌 전용 코너에 두면 찾기가 빨라집니다.
  • THP vs HugeTLB 차이: THP는 커널이 자동으로 거대 페이지를 선택하고 병합하는 반면, HugeTLB는 사용자가 명시적으로 풀을 구성하고 할당합니다.
  • 점검 명령: cat /proc/meminfo | grep Huge, hugepages= 커널 파라미터, hugetlbfs 마운트 방법 등은 minzkn과 일치합니다.
  • 관련 표: minzkn의 Huge Pages 설명은 hugepagesize, HugePages_Total/Free/Rsvd/Surplus 필드 설명을 포함하며, 우리 문서의 빠른 점검 명령 섹션에 이미 포함되어 있습니다.

  • 관련 문서

  • 메모리 관리 개요
  • Buddy Allocator
  • VMA / mmap
  • Huge Pages / THP
  • CMA
  • Migration
  • Memory Cgroup
  • Folio / Page Cache