메모리 티어링은 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"
메모리 티어를 나타내는 핵심 구조체입니다. 하나의 티어는 동일한 추상 거리 범위를 가진 여러 메모리 타입을 포함합니다.
// 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; /* 하위 티어에 속한 모든 노드 마스크 */
};
각 NUMA 노드의 마이그레이션(demotion) 대상 노드 정보를 저장합니다.
// mm/memory-tiers.c:29-31
struct demotion_nodes {
nodemask_t preferred; /* 선호하는 마이그레이션 대상 노드 마스크 */
};
메모리 디바이스 타입을 나타내며, 동일한 추상 거리를 가진 노드들의 그룹입니다.
// 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; /* 참조 카운트 */
};
메모리 노드의 성능 좌표(지연 시간, 대역폭)를 나타내는 구조체입니다.
// 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; /* 쓰기 지연 시간 (나노초) */
};
각 NUMA 노드의 메모리 타입 매핑 정보를 관리합니다.
// mm/memory-tiers.c:33-36
struct node_memory_type_map {
struct memory_dev_type *memtype; /* 노드의 현재 메모리 타입 */
int map_count; /* 매핑된 디바이스 수 */
};
각 NUMA 노드의 pg_data_t에 메모리 티어 포인터가 포함되어 있습니다.
// include/linux/mmzone.h:1516
struct memory_tier __rcu *memtier; /* RCU 보호된 메모리 티어 포인터 */
// 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() 호출하여 마이그레이션 경로 구축// 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를 구축하여 각 티어의 하위 노드 마스크 설정
// 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()로 폴백// 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()로 티어 생성 또는 기존 티어에 연결// mm/memory-tiers.c:65-69
bool folio_use_access_time(struct folio *folio)
역할: 특정 폴리오가 _last_cpupid 필드를 페이지 접근 시간 기록에 사용하는지 확인합니다.
분기 로직:
NUMA_BALANCING_MEMORY_TIERING 모드가 활성화되어 있고true 반환// 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_coordinate를 adistance로 바꾸는 기본 변환 경로입니다. 드라이버가 제공한 읽기/쓰기 지연과 대역폭을 기준 DRAM과 비교해 티어 순서를 만듭니다.
분기 로직:
-ENOENT 반환-EINVAL 반환mt_set_default_dram_perf()에서 오류로 처리// 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` 보호 |
| 경로 | 내용 | 권한 |
|---|---|---|
| /sys/devices/system/memory_tier/memory_tier*/nodelist | 티어에 속한 노드 목록 | 읽기 전용 |
| /sys/kernel/mm/numa/demotion_enabled | 자동 마이그레이션 활성화 | 읽기/쓰기 |