Ryotta's Linux 7.0 MM

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

🚇 페이지 Migration

관련 소스: mm/migrate.c (2750줄), mm/migrate_device.c (1491줄)
관련 문서: 메모리 관리 개요 · Compaction · CMA · NUMA · HMM · Hotplug

개요 (Overview)

페이지 Migration은 리눅스 커널이 실행 중인 프로세스의 페이지를 물리적으로 다른 위치로 이동시키는 메커니즘입니다. 이는 Memory Compaction(단편화 해소), NUMA 밸런싱(노드 간 이동), CMA(연속 메모리 확보), 메모리 핫플러그(온라인/오프라인), HMM(이기종 메모리 관리) 등 여러 서브시스템에서 핵심 빌딩 블록으로 사용됩니다. 원본 페이지의 모든 매핑(PTE)을 migration swap entry로 교체한 뒤, 새 페이지로 데이터를 복사하고 매핑을 복원하는 3단계 프로세스로 동작합니다.

Migration의 핵심 아이디어는 "페이지가 이동되는 동안 프로세스가 접근하면 대기하게 만드는 것"입니다. PTE에 migration entry를 삽입하면, 접근 시 폴트가 발생하고 migration_entry_wait()가 완료될 때까지 블록됩니다. 이를 통해 무결성을 보장하면서 물리 메모리 내에서 페이지를 투명하게 이동할 수 있습니다.

도서관으로 비유하면, 책을 다른 서가로 옮기는 동안 임시 예약표를 붙여 두고, 다시 꽂히기 전까지는 대출 요청을 잠깐 멈추게 하는 방식과 비슷합니다.

소스 파일:
  mm/migrate.c            — 핵심 마이그레이션 엔진 (2750줄)
  mm/migrate_device.c     — 디바이스 메모리 마이그레이션 (1491줄)
  include/linux/migrate.h — 외부 API, struct movable_operations, migrate_vma
  include/linux/migrate_mode.h — enum migrate_mode, enum migrate_reason
Migration 호출 흐름
Migration 자료구조 관계도

빠른 점검 명령

# 마이그레이션 통계 확인
cat /proc/vmstat | grep -E 'pgmigrate|thp_migration|numa_pages_migrated'

# NUMA 밸런싱 활성화 상태 확인
cat /proc/sys/kernel/numa_balancing

# 현재 NUMA 정책과 노드 배치 확인
numactl --show
numastat -p $$

# 메모리 핫플러그 블록 상태 확인
cat /sys/devices/system/memory/auto_online_blocks 2>/dev/null
ls /sys/devices/system/memory/ | grep memory | head -5

# Compaction과 함께 마이그레이션 동작 관찰
cat /proc/vmstat | grep -E 'compact_stall|compact_success|compact_fail'
cat /proc/buddyinfo | head -4

# 페이지 폴트 기반 NUMA 이벤트 확인
cat /proc/vmstat | grep numa_hit

# NUMA 토폴로지와 노드 메모리 분포 확인
numactl -H
cat /proc/zoneinfo | grep -E 'Node|zone|present|managed|spanned' | head -40

# 마이그레이션 관련 커널 트레이스 (root 권한)
sudo perf trace -e 'migrate:*' --duration 10 2>/dev/null || true

# CMA 영역 정보 확인
cat /proc/meminfo | grep Cma

# 메모리 압박과 회수 강도 확인
cat /proc/pressure/memory
cat /proc/vmstat | grep -E 'allocstall|pgscan|pgsteal'

# 핫플러그/이기종 메모리 관련 메시지 확인
dmesg | grep -Ei 'migrate|numa|hotplug|cxl' | tail -20

핵심 자료구조

movable_operations — 드라이버 수준 페이지 이동 인터페이스

balloon, zsmalloc, hotplug 같은 이동 가능한 페이지 소비자가 쓰는 콜백 인터페이스입니다. LRU에 직접 올라가지 않는 페이지도 드라이버가 다시 받아 주고, 이동이 끝나면 원래 자료구조로 되돌릴 수 있게 합니다.

/* include/linux/migrate.h:44-49 */
struct movable_operations {
    bool (*isolate_page)(struct page *, isolate_mode_t);
    int (*migrate_page)(struct page *dst, struct page *src,
            enum migrate_mode);
    void (*putback_page)(struct page *);
};

migrate_vma — 디바이스 메모리 마이그레이션 컨텍스트

GPU/가속기 등 디바이스 메모리와 시스템 메모리 간 페이지 이동에 사용됩니다. src[]dst[] 배열은 MIGRATE_PFN_* 플래그와 PFN을 인코딩하여 각 가상 주소의 페이지 상태를 표현합니다.

src[]dst[](end - start) >> PAGE_SHIFT 개수만큼 준비되어야 하며, migrate_vma_setup() 이후에는 src[]를 건드리면 안 됩니다. migrate_vma_pages()가 끝난 뒤에는 dst[]도 더 이상 바꾸지 않아야 하고, fault_pagemigrate_to_ram() 흐름에서 디바이스 사설 페이지를 RAM으로 되돌릴 때 쓰입니다.

/* include/linux/migrate.h:158-191 */
struct migrate_vma {
    struct vm_area_struct   *vma;
    /*
     * src와 dst 배열은
     * (end - start) >> PAGE_SHIFT 개수만큼 충분히 커야 한다.
     *
     * 호출자는 migrate_vma_setup() 이후 src 배열을 수정하면 안 되고,
     * migrate_vma_pages()가 반환된 뒤에는 dst 배열도 바꾸면 안 된다.
     */
    unsigned long           *dst;
    unsigned long           *src;
    unsigned long           cpages;
    unsigned long           npages;
    unsigned long           start;
    unsigned long           end;

    /*
     * device private memory에서 옮길 때 page_pgmap(page)->owner에
     * 들어 있는 소유자 값과 맞춰 두는 필드다. 이 경우 flags도
     * MIGRATE_VMA_SELECT_DEVICE_PRIVATE로 설정해야 한다.
     * 호출자가 mmu notifier 콜백을 사용할 때는, 옮기지 않을
     * device private 페이지에 대한 device MMU invalidation을 피하려고
     * 이 값을 항상 채워야 한다.
     */
    void                    *pgmap_owner;
    unsigned long           flags;

    /*
     * migrate_to_ram() 콜백의 일부로 페이지를 옮길 때 vmf->page를 넣는다.
     */
    struct page             *fault_page;
};

migrate_pages_stats — 마이그레이션 결과 통계

배치 마이그레이션 중 발생하는 성공/실패/THP 분할 통계를 추적합니다. migrate_pages() 함수 종료 후 vmstat 카운터에 반영됩니다.

/* mm/migrate.c:1592-1601 */
struct migrate_pages_stats {
    int nr_succeeded;   /* base page 단위로 성공한 일반/대형 folio 수 */
    int nr_failed_pages;    /* base page 단위로 실패한 일반/대형 folio 수.
                   시도하지 않은 folio는 제외 */
    int nr_thp_succeeded;   /* THP 성공 수 */
    int nr_thp_failed;  /* THP 실패 수 */
    int nr_thp_split;   /* 마이그레이션 전 분할된 THP 수 */
    int nr_split;   /* THP를 포함한 대형 folio 분할 수 */
};

MIGRATE_PFN_* 플래그 — src/dst 배열 인코딩

migrate_vma API에서 src[]dst[] 배열의 각 엔트리는 PFN과 플래그를 비트 마스킹하여 인코딩됩니다. 비트 0-5는 플래그, 비트 6 이상은 PFN입니다.

플래그비트의미
`MIGRATE_PFN_VALID`bit 0유효한 PFN 엔트리
`MIGRATE_PFN_MIGRATE`bit 1마이그레이션 대상으로 표시
`MIGRATE_PFN_WRITE`bit 3쓰기 권한이 있는 페이지
`MIGRATE_PFN_COMPOUND`bit 4복합(compound) 페이지
`MIGRATE_PFN_SHIFT`6PFN 시작 비트 위치

핵심 함수

migrate_pages() — 최상위 배치 마이그레이션 API

마이그레이션의 메인 진입점입니다. hugetlb 페이지를 먼저 처리한 뒤, 나머지 folio를 배치(batch) 단위로 나누어 비동기 또는 동기 방식으로 마이그레이션합니다.

/* mm/migrate.c:2072-2074 */
int migrate_pages(struct list_head *from, new_folio_t get_new_folio,
        free_folio_t put_new_folio, unsigned long private,
        enum migrate_mode mode, int reason, unsigned int *ret_succeeded)

동작 흐름:

1. migrate_hugetlbs()로 HugeTLB folio를 먼저 처리

2. 남은 folio를 NR_MAX_BATCHED_MIGRATION(THP 사용 시 HPAGE_PMD_NR, 그 외 512) 단위로 배치 분할

3. MIGRATE_ASYNC이면 migrate_pages_batch(), 그 외이면 migrate_pages_sync()

4. 동기 모드에서는 먼저 ASYNC로 시도한 뒤 실패분을 개별 동기 처리

5. 분할된 split_folios도 ASYNC 모드로 한 번 더 시도

6. count_vm_events()로 vmstat 카운터 업데이트

migrate_folio_unmap() — folio 매핑 해제 단계

소스 folio의 모든 PTE를 migration entry로 교체하는 1단계 함수입니다. 페이지 잠금, writeback 대기, anon_vma 획득, try_to_migrate() 호출이 포함됩니다.

/* mm/migrate.c:1203-1206 */
static int migrate_folio_unmap(new_folio_t get_new_folio,
        free_folio_t put_new_folio, unsigned long private,
        struct folio *src, struct folio **dstp, enum migrate_mode mode,
        struct list_head *ret)

동작 흐름:

1. get_new_folio()로 대상 folio 할당

2. 소스 folio 잠금 (MIGRATE_ASYNC에서는 trylock, 실패 시 건너뜀)

3. writeback 중이면 MIGRATE_SYNC에서만 대기

4. 익명 페이지이면 folio_get_anon_vma()로 anon_vma 참조 획득

5. try_to_migrate()로 모든 PTE를 migration entry로 교체

6. 매핑 해제 성공 시 __migrate_folio_record()로 상태 저장 후 반환

migrate_folio_move() — folio 이동 단계

매핑이 해제된 소스 folio를 대상 folio로 실제로 이동시키는 2단계 함수입니다. move_to_new_folio()를 통해 address_space를 교체하고, 성공 시 remove_migration_ptes()로 새 folio를 다시 매핑합니다.

/* mm/migrate.c:1353-1356 */
static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,
              struct folio *src, struct folio *dst,
              enum migrate_mode mode, enum migrate_reason reason,
              struct list_head *ret)

동작 흐름:

1. __migrate_folio_extract()로 이전 상태 복원 정보 추출

2. movable_ops 페이지이면 migrate_movable_ops_page() 호출

3. move_to_new_folio()mapping->a_ops->migrate_folio() 콜백 호출

4. 성공 시 folio_add_lru(dst)로 대상 LRU 추가

5. 매핑된 상태였으면 remove_migration_ptes(src, dst)로 PTE 복원

6. 소스 folio 해제 및 migrate_folio_done() 호출

move_to_new_folio() — address_space 교체

실제 folio 교체를 처리하는 내부 함수입니다. 매핑 유형(무매핑, inaccessible, filesystem, anonymous, fallback)에 따라 적절한 마이그레이션 콜백을 선택합니다.

/* mm/migrate.c:1090-1092 */
static int move_to_new_folio(struct folio *dst, struct folio *src,
                enum migrate_mode mode)

분기 로직:

  • mapping == NULLmigrate_folio() (단순 매핑 없는 folio)
  • mapping_inaccessible(mapping)-EOPNOTSUPP (접근 불가 매핑)
  • mapping->a_ops->migrate_folio 존재 → filesystem/swap 콜백 호출
  • 그 외 → fallback_migrate_folio() (writepages 경고 + 강제 해제)
  • migrate_misplaced_folio() — NUMA 밸런싱용 단일 folio 마이그레이션

    NUMA 밸런싱에서 잘못 배치된 단일 folio를 지정 노드로 이동시킵니다. migrate_misplaced_folio_prepare()로 격리된 folio를 받아 migrate_pages()를 호출합니다.

    /* mm/migrate.c:2722-2723 */
    int migrate_misplaced_folio(struct folio *folio, int node)

    동작 흐름:

    1. folio를 migratepages 리스트에 추가

    2. alloc_misplaced_dst_folio() 콜백으로 대상 노드에 할당

    3. MIGRATE_ASYNC + MR_NUMA_MISPLACED로 마이그레이션 시도

    4. 실패 시 putback_movable_pages()로 복원

    5. 성공 시 NUMA_PAGE_MIGRATE vmstat 카운터 업데이트

    6. 티어링 모드에서 저속→고속 노드 이동 시 PGPROMOTE_SUCCESS 기록

    migrate_vma_setup() / migrate_vma_pages() / migrate_vma_finalize() — 디바이스 경로

    migrate_vma_setup()은 VMA 유효성 검사와 src[] 수집, migrate_vma_pages()는 드라이버가 복사한 뒤 페이지 상태 갱신, migrate_vma_finalize()는 CPU 페이지 테이블 복원을 맡습니다.

    /* include/linux/migrate.h:193-195 */
    int migrate_vma_setup(struct migrate_vma *args);
    void migrate_vma_pages(struct migrate_vma *migrate);
    void migrate_vma_finalize(struct migrate_vma *migrate);

    동작 흐름:

    1. migrate_vma_setup()이 VMA, src[], dst[], fault_page를 검사하고 페이지를 잠근 뒤 언매핑

    2. 드라이버가 dst[]를 기준으로 디바이스 메모리를 할당하고 데이터를 복사

    3. migrate_vma_pages()가 성공한 PFN에 MIGRATE_PFN_MIGRATE를 남김

    4. migrate_vma_finalize()가 CPU 페이지 테이블을 새 페이지 또는 원래 페이지로 정리

    구현은 migrate_vma_collect_pmd()가 PMD/PTE를 훑어 src[]를 채우고, migrate_device_unmap()이 장치 사설 페이지를 떼어낸 뒤 migrate_device_pages()migrate_device_finalize()가 메타데이터와 PTE를 마무리합니다.

    호출 흐름

    migrate_pages()                          ← 최상위 API
    ├── migrate_hugetlbs()                   ← HugeTLB 먼저 처리
    │   └── unmap_and_move_huge_page()
    │       ├── get_new_folio()              ← 대상 할당
    │       ├── try_to_migrate()             ← PTE → migration entry
    │       └── move_to_new_folio()          ← 실제 이동
    ├── migrate_pages_batch()                ← 일반 folio 배치 처리
    │   ├── [pass 1] unmapping 단계
    │   │   ├── try_split_folio()            ← THP 분할 필요 시
    │   │   ├── migrate_folio_unmap()        ← PTE 교체
    │   │   │   ├── get_new_folio()
    │   │   │   ├── try_to_migrate()
    │   │   │   └── __migrate_folio_record()
    │   │   └── unmap_folios/dst_folios 리스트 구축
    │   └── [pass 2] moving 단계
    │       └── migrate_folios_move()
    │           └── migrate_folio_move()
    │               ├── move_to_new_folio()  ← 매핑 교체
    │               ├── folio_add_lru()      ← 대상 LRU 추가
    │               └── remove_migration_ptes() ← PTE 복원
    └── migrate_pages_sync()                 ← 동기 모드 시
        ├── migrate_pages_batch(ASYNC)       ← 먼저 ASYNC로 시도
        └── 개별 동기 처리 (남은 실패분)
    
    migrate_vma_setup()                      ← 디바이스 마이그레이션 진입
    ├── migrate_vma_collect()                ← 페이지 테이블 워크
    │   └── walk_page_range(migrate_vma_walk_ops)
    │       └── migrate_vma_collect_pmd()
    │           ├── migrate_vma_collect_huge_pmd()
    │           └── [PTE 순회] → src[] 배열 채우기
    └── migrate_vma_unmap()                  ← 매핑 해제
        └── migrate_device_unmap()
            ├── folio_isolate_lru()
            └── try_to_migrate()

    조건별 비교

    마이그레이션 모드 비교

    모드동작 방식잠금 블록writeback 대기사용 시점
    `MIGRATE_ASYNC`절대 블록하지 않음trylock만 사용대기 안 함Compaction, proactive
    `MIGRATE_SYNC_LIGHT`대부분의 잠금에서 블록 가능lock 허용uptodata 아닌 경우만NUMA 밸런싱
    `MIGRATE_SYNC`모든 잠금에서 블록lock 허용대기move_pages 시스템 콜

    마이그레이션 사유(migrate_reason) 비교

    사유상수트리거
    Compaction`MR_COMPACTION`단편화 해소를 위한 kcompactd/direct compaction
    Memory Failure`MR_MEMORY_FAILURE`하드웨어 메모리 오류 (ECC 수정 불가)
    Memory Hotplug`MR_MEMORY_HOTPLUG`메모리 블록 오프라인
    Syscall`MR_SYSCALL``move_pages()` 시스템 콜
    mbind`MR_MEMPOLICY_MBIND``mbind()`로 NUMA 정책 변경 시
    NUMA Misplaced`MR_NUMA_MISPLACED`AutoNUMA가 잘못 배치된 페이지 감지
    Contig Range`MR_CONTIG_RANGE`연속 범위 할당 (CMA 등)
    Long-term Pin`MR_LONGTERM_PIN`VFIO/장치 드라이버의 장기 핀ning
    Demotion`MR_DEMOTION`메모리 티어링에서 고속→저속 하향
    DAMON`MR_DAMON`DAMON 기반 접근 패턴 분석

    디바이스 마이그레이션 vs 일반 마이그레이션 비교

    항목일반 마이그레이션 (migrate.c)디바이스 마이그레이션 (migrate_device.c)
    진입 방식`migrate_pages()` + `migrate_reason``migrate_vma_setup()` → `migrate_vma_pages()` → `migrate_vma_finalize()`
    대상LRU에 있는 일반 folioZONE_DEVICE folio (GPU/HBM)
    격리`folio_isolate_lru()``folio_get()`으로 고정 (LRU 없음)
    매핑 해제`try_to_migrate()` + rmap walk`try_to_migrate()` + mm_walk
    콜백`mapping->a_ops->migrate_folio()`드라이버가 dst 할당 + 복사
    API`migrate_pages()``migrate_vma_setup/pages/finalize()`
    THP 처리배치 내 분할 지원`migrate_vma_split_folio()`
    결과 확인반환값 + ret_succeeded`src[]` 배열의 MIGRATE_PFN_MIGRATE 플래그

    실패 시 처리 비교

    반환값의미처리 방법
    0성공src folio 해제, dst LRU 추가
    `-EAGAIN`일시적 실패리스트에 유지, 재시도
    `-ENOMEM`메모리 부족남은 처리 중단, 실패 목록으로 이동
    `-EBUSY`영구 실패실패 목록으로 이동, 재시도 안 함
    `-EOPNOTSUPP`미지원 매핑inaccessible 매핑

    관련 문서

  • 메모리 관리 개요 — 전체 메모리 관리 아키텍처 조감도
  • Buddy Allocator — 페이지 할당 기반
  • Compaction — 단편화 해소를 위한 Two-Scanner 알고리즘
  • CMA — 연속 메모리 할당에서의 마이그레이션 활용
  • NUMA — NUMA 밸런싱과 자동 페이지 이동
  • HMM — 이기종 메모리 관리 프레임워크
  • Hotplug — 메모리 블록 온라인/오프라인
  • Folio — folio 추상화와 마이그레이션 통합
  • Memory Tiers — 메모리 티어링과 demotion