Ryotta's Linux 7.0 MM

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

🌐 NUMA (Non-Uniform Memory Access)

관련 소스: 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 · 메모리 티어링

개요 (Overview)

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_SIZE128티어당 추상 거리 범위
MEMTIER_ADISTANCE_DRAM576기본 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 초기화 전체 흐름

메모리 티어 계층 구조

메모리 티어 계층 구조 & Demotion/Promotion 흐름

빠른 점검 명령

# 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'

핵심 자료구조

1. numa_memblk — 단일 메모리 블록

하드웨어/펌웨어가 제공하는 하나의 물리 메모리 구간을 나타냅니다.

/* 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이면 유효하지 않은 블록
  • 2. numa_meminfo — 전체 NUMA 메모리 블록 컬렉션

    시스템 전체의 NUMA 메모리 블록 배열을 관리합니다.

    /* include/linux/numa_memblks.h:19-22 */
    struct numa_meminfo {
    	int			nr_blks;                /* 현재 등록된 블록 수 */
    	struct numa_memblk	blk[NR_NODE_MEMBLKS]; /* 메모리 블록 배열 */
    };
  • NR_NODE_MEMBLKSMAX_NUMNODES 2로 정의 (기본 최대 256 2 = 512개)
  • numa_memblks.c:17에서 전역 numa_meminfonuma_reserved_meminfo로 관리됨
  • 3. memory_dev_type — 메모리 유형

    하드웨어 메모리 유형(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 노드들의 비트마스크
  • 4. memory_tier — 메모리 계층

    하나의 메모리 계층(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 대상 노드 마스크
  • 5. pglist_data (pg_data_t) — NUMA 노드 데이터

    각 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로 보호되며, 메모리 티어링 시스템에서 노드의 계층 위치를 나타냄
  • 6. demotion_nodes — 계층 이동 대상

    CONFIG_MIGRATION 활성화 시 각 노드의 demotion(하위 계층으로 이동) 대상 노드를 기록합니다.

    /* mm/memory-tiers.c:29-31 */
    struct demotion_nodes {
    	nodemask_t preferred;  /* 선호하는 demotion 대상 노드 마스크 */
    };
  • node_demotion[] 배열: 노드당 하나의 demotion_nodes
  • preferred: 같은 거리의 여러 대상이 있을 때 랜덤 선택용
  • 7. 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;    /* 읽기 지연 (ns) */
    	unsigned int write_latency;   /* 쓰기 지연 (ns) */
    };
  • mt_perf_to_adistance(): 이 좌표를 추상 거리(adistance)로 변환
  • 기본 DRAM 성능(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);
  • 지연시간에 비례하고 대역폭에 반비례하여 추상 거리 계산
  • 기본 DRAM 대비 10% 이상 성능 차이가 나면 default_dram_perf_error로 알고리즘 비활성화
  • 8. node_memory_type_map — 노드별 메모리 유형 매핑

    각 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: 같은 노드에 같은 유형 디바이스가 여러 개 연결될 때 참조 카운트
  • 핵심 함수

    1. numa_memblks_init() — NUMA 초기화 코어

    /* 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. memblockNUMA_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 보호

    2. numa_set_distance() — 노드 간 거리 설정

    /* 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) → 경고 후 무시
  • 3. numa_cleanup_meminfo() — 메모리 블록 정리

    /* 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로 초기화

    4. establish_demotion_targets() — 계층 이동 대상 결정

    /* 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 구축 — 하위 티어 전체 노드 마스크

    5. next_demotion_node() — 다음 demotion 노드 조회

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

    역할: 지정 노드에서 다음 demotion 단계로 이동할 대상 노드를 반환합니다.

    분기 로직:

  • node_demotion 미초기화 → NUMA_NO_NODE
  • preferred에서 allowed_mask 내 노드 하나를 랜덤 선택
  • preferred가 없으면 find_next_best_node()로 대체 대상 탐색
  • 6. numa_fill_memblks() — 메모리 블록 공백 채우기

    /* 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를 앞당겨서 채움

    7. memtier_hotplug_callback() — 핫플러그 콜백

    /* 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()

    조건별 비교

    NUMA 초기화 경로 비교

    조건초기화 함수소스 위치특징
    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에 배치

    메모리 티어링 시나리오 비교

    시나리오노드 구성adistancetier 구조demotion 대상
    듀얼 소켓 DRAMNode 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)모두 576tier0(DRAM)없음 (같은 티어)
    HBM + DRAM + PMEMNode 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 계단식

    pg_data_t 주요 필드 비교 (NUMA 관련)

    필드타입역할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

    커널은 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 정책 지정 가능
  • 추상 거리 계산 (Abstract Distance)

    메모리 티어링 시스템은 각 메모리 유형에 추상 거리(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)
  • 작을수록 빠른 메모리 (HBM < DRAM < PMEM)
  • 추상 거리 등록 체인

    커널은 추상 거리 계산 알고리즘을 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 에뮬레이션

    단일 소켓 시스템에서도 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 매핑 배열
  • 테스트/디버깅용 — 프로덕션에서는 ACPI SRAT 사용
  • 추상 거리 notifier chain 상세

    커널은 추상 거리 계산 알고리즘을 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 포인터 — 여기에 결과 기록 */

    메모리 티어 Hotplug 콜백

    메모리 노드가 런타임에 추가/제거될 때 메모리 티어 구조를 자동 갱신합니다.

    /* 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 해제
  • 메모리 정책 (Memory Policy)

    NUMA 시스템에서 커널과 유저 프로그램은 메모리 할당 위치를 제어하는 정책을 사용합니다.

    정책동작용도
    `MPOL_DEFAULT` (기본)실행 CPU의 로컬 노드에 할당일반적인 워크로드
    `MPOL_BIND`지정된 노드에만 할당DB 서버, HPC — 특정 노드 고정
    `MPOL_INTERLEAVE`노드 간 라운드로빈 교차 할당대용량 공유 메모리 — 균등 분산
    `MPOL_PREFERRED`선호 노드 우선, 불가 시 다른 노드유연한 NUMA 선호도
    `MPOL_LOCAL`로컬 노드 강제지연 최소화가 중요한 워크로드
  • MPOL_BIND 사용 시 numactl --membind=0,1 ./app으로 여러 노드 지정 가능
  • MPOL_INTERLEAVE는 메모리 대역폭이 균등하게 필요한 애플리케이션(예: 대형 해시 테이블)에 유용
  • AutoNUMA가 활성화되면 MPOL_DEFAULT 프로세스의 페이지가 자동으로 올바른 노드로 이동
  • AutoNUMA (NUMA Balancing)

    커널 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를 비활성화하는 것이 바람직합니다. 명시적 정책과 자동 밸런싱이 충돌할 수 있습니다.

    NUMA 메모리 정책 / AutoNUMA 비교

    정책/기능동작할당 위치마이그레이션적합한 워크로드
    `MPOL_DEFAULT`로컬 노드 우선실행 CPU의 로컬 노드AutoNUMA에 의해 자동일반 앱
    `MPOL_BIND`지정 노드 전용지정된 노드만수동 `mbind()`DB, HPC
    `MPOL_INTERLEAVE`노드 간 교차라운드로빈없음대형 공유 메모리
    `MPOL_PREFERRED`선호 노드 우선선호 노드, 없으면 로컬없음유연한 선호도
    AutoNUMAPTE PROT_NONE 트릭접근 패턴 기반 자동커널 자동 마이그레이션범용 워크로드
    NUMA 비활성화`numa_balancing=0`정책/_scheduler 결정없음수동 NUMA 고정 앱

    minzkn.com 비교

    참고: 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` 분석✅ 신규

    환경별 NUMA 시나리오 비교 (minzkn.com 기반 보강)

    환경메모리맵 입력커널 내부 변화확인 방법
    베어메탈 x86_64ACPI SRAT/SLIT, E820/EFImemblock → Node/Zone → zonelist`numactl -H`, `/proc/iomem`
    임베디드 ARM64 SoCDevice Tree `/memory`DT 기반 NUMA 초기화`ls /sys/firmware/devicetree/base/numa`
    대형 NUMA 서버ACPI SRAT/HMAT, CXLNode별 pg_data_t, kswapd`cat /sys/devices/system/node/node*/meminfo`
    KVM/QEMU VM가짜 E820/EFI, virtio게스트 물리 주소 → 호스트 2단계 변환`dmesggrep virtio`
    컨테이너호스트 커널 공유, memcg별도 커널 메모리맵 없음`cat /sys/fs/cgroup/memory.numa_stat`
    CXL/PMEM/DAXACPI CEDT/CFMWSZONE_NORMAL 또는 ZONE_MOVABLE`cat /proc/iomem`, `lsmem`

    추가 참고: DDR5/HBM 하드웨어 영향

    minzkn.com은 DDR5/HBM 하드웨어 구조와 NUMA 성능의 관계를 다룹니다:

    하드웨어특징NUMA 영향
    DDR5 서브채널DIMM 내부 32+32bit 분할채널별 NUMA 거리 차이
    HBM TSV 적층1024비트 와이드 인터페이스극도로 빠른 로컬 접근 (Tier 0)
    On-Die ECC칩 내부 오류 보호시스템 ECC와 별개 — 성능 영향 최소

    관련 문서

  • 메모리 관리 개요 — 전체 메모리 관리 조감도
  • Buddy Allocator — NUMA 인식 물리 페이지 할당
  • Migration — 페이지 마이그레이션과 demotion의 실제 실행체
  • 메모리 Hotplug — 런타임 메모리 추가/제거
  • 메모리 티어링 — 메모리 계층 구조의 심화 분석