Ryotta's Linux 7.0 MM

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

# Reverse Mapping (rmap)

관련 소스: mm/rmap.c, include/linux/rmap.h

개요 (Overview)

Reverse Mapping(rmap)은 물리 페이지(folio)로부터 그 페이지가 매핑된 모든 가상 주소를 찾는 메커니즘입니다. Linux 커널에서 페이지 회수(reclaim), KSM(Kernel Samepage Merging), 페이지 마이그레이션, 페이지 제거(zap) 등 핵심 동작은 "물리 페이지 → 어떤 VMA에 매핑되어 있는가?"를 추적해야 하며, rmap이 이를 담당합니다.

rmap은 두 가지 유형의 매핑을 구분합니다: 익명(anon) 페이지는 anon_vma 체인을 통해 추적하고, 파일(file) 기반 페이지는 address_space->i_mmap interval tree를 통해 추적합니다. 각 folio의 mapping 필드가 anon_vma 또는 address_space를 가리키며, rmap walk 시 해당 구조체를 순회하여 모든 매핑 VMA를 찾습니다.

일상 비유로 보면 rmap은 도서관의 대출 카드 목록과 비슷합니다. 책 한 권(folio)은 실제로 여러 사람의 손에 건네질 수 있고, 반납·이동·폐기 시점에는 "이 책을 누가 어디에서 빌려 갔는가"를 바로 찾아야 합니다. rmap은 바로 그 역방향 색인입니다.

이 문서에서 보는 rmap은 페이지 폴트와 COW까지 이어집니다. 처음 접근한 주소는 페이지 폴트로 물리 페이지를 만들고, fork() 뒤 쓰기 시점에는 COW로 새 페이지를 복사합니다. 이후 회수나 마이그레이션이 오면 rmap이 각 VMA를 다시 찾아 mapcount와 PTE를 정리합니다.

소스 파일 경로:
  mm/rmap.c                          ← rmap 핵심 구현 (3147줄)
  include/linux/rmap.h               ← rmap 공개 API, 구조체 정의 (995줄)
  mm/internal.h                      ← 내부 유틸리티
rmap 구조 관계도
rmap 호출 흐름

빠른 점검 명령

rmap만 단독으로 보지 말고, 전체 메모리 압력과 페이지 폴트 추세를 같이 보면 원인을 잡기 쉽습니다.

# anon_vma 캐시 통계
grep -i "anon_vma" /proc/slabinfo

# 모든 메모리 맵에서 rmap 관련 정보 확인
cat /proc/<pid>/smaps | head -50

# 페이지 매핑 카운트 확인 (mapcount)
cat /proc/kpagecount | head -20

# rmap 관련 트레이스 이벤트 확인
ls /sys/kernel/debug/tracing/events/rmap/ 2>/dev/null

# folio mapcount 디버그 (CONFIG_DEBUG_VM 빌드)
cat /sys/kernel/debug/rmap_stats 2>/dev/null

# KSM이 rmap을 사용하는지 확인
cat /sys/kernel/mm/ksm/pages_shared
cat /sys/kernel/mm/ksm/pages_sharing

# 페이지 테이블 walk를 통한 매핑 확인
cat /proc/<pid>/pagemap | head -20

# 메모리 회수 시 rmap walk 동작 확인
echo 1 > /proc/sys/vm/drop_caches
dmesg | tail -20

# 페이지 폴트와 익명/파일 페이지 규모 확인
cat /proc/vmstat | grep -E 'pgfault|pgmajfault|nr_anon_pages|nr_file_pages|nr_page_table_pages'
cat /proc/meminfo | grep -E 'AnonPages|Mapped|Cached|SwapCached|PageTables'

# 전체 메모리 압력과 분포 확인
cat /proc/pressure/memory
cat /proc/buddyinfo
cat /proc/slabinfo | grep -E 'anon_vma|anon_vma_chain|vm_area_struct'

같이 보는 지표

지표의미함께 보는 이유
`AnonPages`익명 페이지 규모`rmap_walk_anon()`과 COW 활동이 많을수록 같이 커지기 쉽습니다.
`Cached`파일 페이지 규모`rmap_walk_file()` 경로와 page cache 회수가 자주 일어나는지 보기에 좋습니다.
`pgfault` / `pgmajfault`페이지 폴트 추세새 매핑과 실제 I/O가 얼마나 자주 발생하는지 확인합니다.
`PageTables`페이지 테이블 비용mapcount 변화와 PTE 정리 부담을 같이 가늠할 수 있습니다.
`anon_vma` / `anon_vma_chain`rmap 메타데이터fork, COW, 익명 페이지 수가 늘 때 slab 사용량이 함께 커집니다.

핵심 자료구조

struct anon_vma

익명 페이지의 역방향 매핑을 관리하는 핵심 구조체입니다. fork된 프로세스 간에 트리 형태로 연결됩니다.

// include/linux/rmap.h:32-68
struct anon_vma {
    struct anon_vma *root;          // 이 anon_vma 트리의 루트
    struct rw_semaphore rwsem;      // 수정: 쓰기잠금, 순회: 읽기잠금
    atomic_t refcount;              // 참조 카운트
    unsigned long num_children;     // 자식 anon_vma 수 (자기 포함)
    unsigned long num_active_vmas;  // 이 anon_vma를 가리키는 VMA 수
    struct anon_vma *parent;        // 부모 anon_vma
    struct rb_root_cached rb_root;  // 관련 VMA의 interval tree
};

struct anon_vma_chain (AVC)

VMA와 anon_vma를 연결하는 체인 구조체입니다. 하나의 VMA는 여러 anon_vma에 연결될 수 있고, 하나의 anon_vma는 여러 VMA를 가질 수 있습니다.

// include/linux/rmap.h:83-92
struct anon_vma_chain {
    struct vm_area_struct *vma;         // 연결된 VMA
    struct anon_vma *anon_vma;          // 연결된 anon_vma
    struct list_head same_vma;          // 같은 VMA의 다른 AVC 목록
    struct rb_node rb;                  // anon_vma interval tree 노드
    unsigned long rb_subtree_last;      // interval tree 서브트리 마지막 값
};

struct rmap_walk_control

rmap walk를 제어하는 컨트롤 구조체입니다. 각 walk 유형에 맞는 콜백을 설정합니다.

// include/linux/rmap.h:951-965
struct rmap_walk_control {
    void *arg;                          // 콜백에 전달되는 인자
    bool try_lock;                      // 락 획득 실패 시 즉시 반환
    bool contended;                     // 락 경쟁으로 중단됨 플래그
    bool (*rmap_one)(struct folio *folio, struct vm_area_struct *vma,
                     unsigned long addr, void *arg);  // 매 VMA에서 호출될 콜백
    int (*done)(struct folio *folio);   // 순회 종료 조건 검사
    struct anon_vma *(*anon_lock)(const struct folio *folio,
                                  struct rmap_walk_control *rwc);  // anon 락 획득
    bool (*invalid_vma)(struct vm_area_struct *vma, void *arg);    // 건너뛸 VMA 판별
};

struct page_vma_mapped_walk (PVMW)

특정 페이지가 어떤 VMA에 매핑되어 있는지 순회하는 구조체입니다.

// include/linux/rmap.h:864-874
struct page_vma_mapped_walk {
    unsigned long pfn;          // 찾는 페이지의 PFN
    unsigned long nr_pages;     // folio의 페이지 수
    pgoff_t pgoff;              // folio의 페이지 오프셋
    struct vm_area_struct *vma; // 대상 VMA
    unsigned long address;      // 현재 가상 주소
    pmd_t *pmd;                 // 매핑된 PMD 엔트리
    pte_t *pte;                 // 매핑된 PTE 엔트리
    spinlock_t *ptl;            // 페이지 테이블 잠금
    unsigned int flags;         // PVMW_SYNC, PVMW_MIGRATION 등
};

enum ttu_flags (Try To Unmap 플래그)

// include/linux/rmap.h:94-105
enum ttu_flags {
    TTU_USE_SHARED_ZEROPAGE = 0x2,   // large folio 미사용 페이지용
    TTU_SPLIT_HUGE_PMD    = 0x4,     // THP PMD를 PTE로 분할
    TTU_IGNORE_MLOCK       = 0x8,    // mlock 무시
    TTU_SYNC               = 0x10,   // racy 검사 회피 (동기화)
    TTU_HWPOISON          = 0x20,    // hwpoison 엔트리로 변환
    TTU_BATCH_FLUSH        = 0x40,   // TLB flush 배치 처리
    TTU_RMAP_LOCKED        = 0x80,   // rmap 락 미획득 (호출자가 보유)
};

핵심 함수

1. rmap_walk — rmap 순회 엔트리 포인트

folio의 모든 매핑 VMA를 순회합니다. folio 유형에 따라 anon, file, KSM 순회로 분기합니다.

// mm/rmap.c:3093-3101
void rmap_walk(struct folio *folio, struct rmap_walk_control *rwc)
{
    if (unlikely(folio_test_ksm(folio)))
        rmap_walk_ksm(folio, rwc);        // KSM folio 처리
    else if (folio_test_anon(folio))
        rmap_walk_anon(folio, rwc, false); // 익명 페이지 처리
    else
        rmap_walk_file(folio, rwc, false); // 파일 기반 페이지 처리
}

분기 로직:

  • KSM folio → rmap_walk_ksm(): KSM stable/unstable tree 순회
  • anon folio → rmap_walk_anon(): anon_vma->rb_root interval tree 순회
  • file folio → rmap_walk_file(): mapping->i_mmap interval tree 순회
  • 2. rmap_walk_anon — 익명 페이지 rmap 순회

    anon_vma 트리에서 모든 관련 VMA를 찾아 rmap_one 콜백을 실행합니다.

    // mm/rmap.c:2956-3000
    static void rmap_walk_anon(struct folio *folio,
            struct rmap_walk_control *rwc, bool locked)
    {
        struct anon_vma *anon_vma;
        pgoff_t pgoff_start, pgoff_end;
        struct anon_vma_chain *avc;
    
        /* folio lock이 있어야 mapping이 바뀌지 않습니다. */
        VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
    
        if (locked) {
            anon_vma = folio_anon_vma(folio);  // 이미 락된 경우
            /* anon_vma가 사라졌다면 안 됩니다. */
            VM_BUG_ON_FOLIO(!anon_vma, folio);
        } else {
            anon_vma = rmap_walk_anon_lock(folio, rwc); // 락 획득
        }
    
        if (!anon_vma) return;
    
        pgoff_start = folio_pgoff(folio);
        pgoff_end = pgoff_start + folio_nr_pages(folio) - 1;
    
        /* interval tree에서 겹치는 모든 VMA를 순회합니다. */
        anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root,
                pgoff_start, pgoff_end) {
            struct vm_area_struct *vma = avc->vma;
            unsigned long address = vma_address(vma, pgoff_start,
                    folio_nr_pages(folio));
    
            VM_BUG_ON_VMA(address == -EFAULT, vma);
            cond_resched();
    
            if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
                continue;  // 건너뛸 VMA
    
            if (!rwc->rmap_one(folio, vma, address, rwc->arg))
                break;     // 콜백이 false 반환 시 순회 중단
            if (rwc->done && rwc->done(folio))
                break;     // 종료 조건 충족
        }
    
        if (!locked)
            anon_vma_unlock_read(anon_vma);
    }

    3. try_to_unmap — 페이지 매핑 제거

    folio의 모든 페이지 테이블 매핑을 제거합니다. 페이지 회수(swap out), OOM kill 시 사용됩니다.

    // mm/rmap.c:2386-2399
    void try_to_unmap(struct folio *folio, enum ttu_flags flags)
    {
        struct rmap_walk_control rwc = {
            .rmap_one = try_to_unmap_one,     // 매 VMA에서 PTE 제거
            .arg = (void *)flags,
            .done = folio_not_mapped,          // 모든 매핑 제거 시 종료
            .anon_lock = folio_lock_anon_vma_read,
        };
    
        if (flags & TTU_RMAP_LOCKED)
            rmap_walk_locked(folio, &rwc);    // 이미 rmap 락 보유
        else
            rmap_walk(folio, &rwc);           // 락 없이 순회
    }

    try_to_unmap_one 핵심 분기:

  • VM_LOCKED VMA → mlock 복원 후 중단
  • PMD 매핑 THP → TTU_SPLIT_HUGE_PMD 플래그 시 PTE로 분할
  • 익명 페이지 → swap entry로 교체 (page_swap_entry())
  • 파일 기반 페이지 → counter 감소만 수행
  • hwpoison 페이지 → hwpoison swap entry로 교체
  • 4. try_to_migrate — 페이지 마이그레이션을 위한 매핑 교체

    모든 PTE를 migration entry로 교체합니다. 실제 메모리 복사 전에 사용됩니다.

    // mm/rmap.c:2731-2767
    void try_to_migrate(struct folio *folio, enum ttu_flags flags)
    {
        struct rmap_walk_control rwc = {
            .rmap_one = try_to_migrate_one,   // 매 VMA에서 migration entry 설치
            .arg = (void *)flags,
            .done = folio_not_mapped,
            .anon_lock = folio_lock_anon_vma_read,
        };
    
        // 유효하지 않은 플래그 검증
        if (WARN_ON_ONCE(flags & ~(TTU_RMAP_LOCKED | TTU_SPLIT_HUGE_PMD |
                        TTU_SYNC | TTU_BATCH_FLUSH)))
            return;
    
        // 디바이스 전용 folio 처리 제한
        if (folio_is_zone_device(folio) &&
            (!folio_is_device_private(folio) && !folio_is_device_coherent(folio)))
            return;
    
        // exec() 중 임시 VMA 건너뛰기
        if (!folio_test_ksm(folio) && folio_test_anon(folio))
            rwc.invalid_vma = invalid_migration_vma;
    
        if (flags & TTU_RMAP_LOCKED)
            rmap_walk_locked(folio, &rwc);
        else
            rmap_walk(folio, &rwc);
    }

    5. folio_referenced — 페이지 참조 검사

    folio가 참조(referenced)되었는지 모든 매핑에서 검사합니다. 페이지 회수 시 유휴(idle) 판단에 사용됩니다.

    // mm/rmap.c:1059-1094
    int folio_referenced(struct folio *folio, int is_locked,
                         struct mem_cgroup *memcg, vm_flags_t *vm_flags)
    {
        bool we_locked = false;
        struct folio_referenced_arg pra = {
            .mapcount = folio_mapcount(folio),
            .memcg = memcg,
        };
        struct rmap_walk_control rwc = {
            .rmap_one = folio_referenced_one,       // 매 VMA에서 referenced 검사
            .arg = (void *)&pra,
            .anon_lock = folio_lock_anon_vma_read,
            .try_lock = true,                        // 락 경쟁 시 회피
            .invalid_vma = invalid_folio_referenced_vma,
        };
    
        *vm_flags = 0;
        if (!pra.mapcount)
            return 0;
    
        if (!folio_raw_mapping(folio))
            return 0;
    
        /* folio_trylock으로 안전하게 잠금을 획득합니다. */
        if (!is_locked) {
            we_locked = folio_trylock(folio);
            if (!we_locked) return 1;
        }
    
        rmap_walk(folio, &rwc);
        *vm_flags = pra.vm_flags;
    
        if (we_locked)
            folio_unlock(folio);
    
        return rwc.contended ? -1 : pra.referenced;
    }

    호출 흐름

    try_to_unmap(folio, flags)
      └─ rmap_walk(folio, &rwc)
           ├─ [anon] rmap_walk_anon(folio, rwc, false)
           │    ├─ rmap_walk_anon_lock(folio, rwc)  ← anon_vma 락 획득
           │    ├─ anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root)
           │    │    ├─ rwc->invalid_vma(vma, arg)  ← 건너뛸 VMA 확인
           │    │    ├─ rwc->rmap_one(folio, vma, address, arg)
           │    │    │    └─ try_to_unmap_one(folio, vma, address, arg)
           │    │    │         ├─ page_vma_mapped_walk(&pvmw)  ← PTE 순회
           │    │    │         │    ├─ [THP] split_huge_pmd_locked() → 재순회
           │    │    │         │    ├─ [anon] folio_try_share_anon_rmap_pte()
           │    │    │         │    ├─ get_and_clear_ptes()  ← PTE 제거
           │    │    │         │    ├─ set_pte_at(swp_pte)   ← swap entry 설치
           │    │    │         │    └─ folio_remove_rmap_ptes()  ← mapcount 감소
           │    │    │         └─ mmu_notifier_invalidate_range_end()
           │    │    └─ rwc->done(folio)  ← folio_not_mapped() 확인
           │    └─ anon_vma_unlock_read(anon_vma)
           ├─ [file] rmap_walk_file(folio, rwc, false)
           │    ├─ i_mmap_lock_read(mapping)
           │    ├─ vma_interval_tree_foreach(vma, &mapping->i_mmap)
           │    │    └─ rwc->rmap_one(folio, vma, address, arg)
           │    └─ i_mmap_unlock_read(mapping)
           └─ [KSM] rmap_walk_ksm(folio, rwc)

    조건별 비교

    rmap walk 유형 비교

    항목rmap_walk_anonrmap_walk_filermap_walk_ksm
    **대상**익명 페이지 (malloc 등)파일 매핑 (mmap 등)KSM 병합 페이지
    **데이터 구조**anon_vma->rb_root (interval tree)address_space->i_mmap (interval tree)stable_node tree
    **락**anon_vma->root->rwsem (읽기)mapping->i_mmap_rwsem (읽기)KSM lock
    **순회 대상**anon_vma_chain → VMAVMA interval tree → VMAstable_node → rmap_item → VMA
    **호출 경로**회수, OOM, KSM 스캔회수, truncate, fsyncKSM 병합

    try_to_unmap vs try_to_migrate 비교

    항목try_to_unmaptry_to_migrate
    **목적**페이지 매핑 완전 제거PTE를 migration entry로 교체
    **PTE 후속 처리**hwpoison/swap entry 또는 제거writable/readable/exclusive migration entry
    **VM_LOCKED 처리**mlock 복원 후 중단무시 (mlock 불필요)
    **TTU_HWPOISON**지원미지원
    **TTU_IGNORE_MLOCK**지원해당 없음
    **호출 시점**페이지 회수(swap out), OOM페이지 마이그레이션 시작 전

    anon 페이지 매핑 추가 API 비교

    함수시나리오레벨mapcount 처리
    `folio_add_anon_rmap_ptes()`기존 folio에 PTE 매핑 추가PTEatomic_inc_and_test
    `folio_add_anon_rmap_pmd()`기존 folio에 PMD 매핑 추가PMDatomic_inc (entire_mapcount)
    `folio_add_new_anon_rmap()`새 folio에 첫 매핑PTE/PMDatomic_set(0) (새로 시작)
    `folio_add_file_rmap_ptes()`파일 매핑 PTE 추가PTEatomic_inc_and_test
    `folio_add_file_rmap_pmd()`파일 매핑 PMD 추가PMDatomic_inc (entire_mapcount)

    TTU 플래그 사용 시나리오 비교

    플래그try_to_unmaptry_to_migrate비고
    TTU_SPLIT_HUGE_PMDTHP PMD를 PTE로 분할 후 재순회migration entry 설치 전 분할
    TTU_SYNCPTE 제거 후 동기화 대기마이그레이션 대기
    TTU_HWPOISONhwpoison swap entry 설치미지원
    TTU_BATCH_FLUSHTLB flush 배치 처리미지원
    TTU_RMAP_LOCKEDrmap 락 미획득rmap 락 미획득
    TTU_IGNORE_MLOCKVM_LOCKED 무시해당 없음

    lock ordering

    rmap 관련 잠금 순서 (mm/rmap.c 주석에서 발췌):

    inode->i_rwsem (쓰기/트렁케이트 시)
      mm->mmap_lock
        mapping->invalidate_lock
          folio_lock
            hugetlbfs_i_mmap_rwsem_key
              vma_start_write
                mapping->i_mmap_rwsem
                  anon_vma->rwsem
                    mm->page_table_lock or pte_lock
                      swap_lock
                        mmlist_lock

    관련 문서

  • 메모리 관리 개요
  • Buddy Allocator
  • VMA / mmap
  • 페이지 회수
  • Folio / Page Cache
  • Migration
  • KSM
  • OOM Killer
  • Compaction
  • tmpfs / shmem