CXL (Compute Express Link) 메모리
CXL은 PCIe 물리 계층 위에 구축된 캐시 코히런트 인터커넥트로, CPU와 가속기/메모리 확장 장치 간 고속 연결을 제공합니다.
CXL 3.0/3.1 프로토콜(io/cache/mem) 상세, Type 1/2/3 장치 아키텍처, HDM 디코더, 커널 드라이버 계층(drivers/cxl/),
리전 생성, 메모리 티어링, NUMA 노드 통합, CXL 보안/PMU, 포이즌 관리, QEMU 에뮬레이션까지
리눅스 커널 CXL 서브시스템의 모든 것을 다룹니다.
핵심 요약
- 3개 서브프로토콜 — CXL.io(PCIe 호환), CXL.cache(디바이스에서 CPU 캐시), CXL.mem(CPU에서 디바이스 메모리)
- 3가지 장치 유형 — Type1(캐시만), Type2(캐시+메모리, GPU), Type3(메모리만, 확장기)
- NUMA 노드로 노출 — CXL 메모리는 별도 NUMA 노드로 추가되어 numactl로 제어
- 캐시 코히런시 — CPU 캐시와 CXL 장치 캐시 간 자동 일관성 유지
- memory_tier 프레임워크 — DDR > CXL > PMEM 계층에서 자동 demotion/promotion
- HDM 디코더 — Host-managed Device Memory 디코더가 HPA를 DPA로 매핑
- CXL 3.0/3.1 — Back-Invalidate, 멀티헤드, 패브릭 스위칭, GFD(Global Fabric Attached Memory) 지원
- cxl-cli + ndctl — 사용자 공간 CXL 관리 도구
단계별 이해
- CXL 프로토콜 3종 차이 파악
CXL.io/cache/mem이 각각 어떤 역할인지 이해합니다. - Type1/2/3 장치 용도 구분
AI 가속기(Type2)와 메모리 확장기(Type3)의 차이를 파악합니다. - HDM 디코더 구조 학습
HPA(Host Physical Address)에서 DPA(Device Physical Address)로의 매핑 과정을 이해합니다. - 커널 drivers/cxl/ 구조 탐색
cxl_core, cxl_port, cxl_mem 모듈의 역할을 이해합니다. - 리전 생성과 NUMA 노드 확인 실습
CXL 장치가 lscpu/numactl에 어떻게 나타나는지 확인합니다. - memory_tier 정책 설정
자동 demotion/promotion 정책을 조정하는 방법을 익힙니다.
CXL 개요
CXL(Compute Express Link)은 인텔이 주도하고 AMD, ARM, Nvidia, 삼성, SK하이닉스 등이 참여하는 캐시 코히런트 인터커넥트 표준입니다. PCIe 물리 계층 위에 구축되어, 기존 PCIe 장치와 동일한 슬롯을 사용하면서 CPU 캐시 계층과 통합된 메모리 액세스를 제공합니다.
| 특성 | PCIe (일반) | CXL |
|---|---|---|
| 캐시 코히런시 | 없음 (소프트웨어 관리) | 하드웨어 자동 보장 |
| 메모리 공유 | DMA 전용 | Load/Store 직접 접근 |
| 지연시간 | 수십 us (PCIe TLP) | 200~400ns (DDR 유사) |
| NUMA 통합 | 불가 | NUMA 노드로 노출 |
| FLIT 모드 | 68B FLIT (PCIe 6.0) | 256B FLIT (CXL 3.0) |
| 코히런시 프로토콜 | 없음 | Snoop 기반 MESI 확장 |
| 물리 인터페이스 | PCIe 슬롯 | 동일 PCIe 슬롯 사용 |
| 최대 대역폭 | 256 GB/s (PCIe 6.0 x16) | 동일 (물리 계층 공유) |
CXL 버전별 비교
| 항목 | CXL 1.1 | CXL 2.0 | CXL 3.0 | CXL 3.1 |
|---|---|---|---|---|
| PCIe 기반 | PCIe 5.0 | PCIe 5.0 | PCIe 6.0 | PCIe 6.0 |
| FLIT 크기 | 68B | 68B | 256B | 256B |
| 대역폭 (x16) | ~128 GB/s | ~128 GB/s | ~256 GB/s | ~256 GB/s |
| 스위칭 | 지원 안함 | CXL 스위치 | 패브릭 스위치 | 패브릭+포트 라우팅 |
| 풀링 | 지원 안함 | 단일 호스트 풀링 | 다중 호스트 공유 | 향상된 GFD |
| Back-Invalidate | 없음 | 없음 | 지원 (BI) | 지원 (BI+) |
| 보안 | IDE (기본) | IDE | IDE + CMA | IDE + CMA + TSP |
| 커널 지원 시작 | Linux 5.14 | Linux 6.0 | Linux 6.6+ | 개발 중 |
CXL 서브프로토콜
CXL은 3개의 서브프로토콜로 구성되며, 장치 유형에 따라 일부 또는 전체를 구현합니다. 이 3개 프로토콜은 동일한 PCIe 물리 계층 위에서 FLIT(Flow Control Unit) 기반으로 다중화됩니다.
CXL.io 프로토콜 상세
CXL.io는 PCIe TLP(Transaction Layer Packet)와 동일한 프로토콜입니다. CXL 장치의 설정 공간(Configuration Space), MSI/MSI-X 인터럽트, DMA 전송, 그리고 CXL 메일박스(Mailbox) 통신에 사용됩니다. 모든 CXL 장치(Type1/2/3)는 CXL.io를 반드시 구현해야 합니다.
# CXL.io를 통한 장치 설정 공간 읽기
lspci -vvv -d ::0502 # CXL Type3 장치 (Class 0502)
# Capabilities: [xx] CXL cap (DVSEC)
# CXLCtl: Cache- IO+ Mem+ ... ← 프로토콜 활성 상태
# CXLSta: Viral- ← 바이러스 전파 상태
# DVSEC (Designated Vendor Specific Extended Capability) 확인
lspci -vvv | grep -A 20 "CXL"
# DVSEC ID 0: CXL Device Register Locator
# DVSEC ID 1: CXL Function DVSEC
# DVSEC ID 2: CXL Port DVSEC
# DVSEC ID 7: CXL Range Lock
# DVSEC ID 8: CXL GPF for Port
CXL.cache 프로토콜 상세
CXL.cache는 장치가 호스트 CPU의 메모리를 캐시할 때 사용하는 프로토콜입니다. Device-to-Host(D2H) 방향으로 Request/Response 채널이, Host-to-Device(H2D) 방향으로 Response/Data/Snoop 채널이 운용됩니다. MESI 프로토콜의 확장으로, 장치 캐시의 일관성을 하드웨어적으로 보장합니다.
| 채널 | 방향 | 메시지 유형 | 설명 |
|---|---|---|---|
| D2H Req | Device → Host | RdCurr, RdOwn, RdShared, ItoMWr, WrInv | 장치가 호스트 메모리 읽기/쓰기 요청 |
| D2H Rsp | Device → Host | WritePull, GO_WritePull | 장치가 스누프에 대한 응답 |
| D2H Data | Device → Host | 캐시 라인 데이터 | 스누프 결과 데이터 전달 |
| H2D Req (Snoop) | Host → Device | SnpData, SnpInv, SnpCur | 호스트가 장치 캐시 스누핑 |
| H2D Rsp | Host → Device | GO-I, GO-S, GO-E, GO-M, WritePull | 호스트 요청 완료 응답 |
| H2D Data | Host → Device | 캐시 라인 데이터 | 요청된 데이터 전달 |
CXL.mem 프로토콜 상세
CXL.mem은 호스트 CPU가 CXL 장치의 메모리에 직접 Load/Store로 접근할 때 사용합니다. Master-to-Subordinate(M2S) 방향으로 Request/Read-Write-Data 채널이, Subordinate-to-Master(S2M) 방향으로 NDR(No Data Response)/DRS(Data Response) 채널이 운용됩니다.
| 채널 | 방향 | 메시지 유형 | 설명 |
|---|---|---|---|
| M2S Req | Host → Device | MemRd, MemWr, MemRdData | 호스트 메모리 읽기/쓰기 요청 |
| M2S RwD | Host → Device | MemRd, MemWr + Data | 쓰기 데이터 포함 요청 |
| S2M NDR | Device → Host | Cmp, Cmp-S, Cmp-E | 완료 응답 (데이터 없음) |
| S2M DRS | Device → Host | MemData | 읽기 데이터 응답 |
/* CXL 3.0 FLIT 구조 (256바이트) */
/*
* 256B FLIT 레이아웃:
* ┌────────────────────────────────────────┐
* │ Header (2B) : 프로토콜 ID + 슬롯 맵 │
* │ Slot 0 (16B) : CXL.io / cache / mem │
* │ Slot 1 (16B) : CXL.io / cache / mem │
* │ Slot 2 (16B) : CXL.io / cache / mem │
* │ Slot 3 (16B) : CXL.io / cache / mem │
* │ Data (186B) : 캐시 라인 데이터 │
* │ CRC (4B) : FEC 보호 │
* └────────────────────────────────────────┘
* 하나의 FLIT에 여러 프로토콜 메시지를 동시 전송
*/
Type1 / Type2 / Type3 장치 비교
| 특성 | Type 1 | Type 2 | Type 3 |
|---|---|---|---|
| CXL.io | 지원 | 지원 | 지원 |
| CXL.cache | 지원 | 지원 | 지원 안함 |
| CXL.mem | 지원 안함 | 지원 (HDM-DB) | 지원 (HDM-H) |
| HDM 유형 | 없음 | HDM-DB (Device-Bias) | HDM-H (Host-Only) |
| 호스트 캐싱 | 가능 | 가능 | 가능 |
| 디바이스 캐싱 | 가능 | 가능 | 불가 |
| 대표 장치 | SmartNIC, FPGA | GPU, AI 가속기 | 메모리 확장기 |
| NUMA 노드 | 없음 | 있음 (VRAM) | 있음 |
| 커널 드라이버 | cxl_pci | cxl_pci + HMM | cxl_mem + DAX |
memory_tier 프레임워크에 반영합니다.
결과적으로 커널 NUMA 밸런서가 CXL 메모리를 적절한 tier에 배치하고 자동으로 demotion/promotion을 수행합니다.
CDAT는 CXL.io 메일박스를 통해 DOE(Data Object Exchange) 프로토콜로 전달됩니다.
HDM 디코더와 주소 변환
HDM(Host-managed Device Memory) 디코더는 CXL 메모리의 핵심 하드웨어 컴포넌트입니다. CPU가 발행하는 HPA(Host Physical Address)를 장치의 DPA(Device Physical Address)로 변환하며, 멀티 장치 인터리브를 지원합니다. CXL 토폴로지의 각 레벨(루트, 스위치, 엔드포인트)에 디코더가 존재합니다.
/* drivers/cxl/core/hdm.c — HDM 디코더 구조 */
/* CXL 루트 디코더: CFMWS HPA 범위를 루트 포트에 매핑 */
struct cxl_root_decoder {
struct cxl_switch_decoder cxlsd;
struct cxl_dport *cxlsd.target[CXL_DECODER_MAX_INTERLEAVE];
resource_size_t res; /* CFMWS HPA 범위 */
};
/* CXL 스위치 디코더: 스위치 포트 라우팅 */
struct cxl_switch_decoder {
struct cxl_decoder cxld;
int nr_targets;
struct cxl_dport *target[];
};
/* CXL 엔드포인트 디코더: HPA → DPA 최종 변환 */
struct cxl_endpoint_decoder {
struct cxl_decoder cxld;
resource_size_t dpa_res; /* DPA 시작 주소 */
resource_size_t skip; /* 인터리브 스킵 바이트 */
enum cxl_decoder_mode mode; /* RAM 또는 PMEM */
enum cxl_decoder_state state; /* 커밋 상태 */
};
/* 공통 디코더 기본 구조 */
struct cxl_decoder {
struct device dev;
int id;
struct range hpa_range; /* Host Physical Address 범위 */
int interleave_ways; /* 인터리브 배수 (1,2,4,8,16) */
int interleave_granularity; /* 인터리브 단위 (256B~16KB) */
enum cxl_decoder_type target_type; /* accelerator 또는 expander */
unsigned long flags;
};
인터리브 매핑 방식
CXL 디코더는 인터리브(interleave)를 통해 여러 CXL 장치에 메모리 접근을 분산시킵니다. 인터리브 방식(ways)과 단위(granularity)에 따라 HPA의 특정 비트가 대상 장치를 결정합니다.
# 디코더 인터리브 설정 확인
cat /sys/bus/cxl/devices/decoder0.0/interleave_ways # 2 (2-way 인터리브)
cat /sys/bus/cxl/devices/decoder0.0/interleave_granularity # 256 (256바이트 단위)
# HPA → DPA 변환 예시 (2-way, 256B granularity)
# HPA 비트[8] 이 0이면 → mem0 (DPA = HPA[63:9] || HPA[7:0])
# HPA 비트[8] 이 1이면 → mem1 (DPA = HPA[63:9] || HPA[7:0])
# 인터리브 방식별 대역폭 예상
# 1-way: ~50 GB/s (단일 장치)
# 2-way: ~100 GB/s (2개 장치 병렬)
# 4-way: ~200 GB/s (4개 장치 병렬)
# 8-way: ~256 GB/s (PCIe 6.0 x16 최대)
커널 드라이버 구조 (drivers/cxl/)
Linux 5.14부터 drivers/cxl/에 CXL 드라이버가 추가되었습니다.
계층화된 모듈 구조로 CXL 포트 토폴로지, 메모리 장치, 영역(region)을 관리합니다.
드라이버는 cxl_bus라는 전용 버스를 사용하며, 각 레벨의 장치가 이 버스에 등록됩니다.
드라이버 모듈 계층
# CXL 드라이버 계층 (lsmod로 확인)
lsmod | grep cxl
# cxl_acpi — ACPI CEDT 테이블 파싱, CFMWS 등록
# cxl_port — CXL 포트 (스위치, 루트 포트) 관리
# cxl_core — CXL 코어 — 리전, 디코더, memdev, PMU
# cxl_mem — CXL.mem 메모리 장치 드라이버
# cxl_pci — PCIe 기반 CXL 장치 바인딩
# cxl_pmem — CXL PMEM (비휘발성 메모리) 지원
# CXL 장치 확인
ls /sys/bus/cxl/devices/
# mem0 — CXL 메모리 장치 (Type3)
# port1 — CXL 루트 포트
# decoder0.0 — 루트 디코더
# endpoint2 — CXL 엔드포인트
# region0 — CXL 리전 (활성화된 경우)
# root0 — CXL 루트 (ACPI)
# 모듈 의존성 확인
modinfo cxl_mem | grep depends
# depends: cxl_core,cxl_port
# CXL 장치 상세 정보
cat /sys/bus/cxl/devices/mem0/firmware_version
cat /sys/bus/cxl/devices/mem0/payload_max
cat /sys/bus/cxl/devices/mem0/serial
CXL 메일박스 프로토콜
CXL 메일박스(Mailbox)는 호스트와 CXL 장치 간의 명령/응답 통신 채널입니다. CXL.io를 통해 MMIO 레지스터에 접근하며, 장치 초기화, 건강 상태 조회, 포이즌 관리, 보안 명령 등을 처리합니다.
/* drivers/cxl/core/mbox.c — 메일박스 명령 전송 */
struct cxl_mbox_cmd {
u16 opcode; /* 명령 코드 */
u16 return_code; /* 응답 코드 */
size_t size_in; /* 입력 페이로드 크기 */
size_t size_out; /* 출력 페이로드 크기 */
void *payload_in; /* 입력 데이터 */
void *payload_out; /* 출력 데이터 */
};
/* 주요 메일박스 명령 (opcode) */
#define CXL_MBOX_OP_IDENTIFY 0x4000 /* 장치 식별 */
#define CXL_MBOX_OP_GET_HEALTH_INFO 0x4200 /* 건강 상태 */
#define CXL_MBOX_OP_GET_POISON 0x4300 /* 포이즌 목록 */
#define CXL_MBOX_OP_INJECT_POISON 0x4301 /* 포이즌 주입 */
#define CXL_MBOX_OP_CLEAR_POISON 0x4302 /* 포이즌 제거 */
#define CXL_MBOX_OP_GET_SCAN_MEDIA 0x4304 /* 미디어 스캔 */
#define CXL_MBOX_OP_SET_SHUTDOWN 0x4500 /* 정상 종료 */
#define CXL_MBOX_OP_GET_SEC_STATE 0x4600 /* 보안 상태 */
/* 메일박스 전송 함수 */
int cxl_internal_send_cmd(struct cxl_memdev_state *mds,
struct cxl_mbox_cmd *cmd)
{
/* 1. 메일박스 잠금 획득 */
mutex_lock(&mds->mbox_mutex);
/* 2. 페이로드 레지스터에 입력 데이터 쓰기 */
memcpy_toio(mds->payload_regs, cmd->payload_in, cmd->size_in);
/* 3. 명령 레지스터에 opcode + 크기 쓰기 */
writeq(FIELD_PREP(CXLDEV_MBOX_CMD_COMMAND_MASK, cmd->opcode) |
FIELD_PREP(CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK, cmd->size_in),
mds->regs.mbox + CXLDEV_MBOX_CMD_OFFSET);
/* 4. 도어벨 레지스터 쓰기 (전송 시작) */
writel(1, mds->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET);
/* 5. 완료 대기 (폴링 또는 인터럽트) */
cxl_pci_mbox_wait_for_doorbell(mds);
mutex_unlock(&mds->mbox_mutex);
return 0;
}
리전 생성과 관리
CXL 리전(Region)은 하나 이상의 CXL 메모리 장치를 묶어 연속된 HPA 범위로 노출하는 논리적 단위입니다. 리전이 생성되면 DAX(Direct Access) 장치로 등록되고, 이를 system-ram 모드로 전환하면 NUMA 노드의 메모리로 사용할 수 있습니다.
# 전체 리전 생성 워크플로
# 1단계: CXL 장치 발견 확인
cxl list -M
# [{"memdev":"mem0","ram_size":"256.00 GiB","serial":"0x1234..."}]
# 2단계: 디코더 확인
cxl list -D -d root
# [{"decoder":"decoder0.0","interleave_ways":1,...}]
# 3단계: 리전 생성 (1-way, 단일 장치)
cxl create-region -m mem0 -d decoder0.0 -w 1 -g 256 -s 256G
# 결과: region0 생성됨
# 4단계: DAX 장치 확인
daxctl list
# [{"chardev":"dax0.0","size":"256.00 GiB","mode":"devdax"}]
# 5단계: system-ram 모드로 전환 (NUMA 노드 추가)
daxctl reconfigure-device --mode=system-ram dax0.0
# [{"chardev":"dax0.0","size":"256.00 GiB","mode":"system-ram",
# "movable":true}]
# 6단계: NUMA 노드 확인
numactl --hardware
# available: 3 nodes (0,1,2)
# node 2: CXL Type3 메모리 (256 GB)
# node 2 cpus: (비어있음 — CPU 없는 메모리 전용 노드)
# node 2 size: 262144 MB
# 인터리브 리전 생성 예시 (2-way)
cxl create-region -m mem0 -m mem1 -d decoder0.0 -w 2 -g 256 -s 512G
/* drivers/cxl/core/region.c — CXL 리전 생성 핵심 */
struct cxl_region {
struct device dev;
int id;
enum cxl_decoder_mode mode; /* CXL_DECODER_RAM 또는 CXL_DECODER_PMEM */
enum cxl_decoder_type type;
struct cxl_region_params params;
struct cxl_pmem_region *cxlr_pmem;
unsigned long flags;
struct resource *res; /* HPA 리소스 */
};
struct cxl_region_params {
enum cxl_config_state state;
int interleave_ways;
int interleave_granularity;
resource_size_t res_size; /* 리전 크기 */
struct cxl_endpoint_decoder *targets[CXL_DECODER_MAX_INTERLEAVE];
int nr_targets; /* 타겟 수 */
resource_size_t cache_size;
uuid_t uuid;
};
/* 리전 커밋 — 디코더 프로그래밍 */
static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled,
int pos)
{
/* 1. 엔드포인트 디코더를 리전 타겟에 추가 */
cxlr->params.targets[pos] = cxled;
/* 2. 인터리브 설정 검증 */
if (cxled->cxld.interleave_ways != cxlr->params.interleave_ways)
return -EINVAL;
/* 3. 모든 타겟이 설정되면 디코더 커밋 */
if (++cxlr->params.nr_targets == cxlr->params.interleave_ways)
return cxl_region_decode_commit(cxlr);
return 0;
}
메모리 티어링 프레임워크
Linux 6.1+의 memory_tier 프레임워크는 HMAT/CDAT 기반 메모리 지연 정보를 이용하여
DDR, CXL, PMEM 계층 간 자동 demotion/promotion을 수행합니다.
CXL 메모리가 NUMA 노드로 등록되면, 해당 노드의 성능 특성에 따라 자동으로 적절한 티어에 배치됩니다.
memory_tier sysfs 인터페이스
# 메모리 티어 구조 확인
ls /sys/devices/virtual/memory_tiering/
# memory_tier0 — DDR5 (가장 빠름, adistance: 기본값)
# memory_tier1 — CXL Type3 (중간, adistance 기반)
# memory_tier2 — PMEM/Optane (느림)
# 각 티어의 NUMA 노드 확인
cat /sys/devices/virtual/memory_tiering/memory_tier0/nodelist # 0,1
cat /sys/devices/virtual/memory_tiering/memory_tier1/nodelist # 2
# 추상 거리(abstract distance) 확인 — 티어 결정 기준
# HMAT 지연 기반으로 계산됨
cat /sys/devices/system/node/node0/access0/initiators/read_latency # 80 (ns)
cat /sys/devices/system/node/node2/access0/initiators/read_latency # 300 (ns)
# NUMA 밸런싱 활성화 (자동 demotion/promotion)
echo 1 > /proc/sys/kernel/numa_balancing
# demotion 활성화
echo true > /sys/kernel/mm/numa/demotion_enabled
# perf로 메모리 지연 프로파일링
perf stat -e mem_load_retired.l3_miss \
-e mem_load_retired.local_dram \
-e mem_load_retired.remote_dram \
-p $(pidof my_app) -- sleep 5
Demotion / Promotion 커널 내부
/* mm/memory-tiers.c — 메모리 티어 관리 */
struct memory_tier {
struct list_head list; /* 전역 티어 리스트 */
struct device dev;
nodemask_t lower_tier_mask; /* 하위 티어 노드들 */
int adistance_start; /* 추상 거리 시작 */
};
/* 추상 거리(adistance)로 티어 결정 */
/* DRAM: 기본 MEMTIER_ADISTANCE_DRAM (512) */
/* CXL: HMAT 지연 기반 계산 (~680~800) */
/* PMEM: MEMTIER_ADISTANCE_PMEM (1024) */
/* mm/vmscan.c — 페이지 demotion 흐름 */
static bool can_demote(int nid, struct scan_control *sc)
{
if (!numa_demotion_enabled)
return false;
/* nid에서 더 낮은 tier 노드가 있는지 확인 */
return next_demotion_node(nid) != NUMA_NO_NODE;
}
/* demotion 경로: kswapd → shrink_folio_list → demote_folio_list */
static unsigned int demote_folio_list(
struct list_head *demote_folios,
struct pglist_data *pgdat)
{
int target_nid = next_demotion_node(pgdat->node_id);
/* migrate_pages()로 DDR → CXL 노드로 이동 */
return migrate_pages(demote_folios, alloc_demote_folio,
NULL, target_nid, MIGRATE_ASYNC, MR_DEMOTION, NULL);
}
/* promotion 경로: NUMA fault → do_numa_page → migrate_misplaced_folio */
/* CPU가 CXL 메모리의 페이지에 접근하면 NUMA fault 발생 */
/* 접근 빈도가 높은 페이지는 DDR로 promotion */
/proc/sys/kernel/numa_balancing_scan_delay_ms(기본 1000ms)와
/proc/sys/kernel/numa_balancing_scan_size_mb(기본 256MB)를 조정하여 promotion 빈도를 제어합니다.
AI 워크로드에서는 scan_delay를 500ms로 줄여 hot 페이지의 promotion 속도를 높이는 것이 효과적입니다.
NUMA 통합과 numactl 활용
CXL 메모리는 커널이 부팅 시 또는 핫플러그 시 별도 NUMA 노드로 인식합니다. 일반 NUMA API(numactl, libnuma, move_pages)로 CXL 노드에 메모리를 할당하거나 특정 프로세스를 바인딩할 수 있습니다. CXL NUMA 노드는 CPU가 없는 메모리 전용 노드(CPU-less node)로 생성됩니다.
# CXL NUMA 노드 확인
numactl --hardware
# available: 3 nodes (0,1,2)
# node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11
# node 0 size: 131072 MB
# node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23
# node 1 size: 131072 MB
# node 2 cpus: ← CPU 없음 (CXL 메모리 전용)
# node 2 size: 262144 MB
# node distances:
# node 0 1 2
# 0: 10 20 30 ← CXL 노드(2)까지 거리 30
# 1: 20 10 30
# 2: 30 30 10
# AI 추론을 CXL 메모리 노드에 바인딩 (모델 가중치를 CXL에)
numactl --membind=2 --cpunodebind=0 python llm_inference.py
# 인터리브 모드 (DDR + CXL 분산 할당)
numactl --interleave=0,2 ./my_application
# 선호 노드 설정 (DDR 우선, 부족 시 CXL 사용)
numactl --preferred=0 --membind=0,2 ./my_application
# 메모리 노드 간 마이그레이션
migratepages $(pidof llm_server) 0 2 # node0 → node2 이동
# NUMA 통계 모니터링
numastat -p $(pidof llm_server)
numastat -m # 시스템 전체 NUMA 메모리 사용 현황
# HMAT 정보 확인
cat /sys/devices/system/node/node2/access0/initiators/read_latency
cat /sys/devices/system/node/node2/access0/initiators/read_bandwidth
cat /sys/devices/system/node/node2/access0/initiators/write_latency
cat /sys/devices/system/node/node2/access0/initiators/write_bandwidth
/* libnuma를 이용한 CXL 노드 프로그래밍 */
#include <numa.h>
#include <numaif.h>
void allocate_on_cxl(size_t size) {
int cxl_node = 2; /* CXL NUMA 노드 */
/* 방법 1: numa_alloc_onnode */
void *ptr = numa_alloc_onnode(size, cxl_node);
if (!ptr)
perror("CXL 노드 할당 실패");
/* 방법 2: mbind (기존 매핑을 CXL로 이동) */
unsigned long nodemask = 1UL << cxl_node;
mbind(ptr, size, MPOL_BIND, &nodemask,
sizeof(nodemask) * 8, MPOL_MF_MOVE);
/* 방법 3: move_pages (특정 페이지 이동) */
int nodes[1] = { cxl_node };
void *pages[1] = { ptr };
int status[1];
move_pages(0, 1, pages, nodes, status, MPOL_MF_MOVE);
}
CXL 보안 (IDE, CMA, TSP)
CXL은 PCIe 링크 위에서 동작하므로 물리적 공격(프로빙, 스니핑)에 노출될 수 있습니다. 이를 방어하기 위해 IDE(Integrity and Data Encryption), CMA(CXL Memory Authentication), TSP(TEE Security Protocol) 등의 보안 메커니즘을 제공합니다.
| 보안 메커니즘 | CXL 버전 | 보호 대상 | 방식 |
|---|---|---|---|
| IDE (Integrity and Data Encryption) | CXL 2.0+ | 링크 데이터 | AES-256-GCM 암호화 + 무결성 태그 |
| CMA (CXL Memory Authentication) | CXL 3.0+ | 메모리 데이터 | ECC + MAC (Message Authentication Code) |
| TSP (TEE Security Protocol) | CXL 3.1+ | TEE 데이터 | 하드웨어 격리 + 암호화된 메모리 영역 |
| Secure Erase | CXL 2.0+ | 장치 데이터 | 메일박스 명령으로 안전 삭제 |
| Sanitize | CXL 2.0+ | 장치 데이터 | 암호화 키 파기 + 미디어 덮어쓰기 |
/* drivers/cxl/core/mbox.c — CXL 보안 명령 */
/* 보안 상태 조회 */
#define CXL_MBOX_OP_GET_SEC_STATE 0x4600
#define CXL_MBOX_OP_SET_PASSPHRASE 0x4601
#define CXL_MBOX_OP_DISABLE_PASSPHRASE 0x4602
#define CXL_MBOX_OP_UNLOCK 0x4603
#define CXL_MBOX_OP_FREEZE_SECURITY 0x4604
#define CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE 0x4605
/* 보안 상태 비트 필드 */
struct cxl_get_security_output {
__le32 flags;
/* bit 0: user passphrase set */
/* bit 1: master passphrase set */
/* bit 2: locked */
/* bit 3: frozen */
/* bit 4: user passphrase attempts remaining (3bit) */
} __packed;
# CXL 보안 상태 확인
cxl list -m mem0 -i
# "security":"disabled" ← 보안 비활성
# "security":"locked" ← 잠김 (패스프레이즈 필요)
# 보안 설정 (cxl-cli)
cxl set-passphrase mem0 --type user --passphrase MYPASS123
cxl lock mem0
cxl unlock mem0 --passphrase MYPASS123
# 안전 삭제
cxl sanitize mem0 --passphrase MYPASS123
# 또는 Crypto Erase (키만 파기)
cxl sanitize mem0 --crypto-erase
CXL PMU (성능 모니터링 유닛)
CXL 3.0부터 정의된 CXL PMU는 CXL 링크의 성능 카운터를 제공합니다.
리눅스 커널 6.6+에서 perf 도구를 통해 CXL 트래픽, 지연, 대역폭 등을 모니터링할 수 있습니다.
PMU는 CXL 장치의 MMIO 레지스터 공간에 매핑되며, perf 서브시스템에 등록됩니다.
# CXL PMU 장치 확인
ls /sys/bus/event_source/devices/ | grep cxl
# cxl_pmu_mem0 — mem0 장치의 PMU
# 사용 가능한 이벤트 확인
ls /sys/bus/event_source/devices/cxl_pmu_mem0/events/
# cxl_rx_clock_ticks — CXL 수신 클럭 틱
# cxl_tx_clock_ticks — CXL 전송 클럭 틱
# cxl_h2d_req — Host→Device 요청 수
# cxl_d2h_rsp — Device→Host 응답 수
# cxl_d2h_data — Device→Host 데이터 전송
# cxl_cache_miss — CXL 캐시 미스
# perf를 이용한 CXL 성능 모니터링
perf stat -e cxl_pmu_mem0/cxl_h2d_req/ \
-e cxl_pmu_mem0/cxl_d2h_rsp/ \
-e cxl_pmu_mem0/cxl_d2h_data/ \
-- sleep 10
# 실시간 모니터링
perf stat -e cxl_pmu_mem0/cxl_rx_clock_ticks/ \
-I 1000 # 1초마다 출력
# CXL 대역폭 계산
# 대역폭(GB/s) = (cxl_d2h_data * 64B) / 측정_시간(s) / 1e9
/* drivers/cxl/core/pmu.c — CXL PMU 드라이버 */
struct cxl_pmu {
struct device dev;
void __iomem *base; /* PMU 레지스터 기본 주소 */
int index; /* PMU 인덱스 */
u64 counter_width; /* 카운터 비트 폭 */
int num_counters; /* 카운터 수 */
};
/* PMU 레지스터 레이아웃 (CXL 3.0 Spec 8.2.7) */
#define CXL_PMU_CAP 0x00 /* PMU 기능 */
#define CXL_PMU_OVERFLOW_STS 0x08 /* 오버플로우 상태 */
#define CXL_PMU_FREEZE 0x10 /* 카운터 정지 */
#define CXL_PMU_COUNTER_CFG(n) (0x100 + (n) * 0x10)
#define CXL_PMU_COUNTER_VAL(n) (0x108 + (n) * 0x10)
/* perf 서브시스템 연동 */
static struct pmu cxl_pmu_perf = {
.task_ctx_nr = perf_invalid_context,
.event_init = cxl_pmu_event_init,
.add = cxl_pmu_event_add,
.del = cxl_pmu_event_del,
.start = cxl_pmu_event_start,
.stop = cxl_pmu_event_stop,
.read = cxl_pmu_event_read,
};
포이즌 관리 (Poison Management)
CXL 메모리에서 ECC 복구 불가능한 에러가 발생하면 해당 주소에 "포이즌(poison)" 마크가 설정됩니다. 커널은 이러한 포이즌 주소를 추적하고, 사용자 공간에 알리며, 필요 시 복구 또는 격리 작업을 수행합니다. CXL 메일박스의 Get Poison List, Inject Poison, Clear Poison 명령을 통해 관리합니다.
# 포이즌 목록 조회
cxl list -m mem0 --poison
# {"memdev":"mem0","poison":[
# {"offset":"0x1000000","length":"0x40","source":"Internal"}]}
# 포이즌 주입 (테스트용, 디버그 빌드 전용)
echo 0x1000000 > /sys/bus/cxl/devices/mem0/inject_poison
# 포이즌 제거
echo 0x1000000 > /sys/bus/cxl/devices/mem0/clear_poison
# 미디어 스캔 (전체 DRAM 검사)
cxl scan-media mem0
# CXL 이벤트 로그 확인
cxl list -m mem0 -H # 건강 상태 이벤트
# 커널 트레이스로 포이즌 이벤트 모니터링
echo 1 > /sys/kernel/debug/tracing/events/cxl/cxl_poison/enable
cat /sys/kernel/debug/tracing/trace_pipe
# cxl_poison: memdev=mem0 host_addr=0x... dpa=0x... length=64 source=Internal
# MCE (Machine Check Exception) 로그 확인
dmesg | grep -i "memory error\|cxl\|poison"
AI/ML 활용 사례
CXL 메모리는 DDR보다 저렴하게 대용량 메모리를 확보할 수 있어 AI 워크로드에 이상적입니다. 특히 LLM(대형 언어 모델) 추론 서버에서 KV 캐시 확장, 모델 가중치 저장, 임베딩 인덱스 상주에 효과적입니다.
| AI 워크로드 | CXL 활용 패턴 | 기대 효과 | 지연 민감도 |
|---|---|---|---|
| LLM 추론 (Llama-70B) | KV 캐시 → CXL 노드 | 배치 크기 8배 증가 | 중간 |
| LLM 학습 | 그래디언트 체크포인트 → CXL | GPU OOM 감소 | 낮음 |
| 임베딩 데이터베이스 | 인덱스 전체 → CXL (TB급) | SSD 없이 상주 가능 | 낮음 |
| RLHF 리플레이 버퍼 | 경험 데이터 → CXL | 메모리 효율 3배 | 낮음 |
| 멀티모달 캐싱 | 이미지/비디오 피처 캐시 → CXL | 재처리 비용 절감 | 중간 |
| RAG (검색 증강 생성) | 벡터 DB 인덱스 → CXL | 검색 지연 수 ms 이내 | 중간 |
LLM KV 캐시 CXL 배치 예시
# vLLM / SGLang의 CXL KV 캐시 설정 예시
import ctypes
import os
# libnuma로 CXL 노드에 KV 캐시 버퍼 할당
def alloc_kv_cache_on_cxl(size_bytes, cxl_numa_node=2):
libnuma = ctypes.CDLL("libnuma.so.1")
ptr = libnuma.numa_alloc_onnode(size_bytes, cxl_numa_node)
if ptr == 0:
raise MemoryError("CXL 노드에 메모리 할당 실패")
return ptr
# PyTorch 텐서를 CXL 주소에 래핑
import torch
kv_cache_ptr = alloc_kv_cache_on_cxl(64 * 1024 ** 3) # 64 GB on CXL
kv_tensor = torch.frombuffer(
(ctypes.c_byte * 64 * 1024 ** 3).from_address(kv_cache_ptr),
dtype=torch.float16)
# CXL 메모리 정책: Hot KV 엔트리는 DDR, Cold는 CXL
class TieredKVCache:
def __init__(self, ddr_size_gb=16, cxl_size_gb=64,
cxl_node=2, ddr_node=0):
self.ddr_cache = alloc_on_node(ddr_size_gb * 1024**3, ddr_node)
self.cxl_cache = alloc_on_node(cxl_size_gb * 1024**3, cxl_node)
self.access_count = {} # 접근 빈도 추적
def put(self, key, value):
# 신규 항목은 CXL에 배치, 접근 빈도에 따라 DDR로 promote
if self.access_count.get(key, 0) > 10:
return self._store_ddr(key, value)
return self._store_cxl(key, value)
성능 측정과 지연시간 비교
| 메모리 유형 | 읽기 지연 (랜덤) | 대역폭 (seq read) | 용량 | 비용 ($/GB) |
|---|---|---|---|---|
| DDR5 (로컬) | ~80ns | ~200 GB/s (6400MT/s x 8ch) | 수십 GB ~ 수 TB | $3~5 |
| DDR5 (원격 NUMA) | ~160ns | ~100 GB/s | 위와 동일 | $3~5 |
| CXL Type3 (CXL 1.1) | ~300~500ns | ~50~100 GB/s (PCIe 5.0 x16) | 수 TB | $1~2 |
| CXL Type3 (CXL 3.0) | ~200~300ns | ~256 GB/s (PCIe 6.0 x16) | 수십 TB | $1~2 |
| Intel Optane PMEM | ~300~400ns | ~40 GB/s | 수 TB | $2~4 (단종) |
| NVMe SSD | ~70~100us | ~14 GB/s (PCIe 5.0 x4) | 수 TB | $0.1~0.3 |
성능 측정 도구
# Intel MLC로 CXL 지연 및 대역폭 측정
mlc --latency_matrix # NUMA 노드 간 지연 행렬
mlc --bandwidth_matrix # NUMA 노드 간 대역폭 행렬
mlc --loaded_latency -d0 -T # 부하 시 지연 곡선
# STREAM 벤치마크 (CXL 노드 지정)
numactl --membind=2 ./stream # CXL 노드에서 실행
numactl --membind=0 ./stream # DDR 노드에서 비교 실행
# lmbench로 lat_mem_rd (랜덤 읽기 지연)
numactl --membind=2 lat_mem_rd -t -P1 512m
# 결과 예시:
# Stride=64 → ~80ns (DDR), ~300ns (CXL)
# Intel PCM CXL 모니터링
pcm-pcie -cxl # CXL 대역폭 실시간 모니터링
pcm-latency # 메모리 지연 분석
pcm-memory # 메모리 대역폭 채널별 분석
# perf mem 트레이싱
perf mem record -a -- sleep 10
perf mem report --sort=mem,dso
# bpftrace로 CXL 접근 패턴 분석
bpftrace -e 'tracepoint:cxl:cxl_aer_uncorrectable_error { printf("CXL UE: %s\n", args->memdev); }'
numactl --membind로 메모리 노드만 변경하여 측정하세요.
numactl --cpunodebind=0 --membind=0 vs numactl --cpunodebind=0 --membind=2
비교가 정확합니다. CPU를 변경하면 L3 캐시 차이가 결과에 영향을 미칩니다.
QEMU CXL 에뮬레이션
실제 CXL 하드웨어 없이도 QEMU를 통해 CXL 환경을 에뮬레이션할 수 있습니다. QEMU 7.2+ 에서 CXL Type3 장치, 스위치, 멀티 루트 포트를 에뮬레이션합니다. CXL 드라이버 개발, 리전 관리 스크립트 테스트, 메모리 티어링 실험에 유용합니다.
# QEMU CXL 에뮬레이션 실행 (Type3 장치 2개)
qemu-system-x86_64 \
-machine q35,cxl=on \
-m 4G,maxmem=8G,slots=8 \
-smp 4 \
-cpu host -enable-kvm \
# CXL 호스트 브리지
-object memory-backend-file,id=cxl-mem0,share=on,mem-path=/tmp/cxlmem0,size=256M \
-object memory-backend-file,id=cxl-mem1,share=on,mem-path=/tmp/cxlmem1,size=512M \
-object memory-backend-file,id=cxl-lsa0,share=on,mem-path=/tmp/cxllsa0,size=256M \
-object memory-backend-file,id=cxl-lsa1,share=on,mem-path=/tmp/cxllsa1,size=256M \
# CXL 호스트 브리지 (CFMWS 윈도우 정의)
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-cxl-fixed-memory-window targets.0=cxl.1,size=4G \
# CXL 루트 포트
-device cxl-rp,port=0,bus=cxl.1,id=root_port0,chassis=0,slot=2 \
-device cxl-rp,port=1,bus=cxl.1,id=root_port1,chassis=0,slot=3 \
# CXL Type3 메모리 장치
-device cxl-type3,bus=root_port0,volatile-memdev=cxl-mem0,lsa=cxl-lsa0,id=cxl-vmem0 \
-device cxl-type3,bus=root_port1,volatile-memdev=cxl-mem1,lsa=cxl-lsa1,id=cxl-vmem1 \
# 게스트 커널 이미지
-kernel /boot/vmlinuz-6.6.0 \
-initrd /boot/initrd.img-6.6.0 \
-append "root=/dev/sda1 console=ttyS0 cxl_acpi.dyndbg=+p" \
-drive file=guest.qcow2,format=qcow2 \
-nographic
# 게스트 내부에서 CXL 장치 확인
lspci | grep -i cxl
# 00:0c.0 PCI bridge: Intel CXL Root Port
# 01:00.0 CXL: Memory Expander
dmesg | grep -i cxl
# cxl_acpi: ACPI0017: found 1 CFMWS instance
# cxl_pci 0000:01:00.0: CXL: bound mem0
# cxl_port: found port root0
cxl list -M
# [{"memdev":"mem0","ram_size":"256.00 MiB"},
# {"memdev":"mem1","ram_size":"512.00 MiB"}]
cxl_acpi.dyndbg=+p cxl_core.dyndbg=+p cxl_pci.dyndbg=+p를
추가하면 CXL 드라이버의 동적 디버그 메시지를 모두 활성화할 수 있습니다.
또한 QEMU의 -device cxl-type3에 num-dc-regions=N 옵션을 추가하면
Dynamic Capacity(동적 용량) 기능도 테스트할 수 있습니다.
진단 도구 (cxl-cli / ndctl)
# cxl-cli 설치 (ndctl 패키지에 포함)
apt install ndctl # Ubuntu
dnf install ndctl # RHEL/Fedora
# CXL 장치 목록
cxl list
# [{"memdev":"mem0","pmem_size":"256.00 GiB","ram_size":"0.00 GiB"}]
# CXL 포트 토폴로지 조회 (전체 정보)
cxl list -BEIMPDRu
# -B: bus -E: endpoint -I: initiator -M: memdev
# -P: port -D: decoder -R: region -u: uuid
# 특정 memdev 상세 정보
cxl list -m mem0 -vi
# { "memdev":"mem0",
# "ram_size":"256.00 GiB",
# "health":{"maintenance_needed":false,
# "temperature":35,
# "dirty_shutdowns":0,
# "volatile_err_cnt":0},
# "serial":"0x1234567890abcdef",
# "firmware_version":"1.0.0" }
# CXL 리전(region) 관리
cxl create-region -m mem0 -d decoder0.0 # 생성
cxl enable-region region0 # 활성화
cxl disable-region region0 # 비활성화
cxl destroy-region region0 # 삭제
# 리전을 DAXCTL로 온라인 (NUMA 노드에 메모리 추가)
daxctl reconfigure-device --mode=system-ram dax0.0
daxctl reconfigure-device --mode=devdax dax0.0 # 원복
# 메모리가 NUMA 노드로 추가됐는지 확인
numactl --hardware
cat /proc/meminfo | grep MemTotal
# CXL 장치 이벤트 로그 확인
cxl list -e -m mem0 # 에러 이벤트 조회
# 건강 상태 모니터링
cxl list -m mem0 -H
# "health":{"life_used_percent":5,"temperature":42,
# "dirty_shutdowns":0,"correctable_errors":12}
| 명령 | 설명 | 주요 옵션 |
|---|---|---|
cxl list | CXL 자원 목록 | -M(memdev), -P(port), -D(decoder), -R(region) |
cxl create-region | CXL 리전 생성 | -m(memdev), -d(decoder), -w(ways), -g(granularity) |
cxl destroy-region | CXL 리전 삭제 | -f(force) |
cxl enable-region | 리전 활성화 | 리전 이름 |
cxl disable-region | 리전 비활성화 | 리전 이름 |
cxl set-passphrase | 보안 패스프레이즈 설정 | --type(user/master) |
cxl sanitize | 안전 삭제 | --crypto-erase |
daxctl reconfigure-device | DAX 장치 모드 전환 | --mode=system-ram / --mode=devdax |
daxctl online-memory | DAX 메모리 온라인 | 장치 이름 |
커널 소스 가이드
| 파일 / 디렉토리 | 설명 | 주요 함수/구조체 |
|---|---|---|
drivers/cxl/ | CXL 드라이버 루트 | — |
drivers/cxl/core/region.c | CXL 리전 생성, NUMA 등록 | cxl_region_attach() |
drivers/cxl/core/port.c | CXL 포트 토폴로지 관리 | cxl_port_setup() |
drivers/cxl/core/hdm.c | HDM 디코더 관리 | cxl_decoder, cxl_hdm_decode_init() |
drivers/cxl/core/mbox.c | 메일박스 프로토콜 | cxl_internal_send_cmd() |
drivers/cxl/core/pmu.c | CXL PMU 드라이버 | cxl_pmu |
drivers/cxl/core/trace.h | CXL 트레이스 이벤트 | trace_cxl_poison() |
drivers/cxl/pci.c | PCIe CXL 장치 바인딩 | cxl_pci_probe() |
drivers/cxl/mem.c | CXL 메모리 장치 드라이버 | cxl_mem_probe() |
drivers/cxl/acpi.c | ACPI CEDT 파싱 | cxl_acpi_probe() |
drivers/cxl/cxlmem.h | CXL 메모리 구조체 정의 | cxl_memdev_state |
drivers/dax/cxl.c | CXL DAX 드라이버 | cxl_dax_region_probe() |
drivers/acpi/numa/hmat.c | HMAT 파싱 | hmat_parse_locality() |
mm/memory-tiers.c | memory_tier 프레임워크 | memory_tier, next_demotion_node() |
include/linux/cxl.h | CXL 공개 API 헤더 | — |
include/linux/memory-tiers.h | memory_tier API | — |
tools/testing/cxl/ | CXL 단위 테스트 | cxl_mock_* |
커널 설정 (Kconfig)
# CXL 지원
CONFIG_CXL_BUS=y # CXL 버스 (기본)
CONFIG_CXL_PCI=y # PCIe CXL 장치
CONFIG_CXL_ACPI=y # ACPI CEDT 기반 CXL 발견
CONFIG_CXL_PMEM=y # CXL PMEM 지원
CONFIG_CXL_MEM=y # CXL 메모리 장치
CONFIG_CXL_PORT=y # CXL 포트 관리
CONFIG_CXL_REGION=y # CXL 리전 지원
CONFIG_CXL_REGION_INVALIDATION_TEST=n # 운영 환경에서는 비활성
# 메모리 티어링
CONFIG_MEMORY_TIER_DEFAULT_DRAM_PERF_VALUES=y
CONFIG_NUMA_BALANCING=y # NUMA 자동 밸런싱
# DAX (직접 접근)
CONFIG_DEV_DAX=y
CONFIG_DEV_DAX_CXL=y # CXL DAX 장치
CONFIG_DEV_DAX_KMEM=y # DAX를 system-ram으로
# CXL PMU
CONFIG_CXL_PMU=y # CXL 성능 카운터
# 디버깅
CONFIG_CXL_VERBOSE_DEBUG=y # 상세 디버그 메시지
CONFIG_DEBUG_CXL=y # CXL 디버그 모드
CXL 프로브 흐름 상세
/* drivers/cxl/pci.c — CXL PCI 프로브 흐름 */
static int cxl_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct cxl_memdev_state *mds;
struct cxl_dev_state *cxlds;
int rc;
/* 1. CXL 레지스터 매핑 (DVSEC에서 레지스터 위치 파싱) */
rc = cxl_pci_setup_regs(pdev, CXL_REGLOC_RBI_MEMDEV, &map);
/* 2. 메모리 장치 상태 초기화 */
mds = cxl_memdev_state_create(&pdev->dev);
cxlds = &mds->cxlds;
/* 3. 메일박스 초기화 */
rc = cxl_pci_setup_mailbox(mds);
/* 4. Identify 명령으로 장치 정보 수집 */
rc = cxl_dev_state_identify(mds);
/* mds->total_bytes: 총 메모리 용량 */
/* mds->volatile_only_bytes: 휘발성 메모리 용량 */
/* mds->persistent_only_bytes: 비휘발성 메모리 용량 */
/* 5. CDAT 테이블 읽기 (DOE 프로토콜) */
rc = cxl_enumerate_cmds(mds);
rc = cxl_pci_doe_init(cxlds);
/* 6. CXL memdev 등록 (/sys/bus/cxl/devices/memN) */
return devm_cxl_add_memdev(&pdev->dev, cxlds);
}
ACPI 테이블과 펌웨어 인터페이스
CXL 장치 발견과 메모리 특성 전달은 ACPI 테이블을 통해 이루어집니다. CEDT(CXL Early Discovery Table)는 CXL 호스트 브리지와 CFMWS를 정의하고, SRAT(System Resource Affinity Table)은 NUMA 노드 소속을, HMAT(Heterogeneous Memory Attribute Table)은 지연/대역폭 특성을 제공합니다.
| ACPI 테이블 | CXL 관련 역할 | 커널 처리 파일 |
|---|---|---|
| CEDT (CXL Early Discovery Table) | CXL 호스트 브리지, CFMWS 정의 | drivers/cxl/acpi.c |
| CFMWS (CXL Fixed Memory Window) | CXL 메모리의 HPA 범위 지정 | drivers/cxl/acpi.c |
| SRAT (System Resource Affinity) | CXL 메모리의 NUMA 노드 소속 | drivers/acpi/numa/srat.c |
| HMAT (Heterogeneous Memory Attribute) | 지연/대역폭 특성, 캐시 정보 | drivers/acpi/numa/hmat.c |
| CDAT (Coherent Device Attribute) | 장치별 메모리 특성 (DOE로 전달) | drivers/cxl/core/cdat.c |
| DSDT/SSDT | CXL 장치 ACPI 이름 공간 | drivers/acpi/ |
# ACPI 테이블 덤프 (iasl 필요)
acpidump -b
iasl -d cedt.dat # CEDT 테이블 역어셈블
# CEDT 내용 예시
# [000h] Signature : "CEDT"
# [024h] Subtable Type : 0001 (CFMWS)
# [030h] Window Size : 0x0000000100000000 (4 GB)
# [038h] Base HPA : 0x0000004080000000
# [040h] Interleave Ways : 1
# [044h] Interleave Targets : Host Bridge UID 0
# HMAT 정보 확인
cat /sys/firmware/acpi/tables/HMAT | xxd | head -40
# SRAT 정보에서 CXL NUMA 확인
dmesg | grep -i "SRAT\|proximity\|CXL"
# SRAT: Node 2 PXM 2 [mem 0x4080000000-0x40ffffffff]
# CDAT (장치에서 DOE로 읽은 데이터)
cat /sys/bus/cxl/devices/mem0/CDAT
# DSMAS: DPA Range 0x0-0x3FFFFFFF, Non-volatile=0
# DSLBIS: Read Latency=300ns, Write Latency=350ns
# DSLBIS: Read Bandwidth=50000 MB/s, Write Bandwidth=45000 MB/s
/* drivers/cxl/acpi.c — CEDT/CFMWS 파싱 */
static int cxl_parse_cfmws(union acpi_subtable_headers *header,
void *arg, const unsigned long end)
{
struct acpi_cedt_cfmws *cfmws = (struct acpi_cedt_cfmws *)header;
struct cxl_cfmws_context *ctx = arg;
/* CFMWS에서 HPA 범위 추출 */
resource_size_t base = cfmws->base_hpa;
resource_size_t size = cfmws->window_size;
int ways = CFMWS_INTERLEAVE_WAYS(cfmws->interleave_ways);
int granularity = CFMWS_INTERLEAVE_GRANULARITY(cfmws->granularity);
/* CXL 루트 디코더 생성 */
return cxl_root_decoder_add(ctx->root_port, base, size, ways, granularity);
}
/* ACPI CEDT 서브테이블 유형 */
/* Type 0: CXL Host Bridge Structure (CHBS) */
/* Type 1: CXL Fixed Memory Window Structure (CFMWS) */
/* Type 2: CXL XOR Interleave Math Structure (CXIMS) */
CXL 스위칭과 메모리 풀링
CXL 2.0부터 도입된 CXL 스위치는 여러 호스트와 CXL 장치를 연결하는 패브릭을 형성합니다. CXL 3.0에서는 다중 레벨 스위칭과 패브릭 연결을 통해 데이터센터 규모의 메모리 풀링이 가능해집니다. MLD(Multi-Logical Device)를 통해 하나의 물리 장치를 여러 호스트가 논리적으로 분할하여 사용합니다.
drivers/cxl/core/mbox.c에서 FM 관련 명령(Tunnel Management)을 처리합니다.
현재 리눅스 커널에서 FM 지원은 초기 단계입니다.
/* CXL 2.0 MLD (Multi-Logical Device) 구조 */
/* 하나의 물리 장치를 여러 LD로 분할 */
struct cxl_mld {
int num_lds; /* 논리 장치 수 (최대 16) */
struct cxl_mld_ld ld[16]; /* 각 LD 정보 */
};
struct cxl_mld_ld {
int ld_id; /* LD 식별자 (0~15) */
resource_size_t capacity; /* LD 용량 */
int host_id; /* 할당된 호스트 ID */
enum cxl_ld_state state; /* Active/Standby */
};
/* CXL 3.0 Back-Invalidate 구조 */
/* 장치가 호스트 캐시를 무효화 요청 */
struct cxl_bi_message {
u64 address; /* 무효화 대상 HPA */
u16 length; /* 무효화 범위 (캐시 라인 수) */
u8 bi_type; /* BI-Snoop, BI-Invalidate */
u8 bi_id; /* 타겟 호스트 ID */
};
CXL 도입 체크리스트
CXL 메모리는 용량 확장에 강점이 있지만 지연 특성이 DRAM과 다릅니다. 운영에서는 "어떤 데이터를 CXL 티어로 내릴지" 정책 설계가 성능을 좌우합니다.
| 점검 항목 | 확인 방법 | 운영 기준 |
|---|---|---|
| 하드웨어 호환성 | CPU CXL 지원 여부 (SPR+) | Intel Sapphire Rapids / AMD Genoa 이상 |
| BIOS/펌웨어 설정 | CEDT/CFMWS 노출 확인 | CXL 메모리 윈도우 활성화 |
| 커널 버전 | uname -r | Linux 6.2+ (리전), 6.6+ (PMU/포이즌) |
| 커널 설정 | grep CXL /boot/config-* | CONFIG_CXL_BUS/PCI/ACPI/MEM=y |
| 토폴로지 인식 | cxl list -BEIMRu | 포트/리전/노드 매핑 일치 |
| NUMA 통합 | numactl --hardware | CXL 노드가 정상 노출 |
| HMAT/CDAT 특성 | /sys/devices/system/node/ | 지연/대역폭 값 합리적 |
| 티어 정책 | memory_tier sysfs | CXL이 적절한 tier에 배치 |
| demotion 테스트 | 메모리 압박 시 CXL 사용량 | hot set은 DRAM 유지 |
| 워크로드 적합성 | latency-sensitive vs capacity-bound | 용량 중심 데이터 우선 배치 |
| 보안 | cxl list -m mem0 -i | 필요시 패스프레이즈 설정 |
| 모니터링 | CXL PMU, perf, numastat | 대시보드 구성 |
# CXL/NUMA 점검 종합 스크립트
#!/bin/bash
echo "=== CXL 장치 ==="
cxl list -M 2>/dev/null || echo "CXL 장치 없음"
echo "=== NUMA 토폴로지 ==="
numactl --hardware
echo "=== 메모리 총량 ==="
cat /proc/meminfo | grep -E "MemTotal|MemAvailable"
echo "=== 메모리 티어 ==="
for tier in /sys/devices/virtual/memory_tiering/memory_tier*; do
echo "$(basename $tier): nodes=$(cat $tier/nodelist 2>/dev/null)"
done
echo "=== CXL 커널 메시지 ==="
dmesg | grep -Ei "cxl|hmat|memory.tier" | tail -20
echo "=== HMAT 지연 정보 ==="
for node in /sys/devices/system/node/node*; do
if [ -f "$node/access0/initiators/read_latency" ]; then
echo "$(basename $node): read_latency=$(cat $node/access0/initiators/read_latency)ns"
fi
done
echo "=== Demotion 설정 ==="
cat /sys/kernel/mm/numa/demotion_enabled 2>/dev/null
cat /proc/sys/kernel/numa_balancing
CXL 3.0/3.1 프로토콜 심화
CXL 3.0은 PCIe 6.0 물리 계층(64 GT/s PAM4) 위에서 동작하며, 256B FLIT(Flow Control Unit)을 기본 전송 단위로 사용합니다. CXL 1.x/2.0의 68B FLIT과 비교하여 프로토콜 효율이 크게 향상되었으며, FEC(Forward Error Correction) 기반 에러 보호, 멀티 프로토콜 슬롯 다중화, 그리고 Back-Invalidate(BI) 채널이 추가되었습니다. CXL 3.1은 TSP(TEE Security Protocol), 포트 기반 라우팅, 향상된 GFD(Global Fabric Attached Memory)를 도입합니다.
FLIT 모드와 프로토콜 다중화
CXL 3.0의 256B FLIT은 여러 프로토콜 메시지를 동시에 포함할 수 있어 링크 활용률이 크게 향상됩니다. 하나의 FLIT에 CXL.io TLP, CXL.cache 요청, CXL.mem 응답이 모두 담길 수 있습니다.
| FLIT 유형 | 슬롯 구성 | 사용 사례 | 데이터 효율 |
|---|---|---|---|
| G0 (Generic 0) | 4개 16B 슬롯 + 186B 데이터 | 일반 데이터 전송 | 73% |
| G1 (Generic 1) | 3개 16B 슬롯 + 202B 데이터 | 대용량 데이터 전송 | 79% |
| G2 (Generic 2) | 2개 16B 슬롯 + 218B 데이터 | 캐시 라인 전송 최적화 | 85% |
| H (Header Only) | 4개 16B 슬롯만 | 제어 메시지 전용 | 제어 전용 |
| BI (Back-Invalidate) | BI 슬롯 포함 | 호스트 캐시 무효화 | BI 전용 |
/* CXL 3.0 FLIT 슬롯 인코딩 */
/* 슬롯 유형 (4비트 인코딩) */
enum cxl_flit_slot_type {
CXL_SLOT_NULL = 0x0, /* 빈 슬롯 */
CXL_SLOT_IO_TLP = 0x1, /* CXL.io TLP */
CXL_SLOT_CACHE_D2H = 0x2, /* CXL.cache D2H Req/Rsp */
CXL_SLOT_CACHE_H2D = 0x3, /* CXL.cache H2D Snoop/Rsp */
CXL_SLOT_MEM_M2S = 0x4, /* CXL.mem M2S Req/RwD */
CXL_SLOT_MEM_S2M = 0x5, /* CXL.mem S2M NDR/DRS */
CXL_SLOT_BI = 0x6, /* Back-Invalidate (CXL 3.0) */
};
/* 256B FLIT 헤더 구조 */
struct cxl_flit_header {
u8 flit_type : 2; /* G0/G1/G2/H */
u8 slot0_type : 4; /* 슬롯 0 프로토콜 유형 */
u8 slot1_type : 4; /* 슬롯 1 프로토콜 유형 */
u8 slot2_type : 4; /* 슬롯 2 프로토콜 유형 */
u8 slot3_type : 2; /* 슬롯 3 (축약 인코딩) */
} __packed;
/* CXL.cache 코히런시 상태 전이 (MESI 확장) */
/*
* 장치가 RdOwn 요청 → 호스트는 GO-E(Exclusive) 또는 GO-M(Modified) 응답
* 장치가 RdShared 요청 → 호스트는 GO-S(Shared) 응답 + 데이터
* 호스트가 SnpInv → 장치는 캐시 라인 무효화 + WritePull(dirty 시)
* 호스트가 SnpData → 장치는 캐시 라인 데이터 전달 + 상태 유지
*/
Back-Invalidate (BI) 채널 상세
CXL 3.0의 Back-Invalidate(BI)는 CXL 장치가 호스트 CPU의 캐시 라인을 직접 무효화할 수 있는 새로운 채널입니다. 이는 다중 호스트가 하나의 CXL 메모리를 공유할 때 캐시 일관성을 유지하는 데 핵심적입니다. BI 없이는 호스트 A가 캐시한 데이터를 호스트 B가 변경해도 A의 캐시가 갱신되지 않아 일관성 문제가 발생합니다.
| BI 메시지 | 방향 | 동작 | 사용 시나리오 |
|---|---|---|---|
| BISnoop | Device → Host | 호스트 캐시 라인 상태 조회 | 다른 호스트 캐시 확인 |
| BIInvalidate | Device → Host | 호스트 캐시 라인 무효화 | 공유 메모리 변경 전 |
| BIRsp | Host → Device | BI 요청에 대한 응답 | 캐시 라인 상태/데이터 반환 |
| BIConflict | Host → Device | 동시 요청 충돌 보고 | 데드락 방지 |
/* CXL 3.0 Back-Invalidate 처리 흐름 (개념) */
/*
* 시나리오: Host A와 Host B가 동일한 CXL 메모리 주소 공유
*
* 1. Host A가 CXL 메모리 주소 X를 읽음 → CPU A 캐시에 상태 S(Shared)
* 2. Host B가 동일 주소 X에 쓰기 요청
* 3. CXL 장치가 Host A에 BIInvalidate(X) 전송
* 4. Host A CPU가 캐시 라인 X를 무효화하고 BIRsp 반환
* 5. CXL 장치가 Host B의 쓰기 완료 응답(S2M Cmp)
* 6. Host A가 다시 X를 읽으면 CXL 장치에서 최신 데이터 로드
*/
/* BI 처리를 위한 커널 후보 인터페이스 (향후 구현 예정) */
struct cxl_bi_handler {
int (*bi_invalidate)(struct cxl_port *port,
u64 hpa, u32 length);
int (*bi_snoop)(struct cxl_port *port,
u64 hpa, u8 *state);
};
/* CXL 3.1 향상 사항 */
/*
* - TSP (TEE Security Protocol): 장치별 메모리 영역 암호화/격리
* - 포트 기반 라우팅: 스위치에서 LD ID가 아닌 포트 번호로 라우팅
* - GFD (Global Fabric Attached Memory):
* 여러 패브릭 스위치를 연결한 데이터센터 규모 메모리 풀
* - 향상된 QoS: LD별 대역폭/지연 보장 (QoS Telemetry)
* - CMA+ (향상된 메모리 인증): MAC 태그 크기 확장
*/
Type 1/2/3 디바이스 심화 비교
CXL 장치 유형은 사용하는 서브프로토콜과 하드웨어 구성에 따라 결정됩니다. 각 유형의 메모리 모델, 코히런시 엔진, 커널 드라이버 바인딩, 성능 특성이 다르며, 실제 하드웨어 제품도 각기 다른 형태(E3.S, EDSFF, CXL DIMM, AIC)로 출시됩니다.
HDM Bias 모드: Host-Bias vs Device-Bias
CXL Type2 장치는 HDM-DB(Device-Bias)를 지원하여, 메모리 접근 코히런시 관리 주체를 동적으로 전환할 수 있습니다. GPU 커널이 VRAM을 집중적으로 사용할 때는 Device-Bias(장치가 코히런시 관리), CPU가 결과를 읽을 때는 Host-Bias(호스트가 코히런시 관리)로 전환합니다.
| 특성 | HDM-H (Host-Only) | HDM-DB Device-Bias | HDM-DB Host-Bias |
|---|---|---|---|
| 코히런시 관리 주체 | 호스트 CPU | 장치 (DCOH) | 호스트 CPU |
| 장치 유형 | Type 3 | Type 2 | Type 2 |
| 호스트 캐싱 | 가능 | 불가 (UC 매핑) | 가능 |
| 장치 캐싱 | 불가 | 가능 (WB) | 불가 |
| 전환 비용 | 해당 없음 | Bias 전환 시 캐시 플러시 필요 | |
| 최적 사용 시점 | 항상 | GPU 연산 중 | CPU 결과 읽기 |
| 커널 인터페이스 | DAX / system-ram | HMM ZONE_DEVICE + migrate_vma() | |
/* Type2 HDM-DB Bias 전환 (개념적 커널 인터페이스) */
/* HMM 기반 Type2 CXL 메모리 접근 */
#include <linux/hmm.h>
/* Device-Bias → Host-Bias 전환 */
static int cxl_type2_switch_to_host_bias(
struct cxl_memdev *cxlmd,
u64 dpa_start, u64 length)
{
/* 1. 장치 캐시 플러시 (DCOH → 메모리) */
cxl_flush_device_cache(cxlmd, dpa_start, length);
/* 2. Bias 모드 레지스터 업데이트 */
cxl_set_hdm_bias(cxlmd, dpa_start, length,
CXL_HDM_BIAS_HOST);
/* 3. 호스트 TLB shootdown (UC → WB 전환) */
flush_tlb_range(vma, addr, addr + length);
return 0;
}
/* Type2 장치의 ZONE_DEVICE 페이지 등록 */
/* mm/hmm.c와 연동하여 CPU 페이지 테이블에 매핑 */
static int cxl_type2_register_memory(
struct cxl_memdev *cxlmd)
{
struct dev_pagemap pgmap = {
.type = MEMORY_DEVICE_COHERENT, /* 코히런트 장치 메모리 */
.range = {
.start = cxlmd->dpa_base,
.end = cxlmd->dpa_base + cxlmd->size - 1,
},
.ops = &cxl_type2_pgmap_ops,
};
return devm_memremap_pages(&cxlmd->dev, &pgmap);
}
HDM 디코더 프로그래밍 상세
HDM 디코더는 CXL 토폴로지의 각 레벨에서 주소 라우팅을 담당합니다. 루트 디코더(CFMWS 기반), 스위치 디코더(다운스트림 포트 라우팅), 엔드포인트 디코더(HPA→DPA 최종 변환)의 3단계 디코더 체인을 거쳐 메모리 접근이 이루어집니다. 커널은 리전 생성 시 이 디코더 체인을 자동으로 프로그래밍합니다.
/* drivers/cxl/core/hdm.c — HDM 디코더 레지스터 프로그래밍 */
/* HDM 디코더 레지스터 오프셋 (CXL 3.0 Spec 8.2.5) */
#define CXL_HDM_DECODER_CTRL_OFFSET 0x00
#define CXL_HDM_DECODER_TARGET_LIST 0x04
#define CXL_HDM_DECODER_BASE_LOW 0x10
#define CXL_HDM_DECODER_BASE_HIGH 0x14
#define CXL_HDM_DECODER_SIZE_LOW 0x18
#define CXL_HDM_DECODER_SIZE_HIGH 0x1C
#define CXL_HDM_DECODER_CTRL 0x20
#define CXL_HDM_DECODER_SKIP_LOW 0x24
#define CXL_HDM_DECODER_SKIP_HIGH 0x28
/* 디코더 제어 레지스터 비트 필드 */
#define CXL_HDM_DECODER_CTRL_COMMIT BIT(9) /* 디코더 커밋 */
#define CXL_HDM_DECODER_CTRL_COMMITTED BIT(10) /* 커밋 완료 */
#define CXL_HDM_DECODER_CTRL_ERROR BIT(11) /* 커밋 에러 */
#define CXL_HDM_DECODER_CTRL_TYPE BIT(12) /* 0=RAM, 1=PMEM */
#define CXL_HDM_DECODER_CTRL_IW_MASK GENMASK(7, 4) /* 인터리브 ways */
#define CXL_HDM_DECODER_CTRL_IG_MASK GENMASK(3, 0) /* 인터리브 granularity */
/* 엔드포인트 디코더 프로그래밍 */
static int cxl_hdm_decode_commit(
struct cxl_decoder *cxld,
struct cxl_endpoint_decoder *cxled)
{
void __iomem *hdm = cxld->hpa_range.start;
u32 ctrl;
/* 1. HPA 범위 레지스터 설정 */
lo_hi_writeq(cxld->hpa_range.start,
hdm + CXL_HDM_DECODER_BASE_LOW);
lo_hi_writeq(range_len(&cxld->hpa_range),
hdm + CXL_HDM_DECODER_SIZE_LOW);
/* 2. 인터리브 설정 */
ctrl = FIELD_PREP(CXL_HDM_DECODER_CTRL_IW_MASK,
ilog2(cxld->interleave_ways));
ctrl |= FIELD_PREP(CXL_HDM_DECODER_CTRL_IG_MASK,
ilog2(cxld->interleave_granularity) - 8);
/* 3. 디코더 커밋 (하드웨어 활성화) */
ctrl |= CXL_HDM_DECODER_CTRL_COMMIT;
writel(ctrl, hdm + CXL_HDM_DECODER_CTRL);
/* 4. 커밋 완료 대기 */
return cxl_hdm_wait_for_commit(hdm);
}
/* HPA → DPA 변환 로직 (인터리브 고려) */
/*
* 2-way 인터리브, 256B granularity 예시:
*
* HPA: 0x0000_1000_0000_0100
* ├── 비트[8] = 1 → Target: mem1
* └── DPA = (HPA[63:9] << 8) | HPA[7:0]
* = 0x0000_0800_0000_0000 | 0x00
*
* 4-way 인터리브, 256B granularity 예시:
* HPA: 0x0000_1000_0000_0300
* ├── 비트[9:8] = 0b11 → Target: mem3
* └── DPA = (HPA[63:10] << 8) | HPA[7:0]
*/
# HDM 디코더 상태 확인 (sysfs)
ls /sys/bus/cxl/devices/decoder*
# 루트 디코더 상세
cat /sys/bus/cxl/devices/decoder0.0/start # HPA 시작 (16진수)
cat /sys/bus/cxl/devices/decoder0.0/size # 디코더 범위 크기
cat /sys/bus/cxl/devices/decoder0.0/interleave_ways # 인터리브 ways
cat /sys/bus/cxl/devices/decoder0.0/interleave_granularity # 인터리브 단위
cat /sys/bus/cxl/devices/decoder0.0/mode # ram 또는 pmem
# 엔드포인트 디코더 상세
cat /sys/bus/cxl/devices/endpoint2/decoder2.0/start
cat /sys/bus/cxl/devices/endpoint2/decoder2.0/dpa_resource_start # DPA 시작
cat /sys/bus/cxl/devices/endpoint2/decoder2.0/dpa_size # DPA 크기
# 디코더 커밋 상태 확인
cat /sys/bus/cxl/devices/decoder0.0/committed # 1이면 활성
# 인터리브 방식별 HPA 비트 매핑
# ways=1, gran=256: HPA → mem0 (고정)
# ways=2, gran=256: HPA[8] → mem0/mem1
# ways=4, gran=256: HPA[9:8] → mem0~mem3
# ways=8, gran=256: HPA[10:8] → mem0~mem7
# ways=2, gran=4K: HPA[12] → mem0/mem1
# ways=4, gran=4K: HPA[13:12] → mem0~mem3
cxl_calc_interleave_pos()에서 이를 처리합니다.
XOR 인터리브는 특정 접근 패턴에서 모듈로 방식보다 더 균등한 분산을 제공합니다.
커널 CXL 드라이버 내부 심화
CXL 커널 드라이버는 cxl_bus라는 전용 가상 버스를 기반으로 동작합니다.
모든 CXL 장치(포트, 디코더, memdev, 리전)는 이 버스에 등록되며,
장치-드라이버 매칭은 cxl_bus_type의 match 콜백을 통해 이루어집니다.
드라이버 간의 의존성은 모듈 레벨에서 관리되며, 프로브 순서가 중요합니다.
/* drivers/cxl/cxl.h — CXL 버스 타입과 장치/드라이버 매칭 */
/* CXL 버스 장치 유형 */
enum cxl_devtype {
CXL_DEVTYPE_ROOT, /* ACPI CXL 루트 */
CXL_DEVTYPE_PORT, /* CXL 포트 (루트/스위치) */
CXL_DEVTYPE_MEMDEV, /* CXL 메모리 장치 */
CXL_DEVTYPE_DECODER, /* CXL 디코더 */
CXL_DEVTYPE_REGION, /* CXL 리전 */
CXL_DEVTYPE_PMU, /* CXL PMU */
};
/* CXL 포트 구조체 */
struct cxl_port {
struct device dev;
struct device *host_bridge; /* 상위 호스트 브리지 */
struct device *uport_dev; /* 업스트림 포트 장치 */
struct list_head dports; /* 다운스트림 포트 목록 */
struct cxl_hdm *hdm; /* HDM 디코더 관리 */
int id;
int depth; /* 토폴로지 깊이 */
bool dead; /* 제거 중 */
int nr_dports; /* 다운스트림 포트 수 */
struct cxl_cdat cdat; /* CDAT 정보 */
};
/* CXL memdev 구조체 */
struct cxl_memdev {
struct device dev;
struct cdev cdev; /* 문자 장치 (ioctl) */
struct cxl_dev_state *cxlds; /* 장치 상태 */
struct cxl_port *endpoint; /* 연결된 엔드포인트 */
int id;
int num_ram; /* RAM 디코더 수 */
int num_pmem; /* PMEM 디코더 수 */
};
/* CXL 버스 매칭 */
static int cxl_bus_match(struct device *dev,
struct device_driver *drv)
{
/* device_type으로 매칭 */
return dev->type == &((struct cxl_driver *)drv)->type;
}
lock_dep과 device_lock()을 사용하여 프로브 순서를 보장하며,
cxl_port_probe()는 상위 포트가 준비될 때까지 대기합니다.
문제 발생 시 cxl_core.dyndbg=+p 부팅 옵션으로 프로브 로그를 확인하세요.
리전 인터리브 구성 심화
CXL 리전의 인터리브 구성은 대역폭과 지연 특성에 큰 영향을 미칩니다. 인터리브 ways(병렬도)와 granularity(전환 단위)의 조합에 따라 워크로드에 최적화된 메모리 배치가 달라집니다.
| 인터리브 구성 | Granularity | 최적 워크로드 | 대역폭 | 장애 영향 |
|---|---|---|---|---|
| 1-way (non-interleave) | 해당 없음 | 독립 메모리 풀 | ~50 GB/s | 해당 장치만 |
| 2-way, 256B | 256B | 대역폭 집중 순차 접근 | ~100 GB/s | 전체 리전 |
| 2-way, 4KB | 4KB | 페이지 단위 분산 | ~100 GB/s | 전체 리전 |
| 4-way, 256B | 256B | 최대 대역폭 (HPC, AI) | ~200 GB/s | 전체 리전 |
| 8-way, 256B | 256B | 극한 대역폭 (벤치마크) | ~256 GB/s | 전체 리전 |
# 인터리브 리전 생성 실전 예제
# 사전 조건: CXL 장치 확인
cxl list -M
# [{"memdev":"mem0","ram_size":"256.00 GiB"},
# {"memdev":"mem1","ram_size":"256.00 GiB"},
# {"memdev":"mem2","ram_size":"256.00 GiB"},
# {"memdev":"mem3","ram_size":"256.00 GiB"}]
# 루트 디코더 확인 (CFMWS 범위)
cxl list -D -d root
# [{"decoder":"decoder0.0","resource":"0x4080000000",
# "size":"4.00 TiB","interleave_ways":4}]
# 4-way 인터리브 리전 생성 (1TB = 256GB x 4)
cxl create-region -m mem0 -m mem1 -m mem2 -m mem3 \
-d decoder0.0 -w 4 -g 256 -s 1024G
# cxl region: created region0
# 리전 확인
cxl list -R
# [{"region":"region0","resource":"0x4080000000",
# "size":"1.00 TiB","interleave_ways":4,
# "interleave_granularity":256,
# "state":"enabled"}]
# DAX 장치로 NUMA 노드에 추가
daxctl list
# [{"chardev":"dax0.0","size":"1.00 TiB","mode":"devdax"}]
daxctl reconfigure-device --mode=system-ram --no-online dax0.0
daxctl online-memory dax0.0 --no-movable # movable 비활성 (안정성)
# NUMA 노드 확인 — 4-way 인터리브 1TB
numactl --hardware | grep "node 2"
# node 2 size: 1048576 MB
# 대역폭 벤치마크 (STREAM)
numactl --cpunodebind=0 --membind=2 ./stream_c.exe
# Copy: 95,234 MB/s (4-way 인터리브)
# Scale: 94,567 MB/s
# Add: 96,789 MB/s
# Triad: 95,123 MB/s
메모리 티어링 정책 심화
Linux 커널의 memory_tier 프레임워크는 각 NUMA 노드를 성능 특성에 따라 계층(tier)으로 분류하고, 페이지의 접근 빈도에 따라 자동으로 demotion(하강)과 promotion(승격)을 수행합니다. CXL 메모리를 효과적으로 활용하려면 이 정책을 워크로드에 맞게 튜닝해야 합니다.
/* mm/memory-tiers.c — 추상 거리(adistance) 기반 티어 배치 */
/* 추상 거리 상수 정의 */
#define MEMTIER_ADISTANCE_DRAM 512 /* 로컬 DRAM 기준 */
#define MEMTIER_ADISTANCE_PMEM 1024 /* PMEM 기준 */
#define MEMTIER_HOTPLUG_RANGE 128 /* 동일 티어 판정 범위 */
/* CXL 메모리의 adistance 계산 (drivers/cxl/core/cdat.c) */
static int cxl_cdat_calculate_adistance(
struct cxl_port *port)
{
struct access_coordinate coord;
int adist;
/* CDAT DSLBIS에서 지연/대역폭 읽기 */
coord.read_latency = port->cdat.read_latency; /* 예: 300ns */
coord.read_bandwidth = port->cdat.read_bandwidth; /* 예: 50000 MB/s */
/* 추상 거리 계산: 지연 기반 (높을수록 느림) */
/* DRAM(80ns) → 512, CXL(300ns) → ~700, PMEM(500ns) → 1024 */
adist = MEMTIER_ADISTANCE_DRAM +
(coord.read_latency - 80) * 512 / (500 - 80);
return clamp(adist, 0, 4096);
}
/* 노드 demotion 경로 결정 */
/* mm/migrate.c — next_demotion_node() */
int next_demotion_node(int node)
{
struct demotion_nodes *nd;
int target;
nd = rcu_dereference(node_demotion[node]);
if (!nd)
return NUMA_NO_NODE;
/* 라운드 로빈으로 다음 demotion 대상 노드 선택 */
target = nd->preferred[nd->nr++ % nd->nr_nodes];
return target;
}
/* Promotion 경로: NUMA fault 핸들러 */
/* mm/memory.c — do_numa_page() */
static vm_fault_t do_numa_page(struct vm_fault *vmf)
{
struct folio *folio = vm_normal_folio(vmf->vma, ...);
int nid = folio_nid(folio);
int target_nid;
/* 페이지가 현재 노드보다 느린 티어에 있는지 확인 */
if (node_is_toptier(nid))
return 0; /* 이미 최상위 tier */
/* 접근 빈도 확인 후 promotion 결정 */
target_nid = numa_migrate_prep(folio, vmf, ...);
if (target_nid != NUMA_NO_NODE)
migrate_misplaced_folio(folio, vmf->vma, target_nid);
return 0;
}
# 메모리 티어링 정책 튜닝 가이드
# 1. NUMA 밸런싱 활성화 (promotion 전제 조건)
echo 1 > /proc/sys/kernel/numa_balancing
echo 2 > /proc/sys/kernel/numa_balancing # 2=tiered (6.6+)
# 2. Demotion 활성화
echo true > /sys/kernel/mm/numa/demotion_enabled
# 3. Promotion 스캔 간격 조정 (기본 1000ms)
echo 500 > /proc/sys/kernel/numa_balancing_scan_delay_ms
# AI 워크로드: 500ms (빠른 promotion), DB: 2000ms (안정성)
# 4. Promotion 스캔 크기 (기본 256MB)
echo 512 > /proc/sys/kernel/numa_balancing_scan_size_mb
# 대용량 워크로드는 512MB로 증가
# 5. Promotion 임계값 조정
echo 16 > /proc/sys/kernel/numa_balancing_promote_rate_limit_MBps
# promotion 속도 제한 (과도한 마이그레이션 방지)
# 6. 워크로드별 정책 예시
# AI 추론 서버 (LLM): Hot KV 캐시 → DDR, 모델 가중치 → CXL
numactl --preferred=0 --membind=0,2 python llm_server.py
# 자동 tiering: 자주 접근하는 KV → DDR promotion
# 모델 가중치는 접근 빈도 낮아 CXL에 유지
# 데이터베이스 서버: 인덱스 → DDR, 데이터 → CXL
numactl --interleave=0,2 mysqld
# 인덱스 hot set 자동 promotion, cold data CXL demotion
# 7. 티어링 모니터링
# NUMA 페이지 이동 통계
cat /proc/vmstat | grep -E "pgpromote|pgdemote|numa"
# pgpromote_success 12345 ← CXL→DDR 성공한 페이지 수
# pgdemote_kswapd 67890 ← DDR→CXL demotion (kswapd)
# pgdemote_direct 1234 ← DDR→CXL demotion (직접)
# numa_hint_faults 98765 ← NUMA fault 발생 수
# perf로 promotion/demotion 이벤트 추적
perf stat -e migrate:mm_migrate_pages \
-e sched:sched_numa_pair_template \
-p $(pidof my_app) -- sleep 60
pgpromote_success와 pgdemote_kswapd가 동시에 높다면 이 현상입니다.
2) Demotion 지연: kswapd가 CXL 노드로 demote하기 전에 DDR에서 직접 회수(reclaim)하면
CXL 활용률이 낮아집니다. /proc/sys/vm/zone_reclaim_mode=0으로 설정하세요.
3) Movable 페이지 문제: DAX를 system-ram으로 변환할 때 --no-movable 옵션 없이 추가하면
CXL 메모리가 커널 내부 할당에 사용되어 핫 리무브가 불가능해집니다.
CXL Dynamic Capacity (동적 용량)
CXL 3.0에서 도입된 Dynamic Capacity(DC) 기능은 CXL 메모리 장치의 용량을 호스트에 동적으로 할당/해제할 수 있게 합니다. 클라우드 환경에서 VM이나 컨테이너에 필요한 만큼만 CXL 메모리를 할당하고, 반납된 메모리를 다른 호스트에 재할당하는 메모리 오버커밋이 가능해집니다. DC는 Fabric Manager(FM)가 메모리 블록의 할당/해제를 관리합니다.
/* CXL Dynamic Capacity 메일박스 명령 */
/* DC 관련 메일박스 오프코드 */
#define CXL_MBOX_OP_GET_DC_CONFIG 0x4800 /* DC 설정 조회 */
#define CXL_MBOX_OP_GET_DC_EXTENT_LIST 0x4801 /* DC 범위 목록 */
#define CXL_MBOX_OP_ADD_DC_RESPONSE 0x4802 /* DC 추가 응답 */
#define CXL_MBOX_OP_RELEASE_DC 0x4803 /* DC 해제 */
/* DC 설정 조회 응답 구조체 */
struct cxl_mbox_dc_config {
u8 num_regions; /* DC 리전 수 (최대 8) */
u8 regions_returned;
u8 reserved[6];
struct {
__le64 base; /* DPA 시작 */
__le64 decode_len; /* 최대 디코딩 크기 */
__le64 region_len; /* 리전 크기 */
__le64 block_size; /* DC 블록 크기 (예: 4 GB) */
__le32 dsmad_handle; /* DSMAS 핸들 */
u8 flags; /* DC 플래그 */
u8 reserved[3];
} region[];
} __packed;
/* DC 이벤트 레코드 (장치 → 호스트 알림) */
struct cxl_dc_event_record {
u8 event_type; /* Add/Release/Force Release */
u8 flags;
__le16 host_id; /* 대상 호스트 */
u8 region_index; /* DC 리전 인덱스 */
u8 reserved[3];
__le64 dpa_start; /* 추가/해제 DPA 시작 */
__le64 length; /* 추가/해제 크기 */
u8 tag[16]; /* DC 범위 태그 */
} __packed;
/* DC 범위(extent) 추가 처리 (개념적 코드) */
static int cxl_dc_add_extent(
struct cxl_memdev *cxlmd,
u64 dpa_start, u64 length)
{
/* 1. DAX 장치에 새 범위 추가 */
dev_dax_grow(cxlmd->dax_dev, dpa_start, length);
/* 2. system-ram 모드면 메모리 핫플러그 */
if (cxlmd->mode == CXL_DAX_SYSTEM_RAM)
add_memory(cxlmd->nid, dpa_start, length,
MHP_MERGE_RESOURCE);
/* 3. Add 응답을 장치에 전송 */
return cxl_dc_send_add_response(cxlmd, dpa_start, length);
}
# QEMU에서 Dynamic Capacity 테스트
# DC 지원 Type3 장치 생성 (QEMU)
-device cxl-type3,bus=root_port0,volatile-memdev=cxl-mem0,\
lsa=cxl-lsa0,id=cxl-vmem0,num-dc-regions=2
# 게스트에서 DC 설정 확인
cxl list -m mem0 -vi | grep -i "dc\|dynamic"
# "dc_region_count":2
# "dc_region0":{"base":"0x0","max_size":"2.00 GiB","block_size":"256 MiB"}
# "dc_region1":{"base":"0x80000000","max_size":"2.00 GiB","block_size":"256 MiB"}
# DC 리전 생성 (커널 지원 진행 중)
# 향후: cxl create-region --type=dc -m mem0 -d decoder0.0
# DC 범위가 동적으로 추가/제거되며 NUMA 노드 메모리 크기 변동
cxl create-region --type=dc 형태로 지원될 예정입니다.
QEMU 8.2+에서 num-dc-regions 옵션으로 DC 에뮬레이션 테스트가 가능합니다.
CXL RAS와 에러 처리
CXL은 PCIe AER(Advanced Error Reporting) 위에 CXL 고유의 RAS(Reliability, Availability, Serviceability) 메커니즘을 추가합니다. CXL 이벤트 로그, 바이러스(Viral) 전파 모드, Component Error Log(CEL), 그리고 GHES(Generic Hardware Error Source)를 통한 APEI 연동까지 다중 계층 에러 보고 체계를 갖추고 있습니다.
| 에러 유형 | 보고 경로 | 커널 처리 | 심각도 |
|---|---|---|---|
| Correctable ECC | CXL Event Log + GHES | 카운터 증가, 로그 기록 | 정보 |
| Uncorrectable ECC | MCE + CXL Poison + GHES | memory_failure(), 페이지 오프라인 | 경고 |
| 포이즌 감지 | S2M DRS with Poison 비트 | trace_cxl_poison(), SIGBUS | 위험 |
| PCIe AER (CXL) | AER 인터럽트 | cxl_pci_aer_handler() | 심각 |
| CXL Protocol Error | CXL Event Log | cxl_event_trace() | 심각 |
| Viral 전파 | CXL Status Register | 장치 격리 + 리전 비활성화 | 치명적 |
| Overtemp | CXL Health Info | thermal_zone 연동 | 경고 |
| Dirty Shutdown | CXL Event Log | PMEM 데이터 복구 필요 | 경고 |
/* drivers/cxl/core/ras.c — CXL RAS 처리 */
/* CXL 이벤트 로그 유형 */
enum cxl_event_log_type {
CXL_EVENT_TYPE_INFO = 0x00, /* 정보 이벤트 */
CXL_EVENT_TYPE_WARN = 0x01, /* 경고 이벤트 */
CXL_EVENT_TYPE_FAIL = 0x02, /* 실패 이벤트 */
CXL_EVENT_TYPE_FATAL = 0x03, /* 치명적 이벤트 */
CXL_EVENT_TYPE_DCD = 0x04, /* Dynamic Capacity 이벤트 */
};
/* CXL 이벤트 레코드 공통 헤더 */
struct cxl_event_record_hdr {
uuid_t id; /* 이벤트 UUID */
__le32 flags_length; /* 플래그 + 길이 */
__le16 handle; /* 이벤트 핸들 */
__le16 related_handle; /* 관련 이벤트 */
__le64 timestamp; /* 발생 시각 */
u8 maintenance_op; /* 유지보수 권고 */
u8 reserved[15];
} __packed;
/* CXL AER 에러 핸들러 */
static pci_ers_result_t cxl_error_detected(
struct pci_dev *pdev,
pci_channel_state_t state)
{
struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
/* Viral 상태 확인 */
if (cxl_check_viral(cxlds)) {
dev_err(&pdev->dev, "CXL Viral 상태 감지 - 장치 격리");
cxl_disable_region(cxlds);
return PCI_ERS_RESULT_DISCONNECT;
}
/* Uncorrectable Error 처리 */
cxl_handle_ras_uc_error(cxlds);
return PCI_ERS_RESULT_NEED_RESET;
}
/* Viral 전파 모드 */
/*
* CXL Viral은 "독성 전파" 메커니즘입니다.
* 한 장치에서 복구 불가능한 에러가 발생하면,
* 해당 장치와 연결된 모든 포트/스위치에 Viral 상태가 전파됩니다.
* 목적: 오염된 데이터가 다른 장치/호스트로 전파되는 것 방지
* 결과: Viral 상태의 모든 장치/리전이 비활성화됨
*/
# CXL RAS 이벤트 모니터링
# CXL 이벤트 로그 조회
cxl list -m mem0 -e
# [{"event_log":"Information","nr_records":3},
# {"event_log":"Warning","nr_records":0},
# {"event_log":"Failure","nr_records":1}]
# 건강 상태 상세 조회
cxl list -m mem0 -H
# {"health":{
# "maintenance_needed": false,
# "performance_degraded": false,
# "hw_replacement_needed": false,
# "media_normal": true,
# "life_used_percent": 5,
# "temperature": 42,
# "dirty_shutdowns": 0,
# "volatile_err_cnt": 0,
# "pmem_err_cnt": 0}}
# 커널 트레이스 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/cxl/enable
cat /sys/kernel/debug/tracing/trace_pipe
# cxl_general_media: memdev=mem0 dpa=0x1000 descriptor=UC ...
# cxl_aer_uncorrectable_error: memdev=mem0 status=0x... ...
# cxl_poison: memdev=mem0 host_addr=0x... source=Internal
# AER 에러 확인
lspci -vvv -d ::0502 | grep -A5 "AER"
# UESta: ... (Uncorrectable Error Status)
# CESta: ... (Correctable Error Status)
# rasdaemon으로 CXL 에러 수집
systemctl start rasdaemon
ras-mc-ctl --errors # CXL 에러 포함 전체 RAS 이벤트
dmesg | grep "CXL.*[Vv]iral"로 상태를 확인할 수 있습니다.
CXL 메모리 핫플러그
CXL Type3 메모리 장치는 시스템 동작 중 추가/제거가 가능한 핫플러그를 지원합니다. PCIe 핫플러그 인프라를 기반으로 CXL 장치를 발견하고, DAX 장치 등록, system-ram 전환, NUMA 노드 추가까지의 전체 과정이 런타임에 이루어집니다. 반대로 장치 제거 시에는 메모리 오프라인, DAX 해제, 리전 삭제가 순서대로 진행됩니다.
# CXL 메모리 핫플러그 (런타임 장치 추가)
# 1. 장치 삽입 후 자동 감지 확인
dmesg | tail -20
# cxl_pci 0000:81:00.0: CXL.mem: bound mem2
# cxl_port: port3 registered, depth 1
# 2. 새 장치 확인
cxl list -M
# [..., {"memdev":"mem2","ram_size":"512.00 GiB"}]
# 3. 리전 생성 및 DAX 등록
cxl create-region -m mem2 -d decoder0.0 -w 1 -g 256 -s 512G
daxctl reconfigure-device --mode=system-ram dax0.1
# 4. NUMA 노드 확인
numactl --hardware
# node 3 추가됨 (512 GB, CPU-less)
# CXL 메모리 핫리무브 (런타임 장치 제거)
# 1. 메모리 오프라인 (페이지 마이그레이션)
daxctl offline-memory dax0.1
# 모든 페이지가 다른 노드로 마이그레이션됨
# 2. DAX 장치 모드 복원
daxctl reconfigure-device --mode=devdax dax0.1
# 3. 리전 비활성화 및 삭제
cxl disable-region region1
cxl destroy-region region1
# 4. 장치 unbind (PCIe 핫플러그)
echo 1 > /sys/bus/cxl/devices/mem2/driver/unbind
# 또는 물리적으로 E3.S 장치 제거
# 핫리무브 실패 시 원인 확인
# "unmovable page" 에러 → 커널 내부 할당 페이지 (non-movable)
# 해결: daxctl online-memory --no-movable 대신
# daxctl online-memory (movable zone 사용)
# 핫플러그 이벤트 모니터링
udevadm monitor --subsystem-match=cxl
# UDEV [1234.5678] add /devices/.../cxl/mem2
# UDEV [1234.5678] remove /devices/.../cxl/mem2
daxctl online-memory dax0.0(기본: movable zone)으로 온라인해야 합니다.
--no-movable 옵션을 사용하면 커널 내부 할당이 CXL에 배치되어
핫리무브가 불가능해집니다. 단, movable zone은 커널 slab 할당에 사용되지 않으므로
일부 워크로드에서 성능 차이가 있을 수 있습니다.
프로덕션에서는 핫리무브 필요 여부에 따라 선택하세요.
QEMU CXL 에뮬레이션 심화
QEMU의 CXL 에뮬레이션은 스위치, 멀티 루트 포트, MLD(Multi-Logical Device), Dynamic Capacity, PMEM 모드 등 다양한 CXL 토폴로지를 구성할 수 있습니다. 실제 하드웨어 없이 커널 드라이버 개발, 리전 관리 스크립트, 티어링 정책을 테스트하는 데 필수적입니다.
# QEMU CXL 고급 토폴로지: 스위치 + MLD + DC
# === 토폴로지 구성 ===
# Host Bridge
# ├── Root Port 0 ─── Type3 (mem0, 256MB RAM)
# ├── Root Port 1 ─── Type3 (mem1, 256MB RAM + 256MB PMEM)
# └── Root Port 2 ─── CXL Switch
# ├── DSP 0 ─── Type3 (mem2, 512MB RAM)
# └── DSP 1 ─── Type3 (mem3, 512MB RAM, DC)
qemu-system-x86_64 \
-machine q35,cxl=on \
-m 4G,maxmem=16G,slots=8 \
-smp 8 \
-cpu host -enable-kvm \
\
# 메모리 백엔드
-object memory-backend-file,id=vmem0,share=on,mem-path=/tmp/cxl-vmem0,size=256M \
-object memory-backend-file,id=vmem1,share=on,mem-path=/tmp/cxl-vmem1,size=256M \
-object memory-backend-file,id=pmem1,share=on,mem-path=/tmp/cxl-pmem1,size=256M \
-object memory-backend-file,id=vmem2,share=on,mem-path=/tmp/cxl-vmem2,size=512M \
-object memory-backend-file,id=vmem3,share=on,mem-path=/tmp/cxl-vmem3,size=512M \
-object memory-backend-file,id=lsa0,share=on,mem-path=/tmp/cxl-lsa0,size=256M \
-object memory-backend-file,id=lsa1,share=on,mem-path=/tmp/cxl-lsa1,size=256M \
-object memory-backend-file,id=lsa2,share=on,mem-path=/tmp/cxl-lsa2,size=256M \
-object memory-backend-file,id=lsa3,share=on,mem-path=/tmp/cxl-lsa3,size=256M \
\
# CXL 호스트 브리지 + CFMWS
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-cxl-fixed-memory-window targets.0=cxl.1,size=4G \
\
# 직접 연결: Root Port 0, 1
-device cxl-rp,port=0,bus=cxl.1,id=rp0,chassis=0,slot=2 \
-device cxl-rp,port=1,bus=cxl.1,id=rp1,chassis=0,slot=3 \
# 스위치 연결: Root Port 2
-device cxl-rp,port=2,bus=cxl.1,id=rp2,chassis=0,slot=4 \
\
# Type3 장치 (직접 연결)
-device cxl-type3,bus=rp0,volatile-memdev=vmem0,\
lsa=lsa0,id=cxl-t3-0 \
-device cxl-type3,bus=rp1,volatile-memdev=vmem1,\
persistent-memdev=pmem1,lsa=lsa1,id=cxl-t3-1 \
\
# CXL 스위치 (USP + 2 DSP)
-device cxl-upstream,bus=rp2,id=cxl-us0 \
-device cxl-downstream,port=0,bus=cxl-us0,id=cxl-ds0,chassis=0,slot=5 \
-device cxl-downstream,port=1,bus=cxl-us0,id=cxl-ds1,chassis=0,slot=6 \
\
# Type3 장치 (스위치 하위)
-device cxl-type3,bus=cxl-ds0,volatile-memdev=vmem2,\
lsa=lsa2,id=cxl-t3-2 \
-device cxl-type3,bus=cxl-ds1,volatile-memdev=vmem3,\
lsa=lsa3,id=cxl-t3-3,num-dc-regions=2 \
\
# 부팅
-kernel /boot/vmlinuz-6.8.0 \
-initrd /boot/initrd.img-6.8.0 \
-append "root=/dev/sda1 console=ttyS0 \
cxl_acpi.dyndbg=+p cxl_core.dyndbg=+p \
cxl_pci.dyndbg=+p cxl_port.dyndbg=+p \
cxl_mem.dyndbg=+p" \
-drive file=guest.qcow2,format=qcow2 \
-nographic
# === 게스트 내부 검증 ===
# CXL 토폴로지 전체 확인
cxl list -BEIMPDRu
# bus0 → root0 → port1,2,3 → endpoint4,5,6,7 → mem0,1,2,3
# 스위치 토폴로지 확인
cxl list -S # switch 목록
# [{"switch":"switch0","depth":1,"nr_dports":2}]
# 2-way 인터리브 리전 생성 (스위치 하위 장치)
cxl create-region -m mem2 -m mem3 -d decoder0.0 -w 2 -g 256 -s 1G
# DC 장치 확인
cxl list -m mem3 -vi | grep dc
# "dc_region_count":2
--enable-cxl 옵션을 확인하세요.
CXL 게스트 커널은 6.6+ (PMU/포이즌), 6.8+ (향상된 리전), 6.10+ (DC 초기)가 권장됩니다.
게스트 내 cxl-cli는 ndctl 패키지 v79+에서 제공됩니다.
성능 특성과 NUMA 노드 연동 심화
CXL 메모리의 실질적 성능은 PCIe 링크 세대, 레인 수, 인터리브 구성, NUMA 거리, 그리고 워크로드의 접근 패턴(순차/랜덤, 읽기/쓰기 비율)에 따라 크게 달라집니다. 실전에서는 HMAT/CDAT 기반 성능 특성을 정확히 파악하고, 워크로드를 프로파일링하여 CXL 메모리 배치 전략을 수립해야 합니다.
# === CXL 성능 프로파일링 실전 가이드 ===
# 1. HMAT/CDAT 기반 성능 특성 확인
# 각 NUMA 노드의 접근 특성 (initiator별)
for node in /sys/devices/system/node/node*; do
name=$(basename $node)
if [ -d "$node/access0/initiators" ]; then
rlat=$(cat $node/access0/initiators/read_latency 2>/dev/null)
wlat=$(cat $node/access0/initiators/write_latency 2>/dev/null)
rbw=$(cat $node/access0/initiators/read_bandwidth 2>/dev/null)
wbw=$(cat $node/access0/initiators/write_bandwidth 2>/dev/null)
echo "$name: R_lat=${rlat}ns W_lat=${wlat}ns R_BW=${rbw}MB/s W_BW=${wbw}MB/s"
fi
done
# node0: R_lat=80ns W_lat=85ns R_BW=204800MB/s W_BW=204800MB/s (DDR5)
# node1: R_lat=160ns W_lat=170ns R_BW=102400MB/s W_BW=102400MB/s (DDR5 원격)
# node2: R_lat=300ns W_lat=350ns R_BW=51200MB/s W_BW=46080MB/s (CXL)
# 2. Intel MLC 상세 지연 측정
# 노드 간 유휴 지연 행렬
mlc --latency_matrix
# 0 1 2
# 0 78.3 158.2 298.5
# 1 158.2 78.3 298.5
# 2 298.5 298.5 - (CXL는 CPU 없음)
# 부하 시 지연 곡선 (대역폭 증가에 따른 지연 변화)
mlc --loaded_latency -d0 -T -b100m
# Inject Latency Bandwidth
# 00000 298.5ns 52.3 GB/s ← 유휴 상태
# 10000 305.2ns 51.8 GB/s
# 50000 342.1ns 48.2 GB/s
# 80000 425.3ns 35.6 GB/s ← 대역폭 포화 시 지연 급증
# 3. STREAM 벤치마크 비교
# DDR5 노드
numactl --cpunodebind=0 --membind=0 ./stream_c.exe
# Copy: 198,456 MB/s
# CXL 노드 (동일 CPU)
numactl --cpunodebind=0 --membind=2 ./stream_c.exe
# Copy: 49,234 MB/s (DDR 대비 ~25%)
# DDR + CXL 인터리브
numactl --cpunodebind=0 --interleave=0,2 ./stream_c.exe
# Copy: 123,890 MB/s (DDR+CXL 합산 ~62%)
# 4. perf c2c로 캐시 라인 공유 분석
perf c2c record -a -- sleep 10
perf c2c report --sort=dso,iaddr
# CXL 메모리의 캐시 라인 false sharing 감지
# 5. bpftrace로 NUMA fault 추적
bpftrace -e '
tracepoint:migrate:mm_migrate_pages {
printf("migrate: src_nid=%d dst_nid=%d nr=%d mode=%s\n",
args->src_nid, args->dst_nid,
args->nr_succeeded, args->mode == 0 ? "sync" : "async");
}'
# migrate: src_nid=2 dst_nid=0 nr=32 mode=async ← CXL→DDR promotion
# migrate: src_nid=0 dst_nid=2 nr=128 mode=async ← DDR→CXL demotion
| 워크로드 유형 | CXL 배치 전략 | 예상 성능 영향 | 권장 인터리브 |
|---|---|---|---|
| LLM 추론 (배치) | KV 캐시 → CXL, 모델 → DDR | 처리량 유지, 배치 8x | 1-way (장치별 독립) |
| LLM 추론 (실시간) | KV 캐시 → DDR, overflow → CXL | P99 지연 2x 이내 | 자동 tiering |
| 벡터 DB (ANN) | 인덱스 전체 → CXL | 검색 지연 +200ns | 2-way, 256B |
| 인메모리 DB | 인덱스 → DDR, 데이터 → CXL | TPS -15~30% | 자동 tiering |
| HPC (MPI) | 통신 버퍼 → DDR, 계산 배열 → CXL | 대역폭 민감 구간 -40% | 4-way, 256B |
| 로그/캐시 서버 | 전체 → CXL (지연 무관) | 용량 10x, 비용 -60% | 1-way |
| Java/JVM 힙 | Old Gen → CXL, Young Gen → DDR | GC 지연 +50% | 자동 tiering |
CXL 커널 테스트 프레임워크
리눅스 커널의 tools/testing/cxl/ 디렉토리에는 CXL 드라이버를 위한
모의(mock) 테스트 인프라가 제공됩니다. 실제 하드웨어 없이 커널 내부의 CXL 코드 경로를
단위 테스트할 수 있으며, QEMU보다 빠르고 가벼운 검증이 가능합니다.
# CXL 단위 테스트 빌드 및 실행
# 커널 설정에 테스트 옵션 활성화
# CONFIG_CXL_REGION_INVALIDATION_TEST=y
# CONFIG_TEST_CXL=m
# cxl_mock 모듈 로드
modprobe cxl_test
# cxl_test: 가상 CXL 토폴로지 생성
# cxl_test: mock host bridge 0 registered
# cxl_test: mock root port 0,1 registered
# cxl_test: mock memdev 0,1,2,3 registered
# ndctl/cxl 테스트 스위트 실행
cd tools/testing/cxl/
make cxl_test
# 또는 ndctl 프로젝트의 통합 테스트
git clone https://github.com/pmem/ndctl.git
cd ndctl
meson setup build
cd build
meson test -C . --suite cxl
# 테스트 항목:
# cxl-topology.sh — 토폴로지 열거 테스트
# cxl-region.sh — 리전 생성/삭제 테스트
# cxl-labels.sh — LSA 레이블 테스트
# cxl-security.sh — 보안 명령 테스트
# cxl-events.sh — 이벤트 로그 테스트
# cxl-poison.sh — 포이즌 관리 테스트
# 테스트 정리
modprobe -r cxl_test
/* tools/testing/cxl/test/cxl.c — CXL 모의 드라이버 */
/* 가상 CXL 토폴로지 정의 */
static struct cxl_mock_topology mock_topo = {
.nr_host_bridges = 2,
.host_bridge = {
[0] = {
.nr_root_ports = 2,
.root_port = {
[0] = { .nr_endpoints = 1 }, /* mem0 */
[1] = { .nr_endpoints = 1 }, /* mem1 */
},
},
[1] = {
.nr_root_ports = 2,
.root_port = {
[0] = { .nr_endpoints = 1 }, /* mem2 */
[1] = { .nr_endpoints = 1 }, /* mem3 */
},
},
},
};
/* 가상 memdev 설정 */
static void mock_memdev_init(struct cxl_dev_state *cxlds,
int id)
{
cxlds->serial = 0xDEADBEEF0000 + id;
cxlds->total_bytes = SZ_256M; /* 256MB 모의 메모리 */
cxlds->volatile_only_bytes = SZ_256M;
cxlds->persistent_only_bytes = 0;
cxlds->lsa_size = SZ_64K;
}
/* 가상 메일박스 명령 처리 */
static int mock_mbox_send(struct cxl_mailbox *mbox,
struct cxl_mbox_cmd *cmd)
{
switch (cmd->opcode) {
case CXL_MBOX_OP_IDENTIFY:
return mock_identify(mbox, cmd);
case CXL_MBOX_OP_GET_HEALTH_INFO:
return mock_health(mbox, cmd);
case CXL_MBOX_OP_GET_POISON:
return mock_get_poison(mbox, cmd);
default:
return -ENOTSUPP;
}
}
CXL 디버깅과 트러블슈팅
CXL 시스템에서 발생하는 문제는 하드웨어(BIOS/펌웨어), 토폴로지(포트/디코더 구성), 드라이버(프로브 실패), 리전(생성/커밋 오류), 성능(예상보다 낮은 대역폭/높은 지연) 등 다양한 계층에서 발생합니다. 체계적인 디버깅 워크플로가 필수적입니다.
| 증상 | 의심 계층 | 진단 명령 | 해결 방법 |
|---|---|---|---|
| CXL 장치 미발견 | BIOS/펌웨어 | lspci -d ::0502 | BIOS에서 CXL 활성화, CEDT 확인 |
| memdev 미등록 | cxl_pci 드라이버 | dmesg | grep cxl_pci | DVSEC 파싱 에러 확인, 커널 버전 확인 |
| 포트 누락 | cxl_port 드라이버 | cxl list -P | 스위치 감지 여부, cxl_port.dyndbg |
| 리전 생성 실패 | 디코더/리전 | cxl list -D | CFMWS 범위, 인터리브 설정 확인 |
| DAX 전환 실패 | DAX/memhotplug | daxctl list | CONFIG_DEV_DAX_KMEM 확인 |
| NUMA 노드 미노출 | memory-tiers | numactl -H | daxctl reconfigure 확인 |
| 성능 저하 | 인터리브/PCIe | mlc --bandwidth_matrix | 인터리브 구성, PCIe 링크 속도 |
| 포이즌 에러 | 메모리 미디어 | cxl list -m mem0 --poison | Clear Poison, 미디어 스캔 |
| Viral 상태 | RAS | dmesg | grep -i viral | 장치 리셋, 리전 재구성 |
# CXL 디버깅 종합 스크립트
#!/bin/bash
set -e
echo "=== 1. PCIe/CXL 장치 감지 ==="
lspci -d ::0502 2>/dev/null || echo "CXL Type3 장치 없음"
lspci -d ::0501 2>/dev/null || echo "CXL Type2 장치 없음"
lspci -d ::0500 2>/dev/null || echo "CXL Type1 장치 없음"
echo ""
echo "=== 2. CXL 커널 모듈 상태 ==="
lsmod | grep -E "^cxl" || echo "CXL 모듈 미로드"
echo ""
echo "=== 3. CXL 장치 목록 ==="
cxl list -BEIMPDRu 2>/dev/null || echo "cxl-cli 미설치 또는 장치 없음"
echo ""
echo "=== 4. CXL 커널 메시지 (최근 50줄) ==="
dmesg | grep -Ei "cxl|cedt|cfmws|hmat" | tail -50
echo ""
echo "=== 5. NUMA 토폴로지 ==="
numactl --hardware 2>/dev/null || echo "numactl 미설치"
echo ""
echo "=== 6. 메모리 티어 ==="
for tier in /sys/devices/virtual/memory_tiering/memory_tier*; do
if [ -d "$tier" ]; then
echo "$(basename $tier): $(cat $tier/nodelist 2>/dev/null)"
fi
done
echo ""
echo "=== 7. DAX 장치 ==="
daxctl list 2>/dev/null || echo "daxctl 미설치"
echo ""
echo "=== 8. PCIe 링크 상태 ==="
for dev in $(lspci -d ::0502 -D 2>/dev/null | awk '{print $1}'); do
echo "--- $dev ---"
lspci -vvv -s $dev 2>/dev/null | grep -E "LnkSta:|LnkCap:|CXL"
done
echo ""
echo "=== 9. 에러 상태 ==="
for mem in /sys/bus/cxl/devices/mem*; do
if [ -d "$mem" ]; then
name=$(basename $mem)
echo "--- $name ---"
cat $mem/firmware_version 2>/dev/null
cat $mem/serial 2>/dev/null
fi
done
echo ""
echo "=== 10. demotion/promotion 통계 ==="
grep -E "pgpromote|pgdemote|numa_hint" /proc/vmstat 2>/dev/null
cxl_acpi.dyndbg=+p cxl_core.dyndbg=+p cxl_pci.dyndbg=+p cxl_port.dyndbg=+p cxl_mem.dyndbg=+p
런타임: echo 'module cxl_core +p' > /sys/kernel/debug/dynamic_debug/control
또한 ftrace의 CXL 이벤트를 활용하면 포이즌, AER, 이벤트 로그를 실시간으로 추적할 수 있습니다.
echo 1 > /sys/kernel/debug/tracing/events/cxl/enable
# CXL 관련 커널 설정 확인 스크립트
#!/bin/bash
CONFIG="/boot/config-$(uname -r)"
echo "=== CXL 커널 설정 확인 ==="
for opt in \
CONFIG_CXL_BUS CONFIG_CXL_PCI CONFIG_CXL_ACPI \
CONFIG_CXL_PMEM CONFIG_CXL_MEM CONFIG_CXL_PORT \
CONFIG_CXL_REGION CONFIG_CXL_PMU \
CONFIG_DEV_DAX CONFIG_DEV_DAX_CXL CONFIG_DEV_DAX_KMEM \
CONFIG_NUMA_BALANCING CONFIG_MEMORY_HOTPLUG \
CONFIG_MEMORY_TIER_DEFAULT_DRAM_PERF_VALUES; do
val=$(grep "^${opt}=" $CONFIG 2>/dev/null || echo "not set")
if echo "$val" | grep -q "=y\|=m"; then
echo "[OK] $opt = $(echo $val | cut -d= -f2)"
else
echo "[!!] $opt : $val"
fi
done
관련 문서
- PCI/PCIe 서브시스템 — CXL의 물리 계층 기반, DVSEC, FLIT 모드
- NUMA — CXL NUMA 노드 통합, numactl 활용, 거리 행렬
- 고급 메모리 관리 — 메모리 티어링, 페이지 마이그레이션, NUMA 밸런싱
- HMM (이기종 메모리 관리) — CXL Type2와 HMM 연동, ZONE_DEVICE
- Intel PCM — CXL 대역폭/지연 모니터링 도구
- GPU 서브시스템 — CXL Type2 GPU 연동, DRM/KMS
- ACPI — CEDT/HMAT/SRAT 테이블, 펌웨어 인터페이스
- VFIO & mdev — CXL 장치 가상화, 패스스루
- CXL 규격서: computeexpresslink.org (CXL 3.1 Specification)
- 커널 문서:
Documentation/driver-api/cxl/(커널 소스 트리) - ndctl/cxl-cli: github.com/pmem/ndctl (cxl-cli 소스)
- QEMU CXL:
docs/system/devices/cxl.rst(QEMU 소스 트리) - 커널 CXL 테스트:
tools/testing/cxl/(cxl_mock 드라이버)