작성일: 2026-06-18 | 최종 수정: 2026-06-19
관련 소스: mm/mmap.c (1922줄), mm/memory.c (7493줄), mm/vma.c (3309줄), include/linux/mm_types.h (1941줄)
보조 소스:mm/util.c,mm/gup.c,arch/x86/mm/fault.c,arch/x86/include/asm/pgtable_types.h
관련 문서: Huge Pages / THP · Folio / Page Cache · Userfaultfd · 메모리 Sealing · Secret Memory · 메모리 관리 개요
VMA(Virtual Memory Area)는 프로세스의 가상 주소 공간을 구성하는 핵심 자료구조입니다. 각 VMA는 연속된 가상 주소 범위를 나타내며, 공유되는 속성(읽기/쓰기/실행 권한, 백업 파일 등)을 가집니다. 리눅스 커널은 VMA를 관리하기 위해 Maple Tree를 사용하며, mmap() 시스템 콜을 통해 파일이나 장치를 가상 메모리에 매핑합니다.
일상 비유: 프로세스의 가상 메모리는 아파트 단지와 같습니다. 각 VMA는 한 동(棟)에 해당하며, mmap()은 새 동을 짓는 허가증입니다. Page fault는 입주자가 "이 방에 처음 들어갈 때" 관리사무소(MMU)가 열쇠를 만들어주는 과정과 같습니다. COW는 부모-자식이 같은 방을 쓰다가 누군가 가구를 바꿀 때 비로소 방을 분리하는 것과 같습니다.
Page fault 처리는 VMA와 밀접하게 연관됩니다. 프로세스가 매핑된 주소에 처음 접근하면 page fault가 발생하고, 커널은 handle_mm_fault()를 통해 적절한 페이지 테이블 항목을 생성합니다. 이 과정에서 VMA의 속성(익명/파일 기반, 공유/사유 등)에 따라 다른 처리 경로가 선택됩니다.
| 종류 | 원인 | 처리 | I/O 발생 |
|---|---|---|---|
| **minor fault (소 폴트)** | 물리 페이지는 있지만 PTE에 아직 매핑 안 됨 | 기존 물리 페이지를 PTE에 연결만 | 없음 (빠름) |
| **major fault (대 폴트)** | 물리 페이지가 스왑/파일에 있어 디스크 I/O 필요 | 디스크에서 읽어서 물리 페이지 할당 후 PTE 연결 | 있음 (느림) |
| **invalid access** | VMA에 없는 주소, 권한 위반 | 프로세스에 SIGSEGV 시그널 전달 | 없음 |
주요 소스 파일:
- mm/mmap.c (1922줄) — mmap 시스템 콜, brk, VMA 생성/제거
- mm/memory.c (7493줄) — page fault 처리, 페이지 테이블 관리
- mm/vma.c (3309줄) — VMA 병합, 분할, 링크, __mmap_region()
- include/linux/mm_types.h (1941줄) — vm_area_struct, mm_struct 정의
Demand Paging은 프로세스가 mmap()이나 brk()로 가상 주소를 예약했을 때 실제 물리 페이지를 즉시 할당하지 않고, 처음 접근하는 순간에만 페이지를 할당하는 메커니즘입니다. 이것이 프로세스의 VmSize(가상 크기)와 VmRSS(실제 물리 사용량)가 크게 다른 이유입니다.
Copy-on-Write(COW)는 fork() 시 부모와 자식의 메모리를 즉시 복사하지 않고 읽기 전용으로 공유하다가, 실제로 쓰기 시도 시에만 해당 페이지를 복사하는 최적화입니다. 이를 통해 fork()가 즉시 반환되고, 자식이 읽기만 하면 물리 메모리가 추가로 소비되지 않습니다.
malloc(100) → 물리 페이지 확보까지 전체 경로
[사용자 공간]
malloc(100) — glibc 내부 free 청크 확인 (ptmalloc 힙 관리)
↓
청크 없으면 커널에 요청
↓
[커널 공간 (syscall)]
brk() 또는 mmap() 시스템 콜
↓
brk(): 힙 끝 주소(brk) 이동 → do_brk_flags()
mmap(MAP_ANONYMOUS): 새 VMA 생성 (128KB 이상) → mmap_region()
↓
VMA(vm_area_struct) 생성 — 가상 주소 범위만 예약, 물리 페이지 할당 없음
↓
[Page Fault 발생 시]
실제 접근 시 Page Fault → handle_mm_fault()
↓
do_anonymous_page() — minor fault → 물리 페이지 할당 후 PTE 연결
↓
Buddy Allocator — alloc_page(GFP_KERNEL | __GFP_ZERO)
malloc()이 커널로 내려올 때 작은 힙 확장은 보통 brk(), 큰 익명 영역이나 파일 매핑은 mmap() 경로를 탑니다. Linux 7.0의 vm_mmap_pgoff()는 보안 훅과 fsnotify 검사를 먼저 통과한 뒤 do_mmap()을 호출하고, MAP_POPULATE나 MAP_LOCKED로 즉시 fault-in이 필요한 경우 mm_populate()를 실행합니다.
/* mm/util.c:575-586 */
ret = security_mmap_file(file, prot, flag);
if (!ret)
ret = fsnotify_mmap_perm(file, prot, off, len);
if (!ret) {
if (mmap_write_lock_killable(mm))
return -EINTR;
ret = do_mmap(file, addr, len, prot, flag, 0, pgoff, &populate,
&uf);
mmap_write_unlock(mm);
userfaultfd_unmap_complete(mm, &uf);
if (populate)
mm_populate(ret, populate);
}
brk() 계열은 요청 크기를 페이지 경계로 올림한 뒤 기존 범위를 먼저 비우고, do_brk_flags()에서 기존 brk VMA 확장 또는 새 익명 VMA 생성을 수행합니다.
/* mm/mmap.c:1214-1241 */
len = PAGE_ALIGN(request);
if (len < request)
return -ENOMEM;
if (!len)
return 0;
ret = check_brk_limits(addr, len);
if (ret)
goto limits_failed;
ret = do_vmi_munmap(&vmi, mm, addr, len, &uf, 0);
if (ret)
goto munmap_failed;
vma = vma_prev(&vmi);
ret = do_brk_flags(&vmi, vma, addr, len, vm_flags);
populate = ((mm->def_flags & VM_LOCKED) != 0);
mmap_write_unlock(mm);
userfaultfd_unmap_complete(mm, &uf);
if (populate && !ret)
mm_populate(addr, len);
# 현재 프로세스의 VMA 목록 확인
cat /proc/self/maps | head -20
# 특정 프로세스의 VMA 상세 정보 (RSS, PSS, 스왑 등)
cat /proc/<PID>/smaps | head -50
# 시스템 전체 VMA 개수 확인
cat /proc/vmstat | grep nr_mapped
# mmap 시스템 콜 트레이싱
strace -e mmap,brk ls
# 현재 시스템의 페이지 테이블 크기 확인
cat /proc/meminfo | grep PageTables
# 페이지 폴트 통계 (minor/major)
cat /proc/vmstat | grep -E 'pgfault|pgmajfault'
# 프로세스별 VmSize vs VmRSS 비교 (demand paging 확인)
cat /proc/$$/status | grep -E 'VmSize|VmRSS'
# 커널 VMA 통계
cat /proc/meminfo | grep -i vm
# 기본 페이지 크기 확인
getconf PAGE_SIZE
# 현재 셸 프로세스의 minor/major fault 누적값
ps -o pid,minflt,majflt -p $$
# 특정 명령 실행 중 minor/major fault 계측
perf stat -e minor-faults,major-faults <command>
# 특정 프로세스의 TLB miss 관찰
perf stat -e dTLB-load-misses,dTLB-store-misses,iTLB-load-misses -p <PID> -- sleep 5
# 프로세스별 page table 메모리 사용량
grep VmPTE /proc/<PID>/status
# ASLR 활성화 수준 확인 (0=off, 1=conservative, 2=full)
cat /proc/sys/kernel/randomize_va_space
프로세스의 가상 주소 공간 하나를 나타내는 구조체입니다.
/* include/linux/mm_types.h:913-1056 */
struct vm_area_struct {
/* 첫 번째 캐시 라인: VMA 트리 순회 정보 */
union {
struct {
unsigned long vm_start; /* VMA 시작 주소 */
unsigned long vm_end; /* VMA 끝 주소 (포함 안 됨) */
};
freeptr_t vm_freeptr; /* SLAB_TYPESAFE_BY_RCU용 포인터 */
};
struct mm_struct *vm_mm; /* 소속 주소 공간 */
pgprot_t vm_page_prot; /* 이 VMA의 접근 권한 */
union {
const vm_flags_t vm_flags; /* VMA 플래그 (VM_READ, VM_WRITE 등) */
vma_flags_t flags;
};
/* 익명 매핑 관련 */
struct list_head anon_vma_chain;
struct anon_vma *anon_vma;
/* 파일 매핑 관련 */
const struct vm_operations_struct *vm_ops; /* VMA 연산 함수 포인터 */
unsigned long vm_pgoff; /* 파일 내 페이지 오프셋 */
struct file *vm_file; /* 매핑된 파일 (익명이면 NULL) */
void *vm_private_data; /* 드라이버 전용 데이터 */
};
핵심 필드 설명:
vm_start/vm_end: VMA가 차지하는 가상 주소 범위 [vm_start, vm_end)vm_flags: 읽기/쓰기/실행 권한, 공유/사유 매핑 여부 등vm_file: 파일 기반 매핑이면 파일 객체, 익명 매핑이면 NULLvm_ops: page fault 처리 등 VMA 관련 연산 함수 포인터anon_vma: 익명 페이지의 reverse mapping을 위한 구조체프로세스의 전체 가상 주소 공간을 관리하는 구조체입니다.
/* include/linux/mm_types.h:1123-1319 (일부) */
struct mm_struct {
struct {
struct maple_tree mm_mt; /* VMA를 저장하는 Maple Tree */
unsigned long mmap_base; /* mmap 영역 시작 주소 */
unsigned long task_size; /* 가상 주소 공간 크기 */
pgd_t *pgd; /* 페이지 글로벌 디렉토리 */
atomic_t mm_users; /* 사용자 수 */
atomic_t mm_count; /* 참조 카운트 */
int map_count; /* VMA 개수 */
struct rw_semaphore mmap_lock; /* mmap 잠금 */
unsigned long total_vm; /* 전체 페이지 수 */
unsigned long locked_vm; /* 잠긴 페이지 수 */
unsigned long start_code, end_code; /* 코드 영역 */
unsigned long start_data, end_data; /* 데이터 영역 */
unsigned long start_brk, brk; /* 힙 영역 */
unsigned long start_stack; /* 스택 시작 */
};
};
mmap 시 Operation 상태를 추적하는 내부 구조체입니다.
/* mm/vma.c:10-45 */
struct mmap_state {
struct mm_struct *mm;
struct vma_iterator *vmi;
unsigned long addr;
unsigned long end;
pgoff_t pgoff;
unsigned long pglen;
union {
vm_flags_t vm_flags;
vma_flags_t vma_flags;
};
struct file *file;
pgprot_t page_prot;
const struct vm_operations_struct *vm_ops;
void *vm_private_data;
unsigned long charged;
struct vm_area_struct *prev;
struct vm_area_struct *next;
/* 매핑 해제 상태 */
struct vma_munmap_struct vms;
struct ma_state mas_detach;
struct maple_tree mt_detach;
bool check_ksm_early :1;
bool hold_file_rmap_lock :1;
bool file_doesnt_need_get :1;
};
mmap 시 드라이버가 VMA 속성을 수정할 수 있는 중간 설명 구조체입니다.
/* include/linux/mm_types.h:880-899 */
struct vm_area_desc {
/* 변경 불가 상태 */
const struct mm_struct *const mm;
struct file *const file;
unsigned long start;
unsigned long end;
/* 변경 가능한 필드 */
pgoff_t pgoff;
struct file *vm_file;
vma_flags_t vma_flags;
pgprot_t page_prot;
/* 쓰기 전용 필드 */
const struct vm_operations_struct *vm_ops;
void *private_data;
struct mmap_action action;
};
/* mm/mmap.c:335-339 */
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, vm_flags_t vm_flags,
unsigned long pgoff, unsigned long *populate,
struct list_head *uf)
역할: 사용자 공간의 mmap() 시스템 콜을 처리하는 핵심 함수입니다. 매핑 요청을 검증하고, 적절한 가상 주소를 찾아서 mmap_region()에 전달합니다.
주요 분기 로직:
MAP_FIXED 플래그: 지정된 주소에 강제 매핑MAP_SHARED vs MAP_PRIVATE: 공유/사유 매핑 결정file->f_op->mmap 호출 여부 결정populate: 매핑 즉시 페이지 할당 여부 (prefault)/* mm/vma.c:2818-2820 */
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
struct list_head *uf)
역할: 실제 VMA 생성 또는 기존 VMA와의 병합을 수행합니다. __mmap_region()을 호출하여 핵심 로직을 처리합니다.
상세 흐름 (mm/vma.c:2720-2793):
__mmap_region()
├─ __mmap_setup() — mmap_state 초기화, 주소 검증
├─ call_mmap_prepare() — 파일 시스템의 .mmap_prepare 훅 호출
├─ update_ksm_flags() — KSM 플래그 설정 (check_ksm_early)
├─ vma_merge_new_range() — 인접 VMA 병합 시도
│ ├─ 병합 성공: 기존 VMA 확장
│ └─ 병합 실패: __mmap_new_vma() → 새 VMA 할당
├─ __mmap_complete() — VMA 설정 완료
└─ call_action_complete() — .mmap_prepare 후속 액션 실행
주요 분기 로직:
map_deny_write_exec(): MDWE(Memory Deny Write Exec) 검사arch_validate_flags(): 아키텍처별 vm_flags 검증mapping_map_writable(): 쓰기 가능한 파일 매핑 시 메모리 회계/* mm/memory.c:6589-6591 */
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags, struct pt_regs *regs)
역할: 하드웨어 page fault 인터럽트에서 호출되는 진입 함수입니다. VMA 권한 검증 후 __handle_mm_fault()를 호출합니다.
주요 분기 로직:
hugetlb_fault(): HugePage 매핑 시 별도 처리VM_DROPPABLE: 드롭 가능한 매핑의 OOM 에러 무시mem_cgroup_enter_user_fault(): 메모리 cgroup OOM 처리/* mm/memory.c:6355-6357 */
static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
unsigned long address, unsigned int flags)
역할: PGD → P4D → PUD → PMD → PTE 순으로 페이지 테이블을 순회하며 없으면 할당합니다.
주요 분기 로직:
create_huge_pud()/create_huge_pmd(): HugePage PUD/PMD 생성 시도wp_huge_pud()/wp_huge_pmd(): HugePage 쓰기 가능 페이지 처리handle_pte_fault(): 일반 PTE 레벨 fault 처리로 fallback/* mm/memory.c:6273-6275 */
static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
역할: PTE 항목의 상태에 따라 적절한 fault 핸들러로 분기합니다.
주요 분기 로직:
handle_pte_fault()
├─ pmd_none() → vmf->pte = NULL
├─ pte_none(orig_pte) → vmf->pte = NULL (ptomap 해제)
│
├─ vmf->pte == NULL (PTE 없음)
│ └─ do_pte_missing()
│ ├─ vma_is_anonymous() → do_anonymous_page() ← 익명 매핑
│ └─ else → do_fault() ← 파일 기반 매핑
│
├─ !pte_present(orig_pte) → do_swap_page() ← 스왑에서 복구
│
├─ pte_protnone() && vma_is_accessible() → do_numa_page() ← NUMA 마이그레이션
│
└─ PTE 존재, 접근 가능
├─ FAULT_FLAG_WRITE && !pte_write() → do_wp_page() ← COW 처리
├─ FAULT_FLAG_WRITE → pte_mkdirty() ← 쓰기 접근
└─ pte_mkyoung() ← 접근 비트 설정
/* mm/memory.c — static vm_fault_t do_anonymous_page(struct vm_fault *vmf) */
역할: PTE가 없는 익명 매핑 영역에 대한 최초 접근 시 호출됩니다. 새 물리 페이지를 할당하고 PTE에 연결합니다.
흐름:
alloc_anon_folio() — THP(mTHP) 지원 포함 folio 할당mem_cgroup_charge() — 메모리 cgroup 충전folio_add_new_anon_rmap() — anon_vma에 RMAP 등록set_pte_at() — PTE 설정/* mm/memory.c — static vm_fault_t do_wp_page(struct vm_fault *vmf) */
역할: 읽기 전용으로 매핑된 페이지에 쓰기 시도 시 호출됩니다. 참조 카운트가 1이면 wp_page_reuse()로 PTE 쓰기 권한만 활성화하고, 여러 프로세스가 공유 중이면 wp_page_copy()로 새 페이지를 복사합니다.
/* COW 핵심 분기 (mm/memory.c) */
static vm_fault_t do_wp_page(struct vm_fault *vmf)
{
struct page *old_page = vm_normal_page(vmf->vma, vmf->address, vmf->orig_pte);
if (page_count(old_page) == 1)
return wp_page_reuse(vmf); /* 이 프로세스만 사용 → 복사 불필요 */
return wp_page_copy(vmf); /* 여러 프로세스 공유 → 새 페이지 복사 */
}
Linux 7.0 실제 do_wp_page()는 page_count(old_page) == 1만 보지 않고, 공유 매핑 여부와 익명 folio의 exclusive 상태를 기준으로 wp_page_reuse() 또는 wp_page_copy()를 선택합니다.
/* mm/memory.c:4197-4241 */
if (vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) {
if (!vmf->page || is_fsdax_page(vmf->page)) {
vmf->page = NULL;
return wp_pfn_shared(vmf);
}
return wp_page_shared(vmf, folio);
}
if (folio && folio_test_anon(folio) &&
(PageAnonExclusive(vmf->page) || wp_can_reuse_anon_folio(folio, vma))) {
if (!PageAnonExclusive(vmf->page))
SetPageAnonExclusive(vmf->page);
if (unlikely(unshare)) {
pte_unmap_unlock(vmf->pte, vmf->ptl);
return 0;
}
wp_page_reuse(vmf, folio);
return 0;
}
if (folio)
folio_get(folio);
pte_unmap_unlock(vmf->pte, vmf->ptl);
#ifdef CONFIG_KSM
if (folio && folio_test_ksm(folio))
count_vm_event(COW_KSM);
#endif
return wp_page_copy(vmf);
PTE가 비어 있을 때 do_pte_missing()은 VMA가 익명 매핑이면 do_anonymous_page(), 파일 기반이면 do_fault()로 나눕니다.
/* mm/memory.c:4472-4478 */
static vm_fault_t do_pte_missing(struct vm_fault *vmf)
{
if (vma_is_anonymous(vmf->vma))
return do_anonymous_page(vmf);
else
return do_fault(vmf);
}
파일 기반 do_fault()는 읽기 fault, private COW fault, shared write fault를 다시 구분합니다. 이 분기가 minzkn의 do_read_fault() / do_cow_fault() / do_shared_fault() 흐름에 해당합니다.
/* mm/memory.c:5903-5945 */
static vm_fault_t do_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct mm_struct *vm_mm = vma->vm_mm;
vm_fault_t ret;
if (!vma->vm_ops->fault) {
vmf->pte = pte_offset_map_lock(vmf->vma->vm_mm, vmf->pmd,
vmf->address, &vmf->ptl);
if (unlikely(!vmf->pte))
ret = VM_FAULT_SIGBUS;
else {
if (unlikely(pte_none(ptep_get(vmf->pte))))
ret = VM_FAULT_SIGBUS;
else
ret = VM_FAULT_NOPAGE;
pte_unmap_unlock(vmf->pte, vmf->ptl);
}
} else if (!(vmf->flags & FAULT_FLAG_WRITE))
ret = do_read_fault(vmf);
else if (!(vma->vm_flags & VM_SHARED))
ret = do_cow_fault(vmf);
else
ret = do_shared_fault(vmf);
/* preallocated pagetable is unused: free it */
if (vmf->prealloc_pte) {
pte_free(vm_mm, vmf->prealloc_pte);
vmf->prealloc_pte = NULL;
}
return ret;
}
[사용자 공간]
mmap() 시스템 콜
↓
[커널]
sys_mmap() → do_mmap()
↓
mmap_region() → __mmap_region()
↓
┌─────────────────────────────────────┐
│ VMA 병합 시도: vma_merge_new_range()│
│ - 병합 성공: 기존 VMA 확장 │
│ - 병합 실패: __mmap_new_vma() │
└─────────────────────────────────────┘
↓
__mmap_complete() → VMA 설정 완료
↓
[Page Fault 발생 시]
handle_mm_fault()
↓
__handle_mm_fault()
↓
PGD → P4D → PUD → PMD → PTE 순회
↓
handle_pte_fault()
↓
┌─────────────────────────────────────┐
│ PTE 없음: do_anonymous_page() │
│ PTE 없음(파일): do_fault() │
│ 스왑: do_swap_page() │
│ COW: do_wp_page() │
│ NUMA: do_numa_page() │
└─────────────────────────────────────┘
handle_mm_fault()는 MM 공통 진입점이고, x86_64에서는 그 앞에 아키텍처 예외 처리 경로가 있습니다. exc_page_fault()가 CR2 fault 주소를 읽고 handle_page_fault()로 전달하면, 사용자 주소는 do_user_addr_fault()에서 VMA 탐색과 권한 검사를 거친 뒤 handle_mm_fault()로 이어집니다.
/* arch/x86/mm/fault.c:1470-1475 */
if (unlikely(fault_in_kernel_space(address))) {
do_kern_addr_fault(regs, error_code, address);
} else {
do_user_addr_fault(regs, error_code, address);
}
/* arch/x86/mm/fault.c:1325-1335 */
vma = lock_vma_under_rcu(mm, address);
if (!vma)
goto lock_mmap;
if (unlikely(access_error(error_code, vma))) {
bad_area_access_error(regs, error_code, address, NULL, vma);
count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
return;
}
fault = handle_mm_fault(vma, address, flags | FAULT_FLAG_VMA_LOCK, regs);
RCU VMA lock fast path가 실패하거나 retry가 필요하면 lock_mm_and_find_vma()로 mmap lock 기반 경로를 사용합니다.
/* arch/x86/mm/fault.c:1357-1385 */
vma = lock_mm_and_find_vma(mm, address, regs);
if (unlikely(!vma)) {
bad_area_nosemaphore(regs, error_code, address);
return;
}
if (unlikely(access_error(error_code, vma))) {
bad_area_access_error(regs, error_code, address, mm, vma);
return;
}
fault = handle_mm_fault(vma, address, flags, regs);
| 조건 | 처리 함수 | 설명 |
|---|---|---|
| 익명 매핑 (최초 접근) | `do_anonymous_page()` | 새 페이지 할당, zero-fill |
| 파일 기반 매핑 (최초 접근) | `do_fault()` | 파일에서 페이지 읽기 |
| COW (쓰기 fault) | `do_wp_page()` | 페이지 복사 후 독립 사용 |
| 스왑된 페이지 접근 | `do_swap_page()` | 스왑에서 페이지 복구 |
| NUMA 프로텍션 fault | `do_numa_page()` | NUMA 노드 간 페이지 이동 |
| HugePage 매핑 | `hugetlb_fault()` | HugePage 전용 처리 |
| MAP_FIXED 매핑 | 기존 VMA 제거 후 새 매핑 | 지정된 주소에 강제 매핑 |
| MAP_SHARED | 공유 VMA, 파일 backed | `anon_vma` 대신 `i_mmap` 사용 |
| MAP_PRIVATE | 사유 VMA, COW 지원 | `anon_vma` 사용 |
| 플래그 | Linux 7.0 처리 포인트 | 소스 근거 | |
|---|---|---|---|
| `MAP_SHARED` | 파일 매핑이면 쓰기 권한과 append-only/seal을 검사하고 `VM_SHARED \ | VM_MAYSHARE` 설정 | `mm/mmap.c:435-464` |
| `MAP_PRIVATE` | 파일 매핑은 읽기 가능 여부와 noexec를 검사하고, 익명 매핑은 `pgoff = addr >> PAGE_SHIFT` 설정 | `mm/mmap.c:467-539` | |
| `MAP_ANONYMOUS` | `file == NULL` 경로로 들어가며 private 익명 VMA는 파일 없이 demand paging으로 물리 페이지를 뒤늦게 확보 | `mm/mmap.c:493-539` | |
| `MAP_FIXED_NOREPLACE` | `MAP_FIXED` 처리로 넘기되 기존 VMA와 교차하면 `-EEXIST` 반환 | `mm/mmap.c:361-413` | |
| `MAP_POPULATE` | `do_mmap()` 뒤 `populate` 길이를 설정하고, 상위 `vm_mmap_pgoff()`에서 `mm_populate()` 호출 | `mm/mmap.c:559-563`, `mm/util.c:585-586` | |
| `MAP_LOCKED` | `can_do_mlock()`와 `mlock_future_ok()`를 검사하고, populate 조건에도 포함 | `mm/mmap.c:416-421`, `mm/mmap.c:559-563` | |
| `MAP_HUGETLB` | `ksys_mmap_pgoff()`에서 HugeTLB 파일 설정 또는 파일 HugeTLB 길이 정렬을 수행 | `mm/mmap.c:579-603` |
MAP_POPULATE와 MAP_LOCKED의 실제 fault-in은 __mm_populate()가 VMA 범위를 순회하며 populate_vma_page_range()를 호출하는 방식입니다.
/* mm/gup.c:1918-1925 */
/*
* __mm_populate - populate and/or mlock pages within a range of address space.
*
* This is used to implement mlock() and the MAP_POPULATE / MAP_LOCKED mmap
* flags. VMAs must be already marked with the desired vm_flags, and
* mmap_lock must not be held.
*/
int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
VMA는 주소 범위와 정책을 설명하지만, 실제 접근 허용 여부는 최종적으로 PTE 플래그와 TLB 캐시에 반영됩니다. x86_64 기준 PTE 주요 비트는 다음과 같습니다.
| PTE 비트 | 의미 | 소스 근거 |
|---|---|---|
| `_PAGE_PRESENT` bit 0 | 물리 페이지가 현재 매핑되어 있음 | `arch/x86/include/asm/pgtable_types.h:10,51` |
| `_PAGE_RW` bit 1 | 쓰기 가능 | `arch/x86/include/asm/pgtable_types.h:11,52` |
| `_PAGE_USER` bit 2 | 사용자 공간 접근 가능 | `arch/x86/include/asm/pgtable_types.h:12,53` |
| `_PAGE_ACCESSED` bit 5 | CPU가 접근 비트를 세움 | `arch/x86/include/asm/pgtable_types.h:15,56` |
| `_PAGE_DIRTY` bit 6 | CPU가 쓰기 발생을 기록 | `arch/x86/include/asm/pgtable_types.h:16,57` |
| `_PAGE_NX` bit 63 | 실행 금지(No Execute) | `arch/x86/include/asm/pgtable_types.h:30,121-125` |
TLB는 최근 가상주소→물리주소 변환 결과를 캐시합니다. VMA 변경, PTE 권한 변경, COW 복사 후 PTE 교체처럼 변환 결과가 바뀌는 작업은 필요 시 TLB invalidation/shootdown을 동반하며, 실행 중 지연은 perf stat -e dTLB-load-misses,dTLB-store-misses,iTLB-load-misses로 관찰할 수 있습니다.
VMA 병합은 인접한 VMA를 하나로 합치는 최적화입니다. 병합 조건은 vma.c에서 관리됩니다:
/* mm/vma.c:86-101 — 병합 가능 여부 검사 */
static inline bool is_mergeable_vma(struct vma_merge_struct *vmg, bool merge_next)
{
struct vm_area_struct *vma = merge_next ? vmg->next : vmg->prev;
if (!mpol_equal(vmg->policy, vma_policy(vma))) // NUMA 정책 일치
return false;
if ((vma->vm_flags ^ vmg->vm_flags) & ~VM_IGNORE_MERGE) // 플래그 일치
return false;
if (vma->vm_file != vmg->file) // 같은 파일
return false;
if (!is_mergeable_vm_userfaultfd_ctx(vma, vmg->uffd_ctx)) // userfaultfd 컨텍스트
return false;
if (!anon_vma_name_eq(anon_vma_name(vma), vmg->anon_name)) // anon_vma 이름
return false;
return true;
}
병합 불가 조건:
vm_flags 불일치 (VM_READ, VM_WRITE, VM_EXEC 등)