Ryotta's Linux 7.0 MM

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

24. 메모리 티어링 (Memory Tiers)

개요

메모리 티어링은 Linux 커널이 다양한 속도의 메모리 계층(HBM, DRAM, PMEM 등)을 자동으로 관리하는 메커니즘입니다. 각 메모리 노드에 "추상 거리(abstract distance)"를 부여하여, 빠른 메모리에서 느린 메모리로의 자동 마이그레이션(demotion)과 그 반대(promotion)를 수행합니다. 이를 통해 NUMA 환경에서 메모리 성능을 최적화하고, 컨테이너나 클라우드 환경에서 메모리 계층 구조를 유연하게 운용할 수 있습니다.

이 서브시스템은 크게 3가지 기능을 담당합니다: (1) 메모리 티어 구조 생성 및 관리, (2) 노드 간 마이그레이션 경로(demotion path) 결정, (3) 메모리 성능 좌표를 기반으로 한 추상 거리 계산. 핵심 소스 파일은 mm/memory-tiers.c이며, include/linux/memory-tiers.h에 공개 API가 정의되어 있습니다.

일상 비유로 보면, 이 기능은 큰 도서관에서 자주 찾는 책을 출입구 가까운 서가에 두고, 덜 쓰는 책을 더 먼 서가로 옮기는 운영 방식과 비슷합니다. 책의 위치를 바꾸는 기준이 바로 추상 거리이고, 어떤 서가로 옮길지는 노드 거리와 성능 좌표를 함께 보고 정합니다.

소스 파일 경로:
  mm/memory-tiers.c          ← 티어링 핵심 로직
  include/linux/memory-tiers.h ← 공개 API 및 구조체 선언
  include/linux/mmzone.h      ← pg_data_t.memtier 필드 (1516줄)
  include/linux/node.h        ← access_coordinate 구조체 (29줄)
메모리 티어링 자료구조 관계도
메모리 티어링 호출 흐름

빠른 점검 명령

# 메모리 티어 sysfs 디렉토리 확인
ls /sys/devices/system/memory_tier/

# 각 티어의 노드 목록 확인
for f in /sys/devices/system/memory_tier/memory_tier*/nodelist; do printf '%s: ' "$f"; cat "$f"; done

# NUMA 노드 거리 테이블 확인
numactl -H

# NUMA demotion 활성화 상태 확인
cat /sys/kernel/mm/numa/demotion_enabled

# NUMA demotion 활성화
echo y > /sys/kernel/mm/numa/demotion_enabled

# 각 노드의 메모리 티어 정보 확인
cat /sys/devices/system/node/node*/meminfo | grep -i tier

# NUMA balancing 상태 확인
cat /proc/sys/kernel/numa_balancing

# 메모리 노드 거리 확인 (하드웨어 기준)
cat /sys/devices/system/node/node*/distance

# 메모리 핫플러그 관련 메시지 확인
dmesg | grep -i "memory.*tier\|demotion\|adistance"

# 메모리 노드 상태 확인
cat /sys/devices/system/node/node*/meminfo | head -20

# 추상 거리(adistance) 알고리즘 등록 여부 확인
dmesg | grep -i "adistance\|abstract"

핵심 자료구조

struct memory_tier

메모리 티어를 나타내는 핵심 구조체입니다. 하나의 티어는 동일한 추상 거리 범위를 가진 여러 메모리 타입을 포함합니다.

// mm/memory-tiers.c:13-27
struct memory_tier {
    struct list_head list;          /* 모든 티어를 연결하는 리스트 */
    struct list_head memory_types;  /* 이 티어에 속한 메모리 타입 목록 */
    int adistance_start;            /* 추상 거리 시작값 (MEMTIER_CHUNK_SIZE 단위) */
    struct device dev;              /* sysfs 디바이스 객체 */
    nodemask_t lower_tier_mask;     /* 하위 티어에 속한 모든 노드 마스크 */
};

struct demotion_nodes

각 NUMA 노드의 마이그레이션(demotion) 대상 노드 정보를 저장합니다.

// mm/memory-tiers.c:29-31
struct demotion_nodes {
    nodemask_t preferred;  /* 선호하는 마이그레이션 대상 노드 마스크 */
};

struct memory_dev_type

메모리 디바이스 타입을 나타내며, 동일한 추상 거리를 가진 노드들의 그룹입니다.

// include/linux/memory-tiers.h:24-34
struct memory_dev_type {
    struct list_head tier_sibling;  /* 동일 티어 내 형제 타입 연결 */
    struct list_head list;          /* 드라이버 관리 타입 목록 */
    int adistance;                  /* 이 타입의 추상 거리 */
    nodemask_t nodes;               /* 동일 추상 거리를 가진 노드 마스크 */
    struct kref kref;               /* 참조 카운트 */
};

struct access_coordinate

메모리 노드의 성능 좌표(지연 시간, 대역폭)를 나타내는 구조체입니다.

// include/linux/node.h:29-34
struct access_coordinate {
    unsigned int read_bandwidth;   /* 읽기 대역폭 (MB/s) */
    unsigned int write_bandwidth;  /* 쓰기 대역폭 (MB/s) */
    unsigned int read_latency;     /* 읽기 지연 시간 (나노초) */
    unsigned int write_latency;    /* 쓰기 지연 시간 (나노초) */
};

struct node_memory_type_map

각 NUMA 노드의 메모리 타입 매핑 정보를 관리합니다.

// mm/memory-tiers.c:33-36
struct node_memory_type_map {
    struct memory_dev_type *memtype;  /* 노드의 현재 메모리 타입 */
    int map_count;                    /* 매핑된 디바이스 수 */
};

pg_data_t.memtier 필드

각 NUMA 노드의 pg_data_t에 메모리 티어 포인터가 포함되어 있습니다.

// include/linux/mmzone.h:1516
struct memory_tier __rcu *memtier;  /* RCU 보호된 메모리 티어 포인터 */

핵심 함수

1. memory_tier_late_init()

// mm/memory-tiers.c:707-736
static int __init memory_tier_late_init(void)

역할: late_initcall로 실행되어, 아직 티어가 할당되지 않은 모든 N_MEMORY 노드에 메모리 티어를 설정합니다.

분기 로직:

  • node_memory_types[nid].memtype가 이미 있으면 해당 노드 건너뜀 (디바이스 드라이버가 이미 초기화)
  • set_node_memory_tier() 실패 시 해당 노드 건너뜀
  • 마지막에 establish_demotion_targets() 호출하여 마이그레이션 경로 구축
  • 2. establish_demotion_targets()

    // mm/memory-tiers.c:425-518
    static void establish_demotion_targets(void)

    역할: 모든 메모리 노드에 대한 자동 마이그레이션 대상을 결정합니다. 이 함수가 티어링의 핵심 결정 로직입니다.

    분기 로직:

    1. disable_all_demotion_targets()로 기존 설정 초기화

    2. 각 노드에 대해 하위 티어에서 가장 가까운 노드를 find_next_best_node()로 탐색

    3. 동일한 거리를 가진 노드들을 모두 preferred 마스크에 추가

    4. CPU가 있는 티어를 top_tier로 설정 (이 티어에서의 promotion은 불허)

    5. lower_tier_mask를 구축하여 각 티어의 하위 노드 마스크 설정

    3. next_demotion_node()

    // mm/memory-tiers.c:330-374
    int next_demotion_node(int node, const nodemask_t *allowed_mask)

    역할: 주어진 노드에서 다음 마이그레이션 대상 노드를 반환합니다.

    분기 로직:

  • node_demotion이 없으면 NUMA_NO_NODE 반환
  • preferred 마스크와 allowed_mask의 교집합에서 랜덤 선택
  • 교집합이 비어 있으면 find_next_best_node()로 폴백
  • 4. set_node_memory_tier()

    // mm/memory-tiers.c:542-572
    static struct memory_tier *set_node_memory_tier(int node)

    역할: 특정 노드에 메모리 티어를 설정합니다.

    분기 로직:

  • mt_calc_adistance()로 추상 거리 계산
  • node_memory_types[node].memtype가 없으면 mt_find_alloc_memory_type()으로 새 타입 할당
  • 실패 시 default_dram_type으로 폴백
  • find_create_memory_tier()로 티어 생성 또는 기존 티어에 연결
  • 5. folio_use_access_time()

    // mm/memory-tiers.c:65-69
    bool folio_use_access_time(struct folio *folio)

    역할: 특정 폴리오가 _last_cpupid 필드를 페이지 접근 시간 기록에 사용하는지 확인합니다.

    분기 로직:

  • NUMA_BALANCING_MEMORY_TIERING 모드가 활성화되어 있고
  • 폴리오의 노드가 top-tier가 아닌 경우 true 반환
  • 6. mt_perf_to_adistance()

    // mm/memory-tiers.c:795-821
    int mt_perf_to_adistance(struct access_coordinate *perf, int *adist)
    {
        guard(mutex)(&default_dram_perf_lock);
        if (default_dram_perf_error)
            return -EIO;
    
        if (perf->read_latency + perf->write_latency == 0 ||
            perf->read_bandwidth + perf->write_bandwidth == 0)
            return -EINVAL;
    
        if (default_dram_perf_ref_nid == NUMA_NO_NODE)
            return -ENOENT;
    
        /* 기본 DRAM을 기준으로 추상 거리를 계산한다. */
        *adist = MEMTIER_ADISTANCE_DRAM *
            (perf->read_latency + perf->write_latency) /
            (default_dram_perf.read_latency + default_dram_perf.write_latency) *
            (default_dram_perf.read_bandwidth + default_dram_perf.write_bandwidth) /
            (perf->read_bandwidth + perf->write_bandwidth);
    
        return 0;
    }

    역할: access_coordinateadistance로 바꾸는 기본 변환 경로입니다. 드라이버가 제공한 읽기/쓰기 지연과 대역폭을 기준 DRAM과 비교해 티어 순서를 만듭니다.

    분기 로직:

  • 기준 DRAM 정보가 아직 없으면 -ENOENT 반환
  • 지연 시간 또는 대역폭 합이 0이면 -EINVAL 반환
  • 기준 DRAM과의 차이가 너무 크면 mt_set_default_dram_perf()에서 오류로 처리
  • 7. mt_calc_adistance()

    // mm/memory-tiers.c:866-878
    int mt_calc_adistance(int node, int *adist)
    {
        return blocking_notifier_call_chain(&mt_adistance_algorithms, node, adist);
    }

    역할: 등록된 notifier 체인에 추상 거리 계산을 넘깁니다. 기본 DRAM 기준 변환이 아니라 장치별 알고리즘이 있으면 그 결과를 우선 사용합니다.

    분기 로직:

  • 등록된 알고리즘이 NOTIFY_STOP을 반환하면 해당 결과 사용
  • 아무 알고리즘도 값을 주지 않으면 기존 adist를 유지

  • 호출 흐름

    메모리 티어링 초기화 흐름:
    
    subsys_initcall(memory_tier_init)
      ├─ subsys_virtual_register()          ← sysfs 서브시스템 등록
      ├─ kzalloc_objs(node_demotion)        ← 마이그레이션 경로 배열 할당
      └─ mt_find_alloc_memory_type()        ← 기본 DRAM 타입 생성
    
    late_initcall(memory_tier_late_init)
      ├─ for_each_node_state(N_MEMORY)
      │   ├─ set_node_memory_tier(nid)
      │   │   ├─ mt_calc_adistance()        ← 추상 거리 계산
      │   │   ├─ mt_find_alloc_memory_type()← 메모리 타입 할당
      │   │   ├─ __init_node_memory_type()  ← 노드-타입 매핑
      │   │   └─ find_create_memory_tier()  ← 티어 생성/연결
      │   └─ rcu_assign_pointer(pgdat->memtier) ← RCU 할당
      └─ establish_demotion_targets()       ← 마이그레이션 경로 구축
          ├─ disable_all_demotion_targets()
          ├─ for_each_node_state(N_MEMORY)
          │   └─ find_next_best_node()      ← 가장 가까운 하위 노드 탐색
          ├─ top_tier_adistance 설정        ← CPU 포함 티어 = top tier
          └─ lower_tier_mask 구축           ← 하위 티어 마스크 계산
    
    런타임 마이그레이션 흐름:
    
    vmscan.c (kswapd/direct reclaim)
      └─ next_demotion_node()               ← 마이그레이션 대상 노드 결정
          ├─ preferred 마스크에서 랜덤 선택
          └─ 폴백: find_next_best_node()
    
    migrate.c (페이지 마이그레이션)
      ├─ node_is_toptier()                  ← 소스 노드가 top-tier인지 확인
      └─ folio_use_access_time()            ← 접근 시간 기록 필요 여부 확인

    조건별 비교

    메모리 티어 유형 비교

    구분기본 DRAM 티어커스텀 티어 (HBM)PMEM 티어
    추상 거리MEMTIER_ADISTANCE_DRAM (576)< 576 (빠름)> 576 (느림)
    초기화 시점memory_tier_init()디바이스 드라이버디바이스 드라이버
    CPU 포함일반적으로 포함포함 가능포함 안 함
    Promotion불가 (top-tier)가능가능
    Demotion 대상없음기본 DRAM 노드하위 PMEM 노드

    마이그레이션 경로 결정 비교

    조건동작함수
    preferred 마스크가 allowed_mask와 교집합 존재교집합에서 랜덤 선택next_demotion_node()
    preferred 마스크가 allowed_mask와 교집합 없음find_next_best_node()로 폴백next_demotion_node()
    하위 티어 없음 (마지막 티어)NUMA_NO_NODE 반환next_demotion_node()
    node_demotion 미초기화NUMA_NO_NODE 반환next_demotion_node()

    추상 거리 계산 방식 비교

    소스공식비고
    기본 알고리즘MEMTIER_ADISTANCE_DRAM × (read_lat + write_lat) / base_lat × base_bw / (read_bw + write_bw)성능에 반비례
    커스텀 알고리즘register_mt_adistance_algorithm()으로 등록notifier 체인 사용
    기본 DRAM 미설정-ENOENT 반환mt_perf_to_adistance() 실패

    구성 요소 역할 비교

    요소역할핵심 필드/함수
    `memory_tier`sysfs로 노출되는 티어 객체`memory_types`, `lower_tier_mask`
    `memory_dev_type`같은 추상 거리를 공유하는 메모리 타입`adistance`, `nodes`, `kref`
    `node_demotion[]`런타임 demotion 대상 캐시`preferred`
    `default_dram_type`기준이 되는 DRAM 타입`MEMTIER_ADISTANCE_DRAM`
    `pg_data_t.memtier`각 노드가 연결된 티어 포인터`__rcu` 보호

    sysfs 인터페이스 비교

    경로내용권한
    /sys/devices/system/memory_tier/memory_tier*/nodelist티어에 속한 노드 목록읽기 전용
    /sys/kernel/mm/numa/demotion_enabled자동 마이그레이션 활성화읽기/쓰기

    관련 문서

  • 00-overview.html — 메모리 관리 개요
  • 13-numa.html — NUMA 아키텍처
  • 20-migrate.html — 페이지 마이그레이션
  • 22-memory_hotplug.html — 메모리 핫플러그
  • 05-page_reclaim.html — 페이지 회수