관련 소스:mm/numa.c,mm/numa_memblks.c,mm/numa_emulation.c,mm/memory-tiers.c
관련 헤더:include/linux/numa.h,include/linux/numa_memblks.h,include/linux/memory-tiers.h,include/linux/mmzone.h,include/linux/node.h
관련 문서: 메모리 관리 개요 · Buddy Allocator · Migration · 메모리 Hotplug · 메모리 티어링
NUMA(Non-Uniform Memory Access)는 멀티소켓 시스템에서 각 CPU 소켓이 로컬 메모리에 빠르게 접근하고 원격 메모리에는 느리게 접근하는 아키텍처입니다. 리눅스 커널은 물리 메모리를 Node → Zone → Page 3단계 계층으로 관리하며, 각 NUMA 노드는 독립적인 pg_data_t, Zone, kswapd를 가집니다.
일상 비유: NUMA는 대형 도서관 체인과 비슷합니다. 각 지점(Node)에 자체 서가(물리 메모리)가 있고, 지점 내 대출(로컬 접근, ~100ns)은 빠르지만 다른 지점으로 책을 보내는 것(원격 접근, ~200ns+)은 느립니다. 도서관 시스템(커널)은 자주 읽는 책을 인기 지점에 복사하여 성능을 최적화합니다.
Linux 7.0에서는 NUMA 초기화(numa_memblks_init)를 통해 ACPI SRAT/SLIT에서 파싱한 노드 거리 정보와 메모리 블록을 등록하고, Memory Tiering(memory-tiers.c)을 통해 DRAM, PMEM, HBM 같은 이질적인 메모리 계층을 추상 거리(abstract distance)로 통합 관리합니다. 또한 CONFIG_MIGRATION이 활성화되면 자동 계층 이동(demotion/promotion)이 가능해집니다. 커널은 추상 거리 계산 알고리즘을 notifier chain으로 확장 가능하게 설계하여, 드라이버별 맞춤 거리 계산을 지원합니다.
| 항목 | 값 | 설명 |
|---|---|---|
| 로컬 노드 접근 지연 | ~100ns | 같은 소켓의 DDR 메모리 직접 접근 |
| 원격 노드 접근 지연 | ~200ns+ | 인터커넥트(QPI/UPI) 경유 |
| 로컬 vs 원격 비율 | 2×~3× | 원격 접근이 2~3배 느림 |
| MEMTIER_CHUNK_SIZE | 128 | 티어당 추상 거리 범위 |
| MEMTIER_ADISTANCE_DRAM | 576 | 기본 DRAM 추상 거리 |
mm/numa.c — NODE_DATA 할당, 기본 stub 함수
mm/numa_memblks.c — NUMA 메모리 블록 관리, 거리 테이블, 초기화 코어
mm/numa_emulation.c — NUMA 에뮬레이션 (가상 NUMA 구성)
mm/memory-tiers.c — 메모리 티어링, 추상 거리, demotion/promotion, hotplug 콜백
include/linux/numa.h — NODE_DATA(), numa_valid_node(), NUMA_NO_MEMBLK
include/linux/numa_memblks.h — numa_memblk, numa_meminfo 구조체
include/linux/memory-tiers.h — memory_dev_type, adistance, tier API
include/linux/mmzone.h — pglist_data(pg_data_t) 정의
include/linux/node.h — access_coordinate 구조체 (메모리 성능 좌표)
# NUMA 노드 구성 확인 (노드 수, CPU, 메모리 크기)
numactl -H
# 각 노드 메모리 상세
cat /sys/devices/system/node/node0/meminfo
# NUMA 거리 매트릭스 확인 (ACPI SLIT 기반)
numactl --hardware | grep -A 20 "node distances"
# 노드별 Zone 정보
cat /proc/zoneinfo | grep -E 'Node|zone|spanned|present' | head -40
# 메모리 티어 디렉토리 확인
ls /sys/devices/system/memory_tier/
# NUMA 미스/hit 통계 — 원격 접근 비율 모니터링
grep -E 'numa_hit|numa_miss|numa_foreign' /proc/vmstat
# NUMA 자동 계층 이동(demotion) 활성화 상태
cat /sys/kernel/mm/numa/demotion_enabled
# NUMA 관련 커널 설정 확인
grep -E 'CONFIG_NUMA|CONFIG_NUMA_BALANCING|CONFIG_MIGRATION' /boot/config-$(uname -r)
# 현재 프로세스의 NUMA 메모리 분포
numastat -p $$
# NUMA 정책 확인 — 현재 프로세스의 메모리 정책
numactl --show
# NUMA 힌트 폴트 통계 (AutoNUMA 활성화 시)
grep -E 'numa_pte_update|numa_hint_faults|numa_hint_faults_local' /proc/vmstat
# 노드별 총 메모리 사용량 비교
grep -E 'MemTotal|MemFree|MemAvailable' /proc/meminfo
# 각 티어의 노드 목록 확인
for tier in /sys/devices/system/memory_tier/memory_tier*/; do
echo "=== $(basename $tier) ==="
cat ${tier}nodelist 2>/dev/null
done
# NUMA 거리 직접 조회 (libnuma)
numactl --hardware | grep -E 'node|distance'
# 메모리 핫플러그로 추가된 노드 확인
dmesg | grep -E 'NUMA|node|memory' | tail -20
# NUMA 에뮬레이션 확인 (부팅 파라미터)
cat /proc/cmdline | grep -o 'numa=fake=[^ ]*'
# 추상 거리 알림 체인 등록 확인 (커널 로그)
dmesg | grep -E 'adistance|memory tier|tier'
# NUMA 노드별 캐시 속성 확인 (HMEM_REPORTING 활성화 시)
ls /sys/devices/system/node/node*/cache/
# 기본 DRAM 성능 기준값 확인
dmesg | grep -E 'default.*DRAM|abstract distance'
하드웨어/펌웨어가 제공하는 하나의 물리 메모리 구간을 나타냅니다.
/* include/linux/numa_memblks.h:13-17 */
struct numa_memblk {
u64 start; /* 시작 물리 주소 */
u64 end; /* 끝 물리 주소 (exclusive) */
int nid; /* 이 블록이 속한 NUMA 노드 ID */
};
start, end: 물리 주소 범위. start == end이면 빈 블록으로 간주nid: NUMA_NO_NODE이면 유효하지 않은 블록시스템 전체의 NUMA 메모리 블록 배열을 관리합니다.
/* include/linux/numa_memblks.h:19-22 */
struct numa_meminfo {
int nr_blks; /* 현재 등록된 블록 수 */
struct numa_memblk blk[NR_NODE_MEMBLKS]; /* 메모리 블록 배열 */
};
NR_NODE_MEMBLKS는 MAX_NUMNODES 2로 정의 (기본 최대 256 2 = 512개)numa_memblks.c:17에서 전역 numa_meminfo와 numa_reserved_meminfo로 관리됨하드웨어 메모리 유형(DRAM, PMEM, HBM 등)의 추상 거리 정보를 담습니다.
/* include/linux/memory-tiers.h:24-34 */
struct memory_dev_type {
/* 같은 티어에 속하는 메모리 유형들의 리스트 */
struct list_head tier_sibling;
/* 한 드라이버가 관리하는 메모리 유형들의 리스트 */
struct list_head list;
/* 이 메모리 유형의 추상 거리 (abstract distance) */
int adistance;
/* 같은 추상 거리를 가진 노드들의 마스크 */
nodemask_t nodes;
struct kref kref; /* 참조 카운트 */
};
adistance: 작을수록 빠른 메모리. DRAM 기본값은 MEMTIER_ADISTANCE_DRAM (576)tier_sibling: memory_tier.memory_types 리스트에 연결됨nodes: 이 메모리 유형에 속한 NUMA 노드들의 비트마스크하나의 메모리 계층(DRAM tier, PMEM tier 등)을 나타내며, 여러 memory_dev_type이 하나의 tier에 속할 수 있습니다.
/* mm/memory-tiers.c:13-27 */
struct memory_tier {
/* 계층 목록 (top tier → bottom tier 순서) */
struct list_head list;
/* 이 계층에 속한 모든 메모리 유형 목록 */
struct list_head memory_types;
/*
* 추상 거리 범위의 시작값.
* 각 tier는 MEMTIER_CHUNK_SIZE(128) 크기의 거리 범위를 커버
*/
int adistance_start;
struct device dev; /* sysfs 디바이스 */
/* 이 계층 아래에 있는 모든 티어의 노드 마스크 */
nodemask_t lower_tier_mask;
};
list: 전역 memory_tiers 리스트에 연결. top → bottom 순서로 정렬lower_tier_mask: demotion 시 fallback 대상 노드 마스크각 NUMA 노드의 모든 상태를 담는 최상위 구조체입니다.
/* include/linux/mmzone.h:1381-1521 (핵심 필드 발췌) */
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; /* 이 노드의 존들 */
struct zonelist node_zonelists[MAX_ZONELISTS]; /* 전체 시스템 존 리스트 */
int nr_zones; /* 채워진 존 수 */
unsigned long node_start_pfn; /* 시작 PFN */
unsigned long node_present_pages; /* 물리 페이지 합계 */
unsigned long node_spanned_pages; /* hole 포함 전체 범위 */
int node_id; /* NUMA 노드 ID */
struct task_struct *kswapd; /* 페이지 회수 스레드 */
#ifdef CONFIG_NUMA
struct memory_tier __rcu *memtier; /* 이 노드의 메모리 티어 (RCU) */
#endif
/* ... 다른 필드들 ... */
} pg_data_t;
node_data[MAX_NUMNODES] 배열로 전역 관리 (numa.c:8)NODE_DATA(nid) 매크로로 접근 (numa.h:26)memtier: RCU로 보호되며, 메모리 티어링 시스템에서 노드의 계층 위치를 나타냄CONFIG_MIGRATION 활성화 시 각 노드의 demotion(하위 계층으로 이동) 대상 노드를 기록합니다.
/* mm/memory-tiers.c:29-31 */
struct demotion_nodes {
nodemask_t preferred; /* 선호하는 demotion 대상 노드 마스크 */
};
node_demotion[] 배열: 노드당 하나의 demotion_nodespreferred: 같은 거리의 여러 대상이 있을 때 랜덤 선택용메모리 디바이스의 읽기/쓰기 지연시간과 대역폭을 나타내는 구조체입니다.
/* 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; /* 읽기 지연 (ns) */
unsigned int write_latency; /* 쓰기 지연 (ns) */
};
mt_perf_to_adistance(): 이 좌표를 추상 거리(adistance)로 변환default_dram_perf)을 기준으로 비례 계산access_coordinate_class: ACCESS_COORDINATE_LOCAL(노드 간)과 ACCESS_COORDINATE_CPU(CPU-메모리 간) 두 가지 클래스node_set_perf_attrs(): NUMA 거리 매트릭스에 HW 메모리 성능 좌표 등록/* mm/memory-tiers.c:815-819 — 추상 거리 계산 공식 */
*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);
default_dram_perf_error로 알고리즘 비활성화각 NUMA 노드가 어떤 memory_dev_type에 속하는지 추적합니다.
/* mm/memory-tiers.c:33-36 */
struct node_memory_type_map {
struct memory_dev_type *memtype; /* 이 노드의 메모리 유형 */
int map_count; /* 같은 유형의 디바이스 수 */
};
node_memory_types[MAX_NUMNODES]: 전역 배열로 모든 노드 관리map_count: 같은 노드에 같은 유형 디바이스가 여러 개 연결될 때 참조 카운트/* mm/numa_memblks.c:445-484 */
int __init numa_memblks_init(int (*init_func)(void),
bool memblock_force_top_down)
역할: NUMA 메모리 관리의 전체 초기화를 수행하는 메인 진입점.
흐름:
1. 노드 마스크 초기화 (nodes_clear)
2. memblock을 NUMA_NO_NODE로 초기 리셋
3. init_func() 호출 — 아키텍처별 NUMA 정보 파싱 (예: ACPI SRAT)
4. memblock_set_bottom_up(false) — top-down 할당으로 전환
5. numa_cleanup_meminfo() — 메모리 블록 정리 및 병합
6. numa_emulation() — NUMA 에뮬레이션 (선택)
7. numa_register_meminfo() — 실제 등록 및 hotplug 보호
/* mm/numa_memblks.c:105-125 */
void __init numa_set_distance(int from, int to, int distance)
역할: 두 NUMA 노드 간의 거리를 거리 테이블에 기록합니다.
분기 로직:
numa_alloc_distance()로 생성 시도from/to가 범위 초과 → 경고 후 무시distance가 u8 범위 초과 → 경고 후 무시distance != LOCAL_DISTANCE(10) → 경고 후 무시/* mm/numa_memblks.c:237-329 */
int __init numa_cleanup_meminfo(struct numa_meminfo *mi)
역할: 메모리 블록을 정리하고, 겹치는 블록을 병합합니다.
흐름:
1. 트리밍 단계: 각 블록을 DRAM 범위로 자르고, 예약 영역은 numa_reserved_meminfo로 이동
2. 병합 단계: 같은 노드의 인접/겹치는 블록을 병합 (다른 노드와 겹치면 에러)
3. 초기화 단계: 미사용 슬롯을 NUMA_NO_NODE로 초기화
/* mm/memory-tiers.c:425-518 */
static void establish_demotion_targets(void)
역할: 모든 메모리 노드에 대해 자동 demotion(하위 계층 이동) 대상을 설정합니다.
흐름:
1. 기존 demotion 타겟 전부 비활성화
2. 각 메모리 노드에 대해:
- 현재 노드의 tier에서 아래 tier의 노드를 찾음
- find_next_best_node()로 가장 가까운 대상 노드 결정
- 같은 거리의 여러 대상은 모두 preferred 마스크에 추가
3. CPU가 있는 tier를 top tier로 설정 (top_tier_adistance)
4. 각 tier의 lower_tier_mask 구축 — 하위 티어 전체 노드 마스크
/* mm/memory-tiers.c:330-374 */
int next_demotion_node(int node, const nodemask_t *allowed_mask)
역할: 지정 노드에서 다음 demotion 단계로 이동할 대상 노드를 반환합니다.
분기 로직:
node_demotion 미초기화 → NUMA_NO_NODEpreferred에서 allowed_mask 내 노드 하나를 랜덤 선택find_next_best_node()로 대체 대상 탐색/* mm/numa_memblks.c:509-557 */
int __init numa_fill_memblks(u64 start, u64 end)
역할: 지정된 물리 주소 범위를 커버하도록 기존 메모리 블록을 확장하거나 공백을 채웁니다.
흐름:
1. start~end 범위와 겹치는 모든 numa_memblk을 포인터 리스트로 수집
2. 시작 주소순으로 정렬
3. 첫 번째/마지막 블록의 경계를 start/end로 확장
4. 인접 블록 사이의 공백을 뒤쪽 블록의 start를 앞당겨서 채움
/* mm/memory-tiers.c:881-904 */
static int __meminit memtier_hotplug_callback(struct notifier_block *self,
unsigned long action, void *_arg)
역할: 메모리 노드 추가/제거 시 메모리 티어 구조를 자동 갱신합니다.
분기 로직:
NODE_ADDED_FIRST_MEMORY: 새 노드에 set_node_memory_tier() 적용 후 establish_demotion_targets() 호출NODE_REMOVED_LAST_MEMORY: clear_node_memory_tier()로 노드 제거 후 demotion 대상 재설정부팅 시 (arch/x86/kernel/acpi/numa.c 또는 DT 기반)
│
├─ numa_memblks_init(acpi_numa_init)
│ ├─ 메모리 마스크 초기화
│ ├─ acpi_numa_init() ← ACPI SRAT 파싱
│ │ ├─ numa_set_distance(from, to, distance)
│ │ └─ numa_add_memblk(nid, start, end)
│ ├─ numa_cleanup_meminfo()
│ │ ├─ 블록 트리밍 (DRAM 범위 제한)
│ │ ├─ 겹치는 블록 병합
│ │ └─ 예약 영역 분리
│ └─ numa_register_meminfo()
│ ├─ memblock_set_node() ← memblock에 노드 매핑
│ ├─ numa_clear_kernel_node_hotplug()
│ └─ alloc_node_data(nid) ← pg_data_t 할당
│
late_initcall → memory_tier_late_init()
│
├─ memory_tier_init() [subsys_initcall]
│ ├─ subsys_virtual_register()
│ ├─ node_demotion[] 할당
│ └─ default_dram_type 생성
│
├─ memory_tier_late_init()
│ ├─ 미초기화 노드에 set_node_memory_tier()
│ │ ├─ mt_calc_adistance() ← 추상 거리 계산
│ │ └─ find_create_memory_tier() ← tier 생성/연결
│ └─ establish_demotion_targets()
│ ├─ disable_all_demotion_targets()
│ ├─ 각 노드별 preferred/demotion 대상 결정
│ └─ lower_tier_mask 구축
│
런타임 — 메모리 회수/demotion 시
│
├─ next_demotion_node(node, allowed_mask)
│ ├─ node_demotion[node].preferred에서 선택
│ └─ fallback: find_next_best_node()
│
├─ numa_fill_memblks(start, end) ← 메모리 블록 공백 채우기
│ ├─ 겹치는 블록 포인터 리스트 수집
│ ├─ 시작 주소순 정렬
│ └─ 공백 채우기 (인접 블록 start 조정)
│
└─ hotplug 콜백 (memtier_hotplug_callback)
├─ NODE_ADDED_FIRST_MEMORY → set_node_memory_tier()
└─ NODE_REMOVED_LAST_MEMORY → clear_node_memory_tier()
| 조건 | 초기화 함수 | 소스 위치 | 특징 |
|---|---|---|---|
| ACPI NUMA | `acpi_numa_init()` → `numa_memblks_init()` | `arch/x86/kernel/acpi/numa.c` | SRAT/SLIT 테이블에서 파싱. `numa_set_distance()` 호출 |
| Devicetree NUMA | `of_numa_init()` → `numa_memblks_init()` | `drivers/of/of_numa.c` | DT의 `/numa` 노드에서 파싱. ARM/RISC-V |
| NUMA 에뮬레이션 | `numa_emulation()` | `numa_memblks.c` | `numa=` 부팅 파라미터로 가상 NUMA 구성. 단일 소켓 시스템 테스트용 |
| ACPI 실패 시 fallback | 더미 NUMA 초기화 | `arch/x86/mm/numa.c` | 모든 메모리를 node 0에 배치 |
| 시나리오 | 노드 구성 | adistance | tier 구조 | demotion 대상 |
|---|---|---|---|---|
| 듀얼 소켓 DRAM | Node 0,1 (DRAM) + Node 2,3 (PMEM) | DRAM: 576, PMEM: 704+ | tier0(DRAM) → tier1(PMEM) | Node 0→2, Node 1→3 (로컬 PMEM 우선) |
| 메모리 전용 노드 | Node 0,1 (CPU+DRAM) + Node 2 (DRAM only) | 모두 576 | tier0(DRAM) | 없음 (같은 티어) |
| HBM + DRAM + PMEM | Node 0 (CPU+HBM) + Node 1 (DRAM) + Node 2 (PMEM) | HBM: 320, DRAM: 576, PMEM: 704+ | tier0(HBM) → tier1(DRAM) → tier2(PMEM) | Node 0→1→2 계단식 |
| 필드 | 타입 | 역할 | NUMA 영향 |
|---|---|---|---|
| `node_zones[]` | `struct zone` | 이 노드의 존 배열 | 각 노드마다 독립적 |
| `node_zonelists[]` | `struct zonelist` | 할당 시 존 탐색 순서 | 원격 노드 포함 |
| `node_start_pfn` | `unsigned long` | 시작 물리 프레임 번호 | 노드별 물리 메모리 범위 |
| `node_present_pages` | `unsigned long` | 실제 물리 페이지 수 | 노드별 크기 |
| `kswapd` | `task_struct *` | 페이지 회수 스레드 | 노드별 독립 실행 |
| `memtier` | `memory_tier __rcu *` | 메모리 티어 | 계층 이동 기반 |
커널은 NUMA 환경에서 특정 노드의 메모리를 할당하는 API를 제공합니다.
/* include/linux/gfp.h — NUMA-aware 할당 */
/* 특정 노드에서 메모리 할당 */
struct page *alloc_pages_node(int nid, gfp_t gfp, unsigned int order);
/* 특정 노드에서 커널 메모리 할당 (Slab 위에서 동작) */
void *kmalloc_node(size_t size, gfp_t gfp, int node);
/* NUMA 정책 설정 (유저 공간) */
set_mempolicy(MPOL_BIND, &nodemask, maxnode); /* 지정 노드에만 할당 */
set_mempolicy(MPOL_INTERLEAVE, &nodemask, maxnode); /* 노드 간 교차 할당 */
alloc_pages_node(): NUMA 노드를 지정하여 Buddy Allocator에서 직접 할당kmalloc_node(): Slab 캐시에서 NUMA 인식 할당, 내부적으로 __kmalloc_node() → slab_alloc_node() 호출numactl --membind=1 ./app 또는 set_mempolicy()로 NUMA 정책 지정 가능메모리 티어링 시스템은 각 메모리 유형에 추상 거리(adistance)를 부여하여 계층을 결정합니다.
adistance = MEMTIER_ADISTANCE_DRAM × (read_latency + write_latency) / base_latency × base_bandwidth / (read_bandwidth + write_bandwidth)
MEMTIER_ADISTANCE_DRAM: 기본 DRAM 추상 거리 (576)MEMTIER_CHUNK_SIZE: 티어당 거리 범위 (128)커널은 추상 거리 계산 알고리즘을 notifier chain으로 관리합니다.
/* mm/memory-tiers.c:847-851 */
int register_mt_adistance_algorithm(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&mt_adistance_algorithms, nb);
}
register_mt_adistance_algorithm()으로 알고리즘 등록mt_calc_adistance() 호출 시 체인 순서대로 알고리즘 실행NOTIFY_STOP으로 중단단일 소켓 시스템에서도 numa_emulation 기능을 통해 가상 NUMA 구성을 시뮬레이션할 수 있습니다.
# 2개의 가상 NUMA 노드 생성 (각 512MB)
numa=fake=2@512M
# 특정 메모리 블록을 특정 노드에 배치
numa=force
numa_memmap=0x10000000@0x10000000 # 256MB~512MB를 node 1에 배치
/* mm/numa_memblks.c:481 — numa_emulation() 호출 위치 */
numa_emulation(&numa_meminfo, numa_distance_cnt);
/* mm/numa_emulation.c:49-78 — emu_setup_memblk() 핵심 로직 */
static int __init emu_setup_memblk(struct numa_meminfo *ei,
struct numa_meminfo *pi,
int nid, int phys_blk, u64 size)
{
struct numa_memblk *eb = &ei->blk[ei->nr_blks];
struct numa_memblk *pb = &pi->blk[phys_blk];
eb->start = pb->start;
eb->end = pb->start + size;
eb->nid = nid;
if (emu_nid_to_phys[nid] == NUMA_NO_NODE)
emu_nid_to_phys[nid] = pb->nid;
pb->start += size;
if (pb->start >= pb->end)
numa_remove_memblk_from(phys_blk, pi);
/* ... */
}
numa_memblks_init() 마지막 단계에서 호출split_nodes_interleave(): 물리 메모리를 지정 수만큼 균등 분할split_nodes_size(): 특정 크기 단위로 분할emu_nid_to_phys[]: 가상 노드 ID → 물리 노드 ID 매핑 배열커널은 추상 거리 계산 알고리즘을 blocking notifier chain으로 관리하여 드라이버별 확장을 지원합니다.
/* mm/memory-tiers.c:875-878 — mt_calc_adistance() */
int mt_calc_adistance(int node, int *adist)
{
return blocking_notifier_call_chain(&mt_adistance_algorithms, node, adist);
}
알고리즘 등록 흐름:
1. 드라이버가 register_mt_adistance_algorithm(&nb)로 콜백 등록
2. set_node_memory_tier() 호출 시 mt_calc_adistance() 실행
3. 체인 순서대로 콜백 호출 — 결과 제공 시 NOTIFY_STOP 반환
4. 결과가 없으면 기본값(MEMTIER_ADISTANCE_DRAM) 사용
알고리즘 콜백 시그니처:
/* include/linux/memory-tiers.h — 콜백 프로토타입 */
int (*algorithm_notifier)(struct notifier_block *nb,
unsigned long nid, void *data);
/* data는 int *adist 포인터 — 여기에 결과 기록 */
메모리 노드가 런타임에 추가/제거될 때 메모리 티어 구조를 자동 갱신합니다.
/* mm/memory-tiers.c:881-904 — memtier_hotplug_callback() */
static int __meminit memtier_hotplug_callback(struct notifier_block *self,
unsigned long action, void *_arg)
{
struct node_notify *nn = _arg;
switch (action) {
case NODE_REMOVED_LAST_MEMORY:
mutex_lock(&memory_tier_lock);
if (clear_node_memory_tier(nn->nid))
establish_demotion_targets();
mutex_unlock(&memory_tier_lock);
break;
case NODE_ADDED_FIRST_MEMORY:
mutex_lock(&memory_tier_lock);
memtier = set_node_memory_tier(nn->nid);
if (!IS_ERR(memtier))
establish_demotion_targets();
mutex_unlock(&memory_tier_lock);
break;
}
return notifier_from_errno(0);
}
hotplug_node_notifier()로 등록 — MEMTIER_HOTPLUG_PRI 우선순위NODE_ADDED_FIRST_MEMORY: 새 노드에 set_node_memory_tier() 적용 → establish_demotion_targets() 호출NODE_REMOVED_LAST_MEMORY: clear_node_memory_tier()로 노드 제거 → demotion 대상 재설정clear_node_memory_tier()는 synchronize_rcu()로 RCU 보호 하에서 tier 해제NUMA 시스템에서 커널과 유저 프로그램은 메모리 할당 위치를 제어하는 정책을 사용합니다.
| 정책 | 동작 | 용도 |
|---|---|---|
| `MPOL_DEFAULT` (기본) | 실행 CPU의 로컬 노드에 할당 | 일반적인 워크로드 |
| `MPOL_BIND` | 지정된 노드에만 할당 | DB 서버, HPC — 특정 노드 고정 |
| `MPOL_INTERLEAVE` | 노드 간 라운드로빈 교차 할당 | 대용량 공유 메모리 — 균등 분산 |
| `MPOL_PREFERRED` | 선호 노드 우선, 불가 시 다른 노드 | 유연한 NUMA 선호도 |
| `MPOL_LOCAL` | 로컬 노드 강제 | 지연 최소화가 중요한 워크로드 |
MPOL_BIND 사용 시 numactl --membind=0,1 ./app으로 여러 노드 지정 가능MPOL_INTERLEAVE는 메모리 대역폭이 균등하게 필요한 애플리케이션(예: 대형 해시 테이블)에 유용MPOL_DEFAULT 프로세스의 페이지가 자동으로 올바른 노드로 이동커널 3.13부터 도입된 자동 NUMA 밸런싱은 접근 패턴 기반으로 페이지를 자동 마이그레이션합니다.
동작 원리:
1. 커널이 주기적으로 PTE를 PROT_NONE으로 설정
2. 프로세스가 해당 페이지에 접근하면 NUMA 힌트 폴트 발생
3. 커널이 어느 CPU가 접근했는지 추적
4. 원격 노드의 페이지를 로컬 노드로 자동 마이그레이션
5. 태스크 자체를 데이터가 있는 노드로 이동 (task_numa_placement())
활성화/비활성화:
# AutoNUMA 활성화 상태 확인
cat /proc/sys/kernel/numa_balancing
# 활성화 (값: 2 = 자동 밸런싱)
echo 2 > /proc/sys/kernel/numa_balancing
# NUMA 힌트 폴트 통계 확인
grep -E 'numa_pte_update|numa_hint_faults' /proc/vmstat
주의: DB처럼 명시적 NUMA binding을 사용하는 애플리케이션에서는 AutoNUMA를 비활성화하는 것이 바람직합니다. 명시적 정책과 자동 밸런싱이 충돌할 수 있습니다.
| 정책/기능 | 동작 | 할당 위치 | 마이그레이션 | 적합한 워크로드 |
|---|---|---|---|---|
| `MPOL_DEFAULT` | 로컬 노드 우선 | 실행 CPU의 로컬 노드 | AutoNUMA에 의해 자동 | 일반 앱 |
| `MPOL_BIND` | 지정 노드 전용 | 지정된 노드만 | 수동 `mbind()` | DB, HPC |
| `MPOL_INTERLEAVE` | 노드 간 교차 | 라운드로빈 | 없음 | 대형 공유 메모리 |
| `MPOL_PREFERRED` | 선호 노드 우선 | 선호 노드, 없으면 로컬 | 없음 | 유연한 선호도 |
| AutoNUMA | PTE PROT_NONE 트릭 | 접근 패턴 기반 자동 | 커널 자동 마이그레이션 | 범용 워크로드 |
| NUMA 비활성화 | `numa_balancing=0` | 정책/_scheduler 결정 | 없음 | 수동 NUMA 고정 앱 |
참고: minzkn.com의 메모리 관리 개요 페이지 내 NUMA 관련 섹션과 비교합니다.
| 항목 | minzkn.com | 우리 문서 | 상태 |
|---|---|---|---|
| NUMA 기본 개념 (로컬/원격) | ✅ "각 지점에 자체 서가" 비유 | ✅ 도서관 체인 비유 + 소스 코드 | ✅ 보강 |
| Node/Zone/Page 계층 | ✅ 3단계 계층 설명 | ✅ `pg_data_t` 소스 분석 | ✅ 보강 |
| NUMA 거리 매트릭스 | ✅ ACPI SLIT 언급 | ✅ `numa_set_distance()` 코드 | ✅ 보강 |
| 환경별 시나리오 | ✅ 베어메탈, 임베디드, NUMA, VM, 컨테이너 | ✅ 조건별 비교 표 | ✅ 보강 |
| 메모리 티어링 | ✅ DRAM/PMEM/HBM 계층 | ✅ `memory_dev_type`, `adistance` 코드 | ✅ 보강 |
| Memory Hotplug | ✅ 핫플러그 개념 | ✅ `memtier_hotplug_callback` 코드 | ✅ 보강 |
| NUMA 에뮬레이션 | ❌ 없음 | ✅ `numa_emulation.c` 분석 | ✅ 신규 |
| 추상 거리 notifier chain | ❌ 없음 | ✅ `register_mt_adistance_algorithm` | ✅ 신규 |
| access_coordinate 구조체 | ❌ 없음 | ✅ `include/linux/node.h` 분석 | ✅ 신규 |
| 환경 | 메모리맵 입력 | 커널 내부 변화 | 확인 방법 | |
|---|---|---|---|---|
| 베어메탈 x86_64 | ACPI SRAT/SLIT, E820/EFI | memblock → Node/Zone → zonelist | `numactl -H`, `/proc/iomem` | |
| 임베디드 ARM64 SoC | Device Tree `/memory` | DT 기반 NUMA 초기화 | `ls /sys/firmware/devicetree/base/numa` | |
| 대형 NUMA 서버 | ACPI SRAT/HMAT, CXL | Node별 pg_data_t, kswapd | `cat /sys/devices/system/node/node*/meminfo` | |
| KVM/QEMU VM | 가짜 E820/EFI, virtio | 게스트 물리 주소 → 호스트 2단계 변환 | `dmesg | grep virtio` |
| 컨테이너 | 호스트 커널 공유, memcg | 별도 커널 메모리맵 없음 | `cat /sys/fs/cgroup/memory.numa_stat` | |
| CXL/PMEM/DAX | ACPI CEDT/CFMWS | ZONE_NORMAL 또는 ZONE_MOVABLE | `cat /proc/iomem`, `lsmem` |
minzkn.com은 DDR5/HBM 하드웨어 구조와 NUMA 성능의 관계를 다룹니다:
| 하드웨어 | 특징 | NUMA 영향 |
|---|---|---|
| DDR5 서브채널 | DIMM 내부 32+32bit 분할 | 채널별 NUMA 거리 차이 |
| HBM TSV 적층 | 1024비트 와이드 인터페이스 | 극도로 빠른 로컬 접근 (Tier 0) |
| On-Die ECC | 칩 내부 오류 보호 | 시스템 ECC와 별개 — 성능 영향 최소 |