๐ VMA / mmap
์์ฑ์ผ: 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 ยท ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ฐ์
๊ฐ์ (Overview)
VMA(Virtual Memory Area)๋ ํ๋ก์ธ์ค์ ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ์ ๊ตฌ์ฑํ๋ ํต์ฌ ์๋ฃ๊ตฌ์กฐ์
๋๋ค. ๊ฐ VMA๋ ์ฐ์๋ ๊ฐ์ ์ฃผ์ ๋ฒ์๋ฅผ ๋ํ๋ด๋ฉฐ, ๊ณต์ ๋๋ ์์ฑ(์ฝ๊ธฐ/์ฐ๊ธฐ/์คํ ๊ถํ, ๋ฐฑ์
ํ์ผ ๋ฑ)์ ๊ฐ์ง๋๋ค. ๋ฆฌ๋
์ค ์ปค๋์ VMA๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด Maple Tree๋ฅผ ์ฌ์ฉํ๋ฉฐ, mmap() ์์คํ
์ฝ์ ํตํด ํ์ผ์ด๋ ์ฅ์น๋ฅผ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ์ ๋งคํํฉ๋๋ค.
์ผ์ ๋น์ : ํ๋ก์ธ์ค์ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ๋ ์ํํธ ๋จ์ง์ ๊ฐ์ต๋๋ค. ๊ฐ VMA๋ ํ ๋(ๆฃ)์ ํด๋นํ๋ฉฐ, mmap()์ ์ ๋์ ์ง๋ ํ๊ฐ์ฆ์
๋๋ค. Page fault๋ ์
์ฃผ์๊ฐ "์ด ๋ฐฉ์ ์ฒ์ ๋ค์ด๊ฐ ๋" ๊ด๋ฆฌ์ฌ๋ฌด์(MMU)๊ฐ ์ด์ ๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๊ณผ์ ๊ณผ ๊ฐ์ต๋๋ค. COW๋ ๋ถ๋ชจ-์์์ด ๊ฐ์ ๋ฐฉ์ ์ฐ๋ค๊ฐ ๋๊ตฐ๊ฐ ๊ฐ๊ตฌ๋ฅผ ๋ฐ๊ฟ ๋ ๋น๋ก์ ๋ฐฉ์ ๋ถ๋ฆฌํ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
Page fault ์ฒ๋ฆฌ๋ VMA์ ๋ฐ์ ํ๊ฒ ์ฐ๊ด๋ฉ๋๋ค. ํ๋ก์ธ์ค๊ฐ ๋งคํ๋ ์ฃผ์์ ์ฒ์ ์ ๊ทผํ๋ฉด page fault๊ฐ ๋ฐ์ํ๊ณ , ์ปค๋์ handle_mm_fault()๋ฅผ ํตํด ์ ์ ํ ํ์ด์ง ํ
์ด๋ธ ํญ๋ชฉ์ ์์ฑํฉ๋๋ค. ์ด ๊ณผ์ ์์ VMA์ ์์ฑ(์ต๋ช
/ํ์ผ ๊ธฐ๋ฐ, ๊ณต์ /์ฌ์ ๋ฑ)์ ๋ฐ๋ผ ๋ค๋ฅธ ์ฒ๋ฆฌ ๊ฒฝ๋ก๊ฐ ์ ํ๋ฉ๋๋ค.
ํ์ด์ง ํดํธ 3์ข ๋ฅ
| ์ข ๋ฅ | ์์ธ | ์ฒ๋ฆฌ | 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๊ณผ COW ๊ฐ๋
Demand Paging์ ํ๋ก์ธ์ค๊ฐ mmap()์ด๋ brk()๋ก ๊ฐ์ ์ฃผ์๋ฅผ ์์ฝํ์ ๋ ์ค์ ๋ฌผ๋ฆฌ ํ์ด์ง๋ฅผ ์ฆ์ ํ ๋นํ์ง ์๊ณ , ์ฒ์ ์ ๊ทผํ๋ ์๊ฐ์๋ง ํ์ด์ง๋ฅผ ํ ๋นํ๋ ๋ฉ์ปค๋์ฆ์
๋๋ค. ์ด๊ฒ์ด ํ๋ก์ธ์ค์ VmSize(๊ฐ์ ํฌ๊ธฐ)์ VmRSS(์ค์ ๋ฌผ๋ฆฌ ์ฌ์ฉ๋)๊ฐ ํฌ๊ฒ ๋ค๋ฅธ ์ด์ ์
๋๋ค.
Copy-on-Write(COW)๋ fork() ์ ๋ถ๋ชจ์ ์์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฆ์ ๋ณต์ฌํ์ง ์๊ณ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ๊ณต์ ํ๋ค๊ฐ, ์ค์ ๋ก ์ฐ๊ธฐ ์๋ ์์๋ง ํด๋น ํ์ด์ง๋ฅผ ๋ณต์ฌํ๋ ์ต์ ํ์
๋๋ค. ์ด๋ฅผ ํตํด fork()๊ฐ ์ฆ์ ๋ฐํ๋๊ณ , ์์์ด ์ฝ๊ธฐ๋ง ํ๋ฉด ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ถ๊ฐ๋ก ์๋น๋์ง ์์ต๋๋ค.
malloc()์์ ์ปค๋๊น์ง โ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ์ ์ฒด ๊ฒฝ๋ก
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)
mmap/brk ์๋จ ์ฒ๋ฆฌ ๋ณด๊ฐ
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
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
vm_area_struct (include/linux/mm_types.h:913)
ํ๋ก์ธ์ค์ ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ ํ๋๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด์ ๋๋ค.
/* 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์ ์ํ ๊ตฌ์กฐ์ฒดmm_struct (include/linux/mm_types.h:1123)
ํ๋ก์ธ์ค์ ์ ์ฒด ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ์ ๊ด๋ฆฌํ๋ ๊ตฌ์กฐ์ฒด์ ๋๋ค.
/* 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_state (mm/vma.c:10)
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;
};
vm_area_desc (include/linux/mm_types.h:880)
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;
};
ํต์ฌ ํจ์
1. do_mmap() โ mmap ์์คํ ์ฝ์ ํต์ฌ ๋ก์ง
/* 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)2. mmap_region() โ VMA ์์ฑ/๋ณํฉ ์ํ
/* 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(): ์ฐ๊ธฐ ๊ฐ๋ฅํ ํ์ผ ๋งคํ ์ ๋ฉ๋ชจ๋ฆฌ ํ๊ณ3. handle_mm_fault() โ Page Fault ์ฒ๋ฆฌ ์ง์ ์
/* 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 ์ฒ๋ฆฌ4. __handle_mm_fault() โ 4๋จ๊ณ ํ์ด์ง ํ ์ด๋ธ ์ํ
/* 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 ์ฒ๋ฆฌ๋ก fallback5. handle_pte_fault() โ PTE ๋ ๋ฒจ fault ๋ถ๊ธฐ
/* 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() โ ์ ๊ทผ ๋นํธ ์ค์
6. do_anonymous_page() โ ์ต๋ช ํ์ด์ง ํ ๋น (minor fault)
/* 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 ์ค์ 7. do_wp_page() โ Copy-on-Write ์ฒ๋ฆฌ
/* 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 ์ค์ COW ๋ถ๊ธฐ
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);
ํ์ผ ๋งคํ fault ์ธ๋ถ ๋ถ๊ธฐ
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() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
x86_64 Page Fault ์๋จ
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` ์ฌ์ฉ |
mmap ํ๋๊ทธ ๋น๊ต
| ํ๋๊ทธ | 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)
PTE ํ๋๊ทธ์ TLB ๊ด์
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๋ฅผ ํ๋๋ก ํฉ์น๋ ์ต์ ํ์
๋๋ค. ๋ณํฉ ์กฐ๊ฑด์ 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 ๋ฑ)