Ryotta's Linux 7.0 MM

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

# 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.hMADV_* 상수 정의 (0~25, 100~103)
  • include/linux/mm.hVM_* 플래그 (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_LOCKguard marker 설치/해제
    `MADV_WILLNEED`, `MADV_COLD`, `MADV_PAGEOUT`MMAP_READ_LOCKwalk_page_range 사용
    `MADV_REMOVE`MMAP_READ_LOCKmmap_lock 해제 후 fallocate 호출
    `MADV_COLLAPSE`MMAP_READ_LOCKTHP 동기 병합
    `MADV_POPULATE_READ`, `MADV_POPULATE_WRITE`MMAP_READ_LOCKfaultin_page_range 사용
    `MADV_NORMAL/SEQUENTIAL/RANDOM/DONTFORK/DOFORK`MMAP_WRITE_LOCKvm_flags 변경 필요
    `MADV_WIPEONFORK/KEEPONFORK/DONTDUMP/DODUMP`MMAP_WRITE_LOCKvm_flags 변경 필요
    `MADV_MERGEABLE/UNMERGEABLE`MMAP_WRITE_LOCKKSM 설정 변경
    `MADV_HUGEPAGE/NOHUGEPAGE`MMAP_WRITE_LOCKTHP 설정 변경
    `MADV_HWPOISON`, `MADV_SOFT_OFFLINE`NO_LOCK메모리 오류 주입, 자체 처리

    MADV_DONTNEED vs MADV_FREE 동작 비교

    항목MADV_DONTNEEDMADV_FREE
    페이지 해제 시점즉시 (zap_page_range)지연 (메모리 부족 시)
    PTE 처리페이지 테이블 완전 클리어lazyfree 마킹만
    dirty 페이지드롭 (복구 불가)드롭 (복구 불가)
    clean 페이지즉시 반환free list에 유지
    적용 대상모든 VMA (HugeTLB 포함)익명 VMA만
    mmap_lockVMA_READ_LOCK 필요VMA_READ_LOCK 필요
    userfaultfd제거 후 재설정제거 후 재설정

    MADV_COLD vs MADV_PAGEOUT 동작 비교

    항목MADV_COLDMADV_PAGEOUT
    LRU 동작active → inactive 이동LRU에서 격리 → 즉시 회수
    페이지 해제메모리 부족 시 회수즉시 회수 (reclaim_pages)
    THP 처리split 시도 후 deactivationsplit 시도 후 LRU 격리
    파일 매핑건너뜀 (can_do_file_pageout)쓰기 권한 확인 후 출력
    적용 불가VM_LOCKED, VM_PFNMAP, VM_HUGEPAGEVM_LOCKED, VM_PFNMAP, VM_HUGEPAGE

    process_madvise 원격 적용 가능 behavior

    behavior원격 적용설명
    `MADV_COLD`O페이지 비활성화
    `MADV_PAGEOUT`O페이지 즉시 회수
    `MADV_WILLNEED`Oreadahead 트리거
    `MADV_COLLAPSE`OTHP 동기 병합
    그 외XCAP_SYS_NICE 필요 + 비파괴적 behavior만 허용

    MADV_* 전체 목록

    상수설명VMA 변경
    `MADV_NORMAL`0기본 readaheadVM_SEQ_READ, VM_RAND_READ 해제
    `MADV_RANDOM`1최소 readaheadVM_RAND_READ 설정
    `MADV_SEQUENTIAL`2공격적 readaheadVM_SEQ_READ 설정
    `MADV_WILLNEED`3readahead 트리거없음
    `MADV_DONTNEED`4즉시 페이지 해제없음
    `MADV_FREE`8lazyfree 마킹없음
    `MADV_REMOVE`9파일 hole punching없음
    `MADV_DONTFORK`10fork 시 복제 제외VM_DONTCOPY 설정
    `MADV_DOFORK`11fork 시 복제 포함VM_DONTCOPY 해제
    `MADV_MERGEABLE`12KSM 병합 대상VM_MERGEABLE 설정
    `MADV_UNMERGEABLE`13KSM 병합 제외VM_MERGEABLE 해제
    `MADV_HUGEPAGE`14THP 대상VM_HUGEPAGE 설정
    `MADV_NOHUGEPAGE`15THP 제외VM_NOHUGEPAGE 설정
    `MADV_DONTDUMP`16core dump 제외VM_DONTDUMP 설정
    `MADV_DODUMP`17core dump 포함VM_DONTDUMP 해제
    `MADV_WIPEONFORK`18fork 시 자식 zero-fillVM_WIPEONFORK 설정
    `MADV_KEEPONFORK`19fork 시 데이터 유지VM_WIPEONFORK 해제
    `MADV_COLD`20페이지 비활성화없음
    `MADV_PAGEOUT`21즉시 스왑아웃없음
    `MADV_POPULATE_READ`22읽기 fault 사전 생성없음
    `MADV_POPULATE_WRITE`23쓰기 fault 사전 생성없음
    `MADV_DONTNEED_LOCKED`24locked 페이지 포함 해제없음
    `MADV_COLLAPSE`25THP 동기 병합없음
    `MADV_SOFT_OFFLINE`101소프트 오프라인없음
    `MADV_HWPOISON`100하드웨어 오류 주입없음
    `MADV_GUARD_INSTALL`102접근 시 fatal signalguard PTE 마커
    `MADV_GUARD_REMOVE`103guard 해제guard PTE 클리어

    다이어그램

    madvise 호출 흐름
    madvise 자료구조 관계도

    관련 문서

  • 메모리 관리 개요
  • VMA / mmap
  • 페이지 회수
  • process_vm_readv/writev
  • Swap / zswap
  • Huge Pages / THP
  • KSM
  • Sealing (mseal)
  • mlock / munlock
  • mprotect
  • mremap