# madvise / process_madvise
관련 소스: mm/madvise.c, include/uapi/asm-generic/mman-common.h, include/linux/mm.h
개요 (Overview)
madvise()는 사용자 프로세스가 커널에게 특정 메모리 영역에 대한 힌트(hint)를 제공하는 시스템 호출이다. readahead 정책 변경, 페이지 해제, 스왑아웃, THP 병합, KSM 병합 등 다양한 동작을 단일 진입점을 통해 처리한다. Linux 7.0에서는 madvise_behavior 구조체를 중심으로 locking, VMA walk, 각 behavior별 분기 처리가 체계화되어 있으며, process_madvise()를 통해 원격 프로세스에도 적용할 수 있다.
핵심 소스 파일:
mm/madvise.c — 시스템 호출 구현, behavior 분기, VMA walk, 페이지 테이블 조작
include/uapi/asm-generic/mman-common.h — MADV_* 상수 정의 (0~25, 100~103)
include/linux/mm.h — VM_* 플래그 (VM_DONTCOPY, VM_WIPEONFORK, VM_SEQ_READ, VM_RAND_READ, VM_DONTDUMP)
include/linux/mman.h — VMA 수정 관련 헬퍼
빠른 점검 명령
# 현재 프로세스의 madvise 사용 통계 확인 (perf)
perf stat -e 'syscalls:sys_enter_madvise' -a sleep 1
# VMA flag 변경 추적 (ftrace)
echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_madvise/enable
cat /sys/kernel/debug/tracing/trace_pipe
# madvise 관련 커널 심볼 확인
cat /proc/kallsyms | grep -E 'do_madvise|process_madvise|madvise_walk'
# process_madvise syscall 추적
perf stat -e 'syscalls:sys_enter_process_madvise' -a sleep 1
# 특정 프로세스의 VMA flags 확인 (madvise 결과 관찰)
cat /proc/<PID>/maps | head -20
# THP collapse 상태 확인 (MADV_COLLAPSE 관련)
cat /sys/kernel/mm/transparent_hugepage/enabled
# KSM 상태 확인 (MADV_MERGEABLE 관련)
cat /sys/kernel/mm/ksm/run
cat /sys/kernel/mm/ksm/pages_shared
# 메모리 회수 상태 확인 (MADV_COLD/PAGEOUT 관련)
cat /proc/vmstat | grep -E 'pgscan|pgsteal|pgdeactivate'
핵심 자료구조
madvise_behavior — behavior 처리 상태 컨테이너
// mm/madvise.c:66-82
struct madvise_behavior {
struct mm_struct *mm; // 대상 프로세스의 mm_struct
int behavior; // MADV_* 상수 값
struct mmu_gather *tlb; // TLB batch 수집기
enum madvise_lock_mode lock_mode; // 잠금 모드 결정
struct anon_vma_name *anon_name; // 익명 VMA 이름 (__MADV_SET_ANON_VMA_NAME용)
struct madvise_behavior_range range; // 현재 처리 중인 주소 범위
struct vm_area_struct *prev; // 이전 VMA (VMA split/merge용)
struct vm_area_struct *vma; // 현재 대상 VMA
bool lock_dropped; // mmap_lock 해제 여부 추적
};
lock_dropped: MADV_WILLNEED에서 vfs_fadvise() 호출 시 mmap_lock을 해제하므로, 이후 VMA 재탐색이 필요함
lock_mode: behavior별로 MADVISE_NO_LOCK, MADVISE_MMAP_READ_LOCK, MADVISE_MMAP_WRITE_LOCK, MADVISE_VMA_READ_LOCK 중 선택
madvise_lock_mode — 잠금 모드 열거형
// mm/madvise.c:54-59
enum madvise_lock_mode {
MADVISE_NO_LOCK, // 메모리 오류 주입 시 잠금 불필요
MADVISE_MMAP_READ_LOCK, // mmap_lock 읽기 잠금 (WILLNEED, COLD, PAGEOUT 등)
MADVISE_MMAP_WRITE_LOCK, // mmap_lock 쓰기 잠금 (vm_flags 변경 시)
MADVISE_VMA_READ_LOCK, // VMA 읽기 잠금 (DONTNEED, FREE, GUARD 등)
};
madvise_behavior_range — 처리 범위
// mm/madvise.c:61-64
struct madvise_behavior_range {
unsigned long start; // 시작 가상 주소 (페이지 정렬)
unsigned long end; // 종료 가상 주소 (exclusive)
};
madvise_walk_private — 페이지 테이블 walk 비공개 데이터
// mm/madvise.c:49-52
struct madvise_walk_private {
struct mmu_gather *tlb; // TLB 수집 컨텍스트
bool pageout; // true = MADV_PAGEOUT, false = MADV_COLD
};
핵심 함수
do_madvise — 시스템 호출 메인 진입점
// mm/madvise.c:2012-2033
int do_madvise(struct mm_struct *mm, unsigned long start,
size_t len_in, int behavior)
{
int error;
struct mmu_gather tlb;
struct madvise_behavior madv_behavior = {
.mm = mm,
.behavior = behavior,
.tlb = &tlb,
};
if (madvise_should_skip(start, len_in, behavior, &error))
return error;
error = madvise_lock(&madv_behavior); // behavior별 잠금 획득
if (error)
return error;
madvise_init_tlb(&madv_behavior); // TLB batch 초기화
error = madvise_do_behavior(start, len_in, &madv_behavior);
madvise_finish_tlb(&madv_behavior); // TLB batch 해제
madvise_unlock(&madv_behavior); // 잠금 해제
return error;
}
흐름: 검증 → 잠금 → TLB 초기화 → behavior 처리 → TLB 마무리 → 잠금 해제
process_madvise — pidfd 기반 원격 적용 진입점
// mm/madvise.c:2107-2168
SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec,
size_t, vlen, int, behavior, unsigned int, flags)
{
ssize_t ret;
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
struct iov_iter iter;
struct task_struct *task;
struct mm_struct *mm;
unsigned int f_flags;
if (flags != 0) {
ret = -EINVAL;
goto out;
}
ret = import_iovec(ITER_DEST, vec, vlen, ARRAY_SIZE(iovstack), &iov, &iter);
if (ret < 0)
goto out;
task = pidfd_get_task(pidfd, &f_flags);
if (IS_ERR(task)) {
ret = PTR_ERR(task);
goto free_iov;
}
/* ASLR 메타데이터 유출을 막기 위해 PTRACE_MODE_READ를 요구한다. */
mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
if (IS_ERR(mm)) {
ret = PTR_ERR(mm);
goto release_task;
}
/* 원격 주소 공간을 건드릴 때만 behavior 제한을 적용한다. */
if (mm != current->mm && !process_madvise_remote_valid(behavior)) {
ret = -EINVAL;
goto release_mm;
}
/* 원격 대상에는 CAP_SYS_NICE가 필요하다. */
if (mm != current->mm && !capable(CAP_SYS_NICE)) {
ret = -EPERM;
goto release_mm;
}
ret = vector_madvise(mm, &iter, behavior);
release_mm:
mmput(mm);
release_task:
put_task_struct(task);
free_iov:
kfree(iov);
out:
return ret;
}
process_madvise_remote_valid()는 MADV_COLD, MADV_PAGEOUT, MADV_WILLNEED, MADV_COLLAPSE만 허용한다.
vector_madvise()는 iovec를 순회하면서 각 구간을 같은 madvise 처리 경로로 넘긴다.
madvise_vma_behavior — behavior별 분기 디스패처
// mm/madvise.c:1345-1448
static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
{
int behavior = madv_behavior->behavior;
struct vm_area_struct *vma = madv_behavior->vma;
vm_flags_t new_flags = vma->vm_flags;
struct madvise_behavior_range *range = &madv_behavior->range;
int error;
if (unlikely(!can_madvise_modify(madv_behavior)))
return -EPERM;
switch (behavior) {
case MADV_REMOVE: return madvise_remove(madv_behavior);
case MADV_WILLNEED: return madvise_willneed(madv_behavior);
case MADV_COLD: return madvise_cold(madv_behavior);
case MADV_PAGEOUT: return madvise_pageout(madv_behavior);
case MADV_FREE:
case MADV_DONTNEED:
case MADV_DONTNEED_LOCKED: return madvise_dontneed_free(madv_behavior);
case MADV_COLLAPSE:
return madvise_collapse(vma, range->start, range->end,
&madv_behavior->lock_dropped);
case MADV_GUARD_INSTALL: return madvise_guard_install(madv_behavior);
case MADV_GUARD_REMOVE: return madvise_guard_remove(madv_behavior);
// VMA 플래그 변경 (madvise_update_vma 호출)
case MADV_NORMAL:
new_flags = new_flags & ~VM_RAND_READ & ~VM_SEQ_READ;
break;
case MADV_SEQUENTIAL:
new_flags = (new_flags & ~VM_RAND_READ) | VM_SEQ_READ;
break;
case MADV_RANDOM:
new_flags = (new_flags & ~VM_SEQ_READ) | VM_RAND_READ;
break;
case MADV_DONTFORK:
new_flags |= VM_DONTCOPY;
break;
case MADV_DOFORK:
if (new_flags & VM_SPECIAL)
return -EINVAL;
new_flags &= ~VM_DONTCOPY;
break;
case MADV_WIPEONFORK:
if (vma->vm_file || new_flags & VM_SHARED)
return -EINVAL;
new_flags |= VM_WIPEONFORK;
break;
case MADV_KEEPONFORK:
if (new_flags & VM_DROPPABLE)
return -EINVAL;
new_flags &= ~VM_WIPEONFORK;
break;
case MADV_DONTDUMP:
new_flags |= VM_DONTDUMP;
break;
case MADV_DODUMP:
if ((!is_vm_hugetlb_page(vma) && (new_flags & VM_SPECIAL)) ||
(new_flags & VM_DROPPABLE))
return -EINVAL;
new_flags &= ~VM_DONTDUMP;
break;
case MADV_MERGEABLE:
case MADV_UNMERGEABLE:
error = ksm_madvise(vma, range->start, range->end,
behavior, &new_flags);
if (error)
goto out;
break;
case MADV_HUGEPAGE:
case MADV_NOHUGEPAGE:
error = hugepage_madvise(vma, &new_flags, behavior);
if (error)
goto out;
break;
}
/* 이 분기는 VMA 수정 경로다. */
VM_WARN_ON_ONCE(madv_behavior->lock_mode != MADVISE_MMAP_WRITE_LOCK);
error = madvise_update_vma(new_flags, madv_behavior);
out:
/* 메모리 자원이 일시적으로 부족하면 EAGAIN으로 변환한다. */
if (error == -ENOMEM)
error = -EAGAIN;
return error;
}
madvise_walk_vmas — VMA 순회 + behavior 적용
// mm/madvise.c:1668-1742
static int madvise_walk_vmas(struct madvise_behavior *madv_behavior)
{
struct mm_struct *mm = madv_behavior->mm;
struct madvise_behavior_range *range = &madv_behavior->range;
unsigned long last_end = range->end;
int unmapped_error = 0;
int error;
struct vm_area_struct *prev, *vma;
// VMA read lock 가능한 경우 단일 VMA 시도
if (madv_behavior->lock_mode == MADVISE_VMA_READ_LOCK &&
try_vma_read_lock(madv_behavior)) {
error = madvise_vma_behavior(madv_behavior);
vma_end_read(madv_behavior->vma);
return error;
}
vma = find_vma_prev(mm, range->start, &prev);
if (vma && range->start > vma->vm_start)
prev = vma;
// VMA 순회 루프
for (;;) {
if (!vma)
return -ENOMEM;
if (range->start < vma->vm_start) {
unmapped_error = -ENOMEM;
range->start = vma->vm_start;
if (range->start >= last_end)
break;
}
range->end = min(vma->vm_end, last_end);
error = madvise_vma_behavior(madv_behavior);
if (error)
return error;
if (madv_behavior->lock_dropped) {
// mmap_lock을 내려서 VMA 참조를 유지할 수 없는 경우
prev = NULL;
vma = NULL;
madv_behavior->lock_dropped = false;
} else {
vma = madv_behavior->vma;
prev = vma;
}
if (vma && range->end < vma->vm_end)
range->end = vma->vm_end;
if (range->end >= last_end)
break;
vma = find_vma(mm, vma ? vma->vm_end : range->end);
range->start = range->end;
}
return unmapped_error;
}
madvise_cold_or_pageout_pte_range — PTE 수준 페이지 비활성화/출력
// mm/madvise.c:352-565 (핵심 루프)
static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,
unsigned long addr, unsigned long end, struct mm_walk *walk)
{
// THP 처리: pmd_trans_huge 확인 → split 또는 직접 처리
// 일반 PTE 루프:
for (; addr < end; pte += nr, addr += nr * PAGE_SIZE) {
ptent = ptep_get(pte);
folio = vm_normal_folio(vma, addr, ptent);
// Large folio → madvise_folio_pte_batch()로 일괄 처리
// LRU 테스트 실패 또는 mapcount 불일치 시 건너뜀
if (!pageout && pte_young(ptent))
clear_young_dirty_ptes(vma, addr, pte, nr, CYDP_CLEAR_YOUNG); // COLD
folio_clear_referenced(folio);
if (pageout)
list_add(&folio->lru, &folio_list); // PAGEOUT: LRU에서 격리
else
folio_deactivate(folio); // COLD: active→inactive 이동
}
if (pageout)
reclaim_pages(&folio_list); // PAGEOUT: 즉시 회수
}
madvise_free_pte_range — lazyfree 마킹
// mm/madvise.c:651-782 (핵심 로직)
static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long end, struct mm_walk *walk)
{
for (; addr != end; pte += nr, addr += PAGE_SIZE * nr) {
ptent = ptep_get(pte);
// swap entry → PTE 클리어 (스왑인보다 페이지 할당+제로잉이 저렴)
if (!pte_present(ptent)) {
if (softleaf_is_swap(entry))
swap_put_entries_direct(entry, nr);
continue;
}
folio = vm_normal_folio(vma, addr, ptent);
// swap cache 또는 dirty → folio_free_swap 시도, dirty 해제
if (folio_test_swapcache(folio) || folio_test_dirty(folio)) {
if (!folio_free_swap(folio)) continue;
folio_clear_dirty(folio);
}
// young/dirty PTE 클리어
if (pte_young(ptent) || pte_dirty(ptent))
clear_young_dirty_ptes(vma, addr, pte, nr, cydp_flags);
folio_mark_lazyfree(folio); // lazyfree로 표시 → 메모리 부족 시 자동 해제
}
}
호출 흐름
SYSCALL_DEFINE3(madvise, ...)
└── do_madvise(mm, start, len_in, behavior)
├── madvise_should_skip() ← 유효성 검증
├── madvise_lock() ← behavior별 잠금 선택
│ └── get_lock_mode()
├── madvise_init_tlb() ← TLB batch 초기화
├── madvise_do_behavior()
│ ├── [MADV_HWPOISON/SOFT_OFFLINE] → madvise_inject_error()
│ ├── [MADV_POPULATE_*] → madvise_populate()
│ │ └── faultin_page_range()
│ └── [기타] → madvise_walk_vmas()
│ ├── try_vma_read_lock() ← VMA lock 우선 시도
│ └── madvise_vma_behavior() ← behavior별 분기
│ ├── MADV_WILLNEED → madvise_willneed()
│ │ ├── [anon] → swapin_walk_pmd_entry()
│ │ ├── [shmem] → shmem_swapin_range()
│ │ └── [file] → vfs_fadvise(POSIX_FADV_WILLNEED)
│ ├── MADV_COLD → madvise_cold()
│ │ └── madvise_cold_page_range()
│ │ └── madvise_cold_or_pageout_pte_range(pageout=false)
│ │ └── folio_deactivate()
│ ├── MADV_PAGEOUT → madvise_pageout()
│ │ └── madvise_pageout_page_range()
│ │ └── madvise_cold_or_pageout_pte_range(pageout=true)
│ │ └── reclaim_pages()
│ ├── MADV_DONTNEED/FREE → madvise_dontneed_free()
│ │ ├── [DONTNEED] → zap_page_range_single_batched()
│ │ └── [FREE] → madvise_free_single_vma()
│ │ └── madvise_free_pte_range() → folio_mark_lazyfree()
│ ├── MADV_REMOVE → madvise_remove()
│ │ └── vfs_fallocate(PUNCH_HOLE)
│ ├── MADV_COLLAPSE → madvise_collapse()
│ ├── MADV_GUARD_INSTALL → madvise_guard_install()
│ │ └── walk_page_range → guard marker PTE 설치
│ ├── MADV_GUARD_REMOVE → madvise_guard_remove()
│ └── [VMA 플래그] → madvise_update_vma()
│ └── vma_modify_flags() / vma_modify_name()
├── madvise_finish_tlb()
└── madvise_unlock()
조건별 비교
behavior별 잠금 모드
| behavior | 잠금 모드 | 설명 |
| `MADV_DONTNEED`, `MADV_DONTNEED_LOCKED`, `MADV_FREE` | VMA_READ_LOCK | 페이지 테이블 수정, 단일 VMA만 가능 |
| `MADV_GUARD_INSTALL`, `MADV_GUARD_REMOVE` | VMA_READ_LOCK | guard marker 설치/해제 |
| `MADV_WILLNEED`, `MADV_COLD`, `MADV_PAGEOUT` | MMAP_READ_LOCK | walk_page_range 사용 |
| `MADV_REMOVE` | MMAP_READ_LOCK | mmap_lock 해제 후 fallocate 호출 |
| `MADV_COLLAPSE` | MMAP_READ_LOCK | THP 동기 병합 |
| `MADV_POPULATE_READ`, `MADV_POPULATE_WRITE` | MMAP_READ_LOCK | faultin_page_range 사용 |
| `MADV_NORMAL/SEQUENTIAL/RANDOM/DONTFORK/DOFORK` | MMAP_WRITE_LOCK | vm_flags 변경 필요 |
| `MADV_WIPEONFORK/KEEPONFORK/DONTDUMP/DODUMP` | MMAP_WRITE_LOCK | vm_flags 변경 필요 |
| `MADV_MERGEABLE/UNMERGEABLE` | MMAP_WRITE_LOCK | KSM 설정 변경 |
| `MADV_HUGEPAGE/NOHUGEPAGE` | MMAP_WRITE_LOCK | THP 설정 변경 |
| `MADV_HWPOISON`, `MADV_SOFT_OFFLINE` | NO_LOCK | 메모리 오류 주입, 자체 처리 |
MADV_DONTNEED vs MADV_FREE 동작 비교
| 항목 | MADV_DONTNEED | MADV_FREE |
| 페이지 해제 시점 | 즉시 (zap_page_range) | 지연 (메모리 부족 시) |
| PTE 처리 | 페이지 테이블 완전 클리어 | lazyfree 마킹만 |
| dirty 페이지 | 드롭 (복구 불가) | 드롭 (복구 불가) |
| clean 페이지 | 즉시 반환 | free list에 유지 |
| 적용 대상 | 모든 VMA (HugeTLB 포함) | 익명 VMA만 |
| mmap_lock | VMA_READ_LOCK 필요 | VMA_READ_LOCK 필요 |
| userfaultfd | 제거 후 재설정 | 제거 후 재설정 |
MADV_COLD vs MADV_PAGEOUT 동작 비교
| 항목 | MADV_COLD | MADV_PAGEOUT |
| LRU 동작 | active → inactive 이동 | LRU에서 격리 → 즉시 회수 |
| 페이지 해제 | 메모리 부족 시 회수 | 즉시 회수 (reclaim_pages) |
| THP 처리 | split 시도 후 deactivation | split 시도 후 LRU 격리 |
| 파일 매핑 | 건너뜀 (can_do_file_pageout) | 쓰기 권한 확인 후 출력 |
| 적용 불가 | VM_LOCKED, VM_PFNMAP, VM_HUGEPAGE | VM_LOCKED, VM_PFNMAP, VM_HUGEPAGE |
process_madvise 원격 적용 가능 behavior
| behavior | 원격 적용 | 설명 |
| `MADV_COLD` | O | 페이지 비활성화 |
| `MADV_PAGEOUT` | O | 페이지 즉시 회수 |
| `MADV_WILLNEED` | O | readahead 트리거 |
| `MADV_COLLAPSE` | O | THP 동기 병합 |
| 그 외 | X | CAP_SYS_NICE 필요 + 비파괴적 behavior만 허용 |
MADV_* 전체 목록
| 상수 | 값 | 설명 | VMA 변경 |
| `MADV_NORMAL` | 0 | 기본 readahead | VM_SEQ_READ, VM_RAND_READ 해제 |
| `MADV_RANDOM` | 1 | 최소 readahead | VM_RAND_READ 설정 |
| `MADV_SEQUENTIAL` | 2 | 공격적 readahead | VM_SEQ_READ 설정 |
| `MADV_WILLNEED` | 3 | readahead 트리거 | 없음 |
| `MADV_DONTNEED` | 4 | 즉시 페이지 해제 | 없음 |
| `MADV_FREE` | 8 | lazyfree 마킹 | 없음 |
| `MADV_REMOVE` | 9 | 파일 hole punching | 없음 |
| `MADV_DONTFORK` | 10 | fork 시 복제 제외 | VM_DONTCOPY 설정 |
| `MADV_DOFORK` | 11 | fork 시 복제 포함 | VM_DONTCOPY 해제 |
| `MADV_MERGEABLE` | 12 | KSM 병합 대상 | VM_MERGEABLE 설정 |
| `MADV_UNMERGEABLE` | 13 | KSM 병합 제외 | VM_MERGEABLE 해제 |
| `MADV_HUGEPAGE` | 14 | THP 대상 | VM_HUGEPAGE 설정 |
| `MADV_NOHUGEPAGE` | 15 | THP 제외 | VM_NOHUGEPAGE 설정 |
| `MADV_DONTDUMP` | 16 | core dump 제외 | VM_DONTDUMP 설정 |
| `MADV_DODUMP` | 17 | core dump 포함 | VM_DONTDUMP 해제 |
| `MADV_WIPEONFORK` | 18 | fork 시 자식 zero-fill | VM_WIPEONFORK 설정 |
| `MADV_KEEPONFORK` | 19 | fork 시 데이터 유지 | VM_WIPEONFORK 해제 |
| `MADV_COLD` | 20 | 페이지 비활성화 | 없음 |
| `MADV_PAGEOUT` | 21 | 즉시 스왑아웃 | 없음 |
| `MADV_POPULATE_READ` | 22 | 읽기 fault 사전 생성 | 없음 |
| `MADV_POPULATE_WRITE` | 23 | 쓰기 fault 사전 생성 | 없음 |
| `MADV_DONTNEED_LOCKED` | 24 | locked 페이지 포함 해제 | 없음 |
| `MADV_COLLAPSE` | 25 | THP 동기 병합 | 없음 |
| `MADV_SOFT_OFFLINE` | 101 | 소프트 오프라인 | 없음 |
| `MADV_HWPOISON` | 100 | 하드웨어 오류 주입 | 없음 |
| `MADV_GUARD_INSTALL` | 102 | 접근 시 fatal signal | guard PTE 마커 |
| `MADV_GUARD_REMOVE` | 103 | guard 해제 | guard PTE 클리어 |
다이어그램
관련 문서
메모리 관리 개요
VMA / mmap
페이지 회수
process_vm_readv/writev
Swap / zswap
Huge Pages / THP
KSM
Sealing (mseal)
mlock / munlock
mprotect
mremap