# HugeTLB (HugeTLBfs)
관련 소스:mm/hugetlb.c,include/linux/hugetlb.h
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 풀 상태 확인
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
하나의 거대 페이지 크기(예: 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];
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; /* 글로벌 풀에서 예약한 수 */
};
거대 페이지 예약(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;
};
공유 매핑(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 */
};
거대 페이지의 특수 상태를 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,
};
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 페이지 폴트의 메인 진입점입니다. 페이지 테이블에 거대 페이지 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(...);
}
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);
}
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);
}
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, ®ions_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);
}
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에 따라 특정 노드 우선 |
| 매핑 유형 | 예약 관리 | 페이지 캐시 | rmap | COW |
|---|---|---|---|---|
| 공유 (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_restore_reserve` | 예약 소비 할당 시 | 페이지 완전 인스턴스화 시 | 오류 시 예약 복원 필요 |
| `HPG_migratable` | page cache/tab에 추가 후 | 마이그레이션 격리 시 | 마이그레이션 후보 |
| `HPG_temporary` | buddy에서 임시 할당 시 | 프리 시 즉시 buddy 반환 | 풀 페이지 아님 |
| `HPG_freed` | 프리 리스트 enqueue 시 | dequeue 시 | 프리 상태 표시 |
| `HPG_vmemmap_optimized` | vmemmap 해제 시 | - | vmemmap 최적화 적용 |
minzkn.com의 메모리 관리 개요 페이지는 HugeTLB/THP 섹션에서 다음 개념을 다룹니다:
cat /proc/meminfo | grep Huge, hugepages= 커널 파라미터, hugetlbfs 마운트 방법 등은 minzkn과 일치합니다.hugepagesize, HugePages_Total/Free/Rsvd/Surplus 필드 설명을 포함하며, 우리 문서의 빠른 점검 명령 섹션에 이미 포함되어 있습니다.