CMA는 커널 부팅 초기에 물리 메모리에서 연속된 영역을 예약하고, 런타임에 해당 영역의 페이지를 할당/해제하는 서브시스템입니다. 주로 DMA, 디스플레이 프레임버퍼, GPU, 비디오 디코더 같은 장치에서 하드웨어 제약으로 인해 연속 물리 메모리가 필요할 때 사용됩니다. CMA 영역은 평소에는 Buddy Allocator에 반환되어 일반 메모리로 활용되지만, 장치가 할당을 요청하면 해당 영역의 사용자 페이지를 재배치(migrating)하여 연속 블록을 확보합니다.
Linux 7.0에서 CMA는 멀티 레인지(multi-range) 지원을 통해 하나의 CMA 영역이 최대 8개의 불연속 물리 범위로 구성될 수 있습니다. 이는 메모리 홀(memory hole)이 있는 시스템에서 단일 연속 영역 확보가 어려운 경우에 유용합니다.
소스 파일:
mm/cma.c ← 핵심 구현 (할당/해제/초기화)
mm/cma.h ← 내부 구조체 (cma, cma_memrange)
include/linux/cma.h ← 외부 API 헤더
include/trace/events/cma.h ← tracepoint 정의
# CMA 영역 전체 현황 확인 (이름, 크기, 사용량)
cat /proc/meminfo | grep Cma
# CMA 영역별 상세 정보
cat /sys/kernel/debug/cma/cma-0/used_cma # 사용 중인 페이지 수
cat /sys/kernel/debug/cma/cma-0/count # 전체 페이지 수
# Buddy Allocator에서 CMA 페이지 블록 분포 확인
cat /proc/buddyinfo | grep MIGRATE_CMA
# CMA 영역의 pageblock 마이그레이션 타입 확인
cat /proc/pagetypeinfo | grep CMA
# CMA 할당/해제 tracepoint 확인
sudo perf stat -e cma:cma_alloc_start,cma:cma_alloc_finish,cma:cma_release -- sleep 5
# CMA 영역 디버그 정보 (CONFIG_CMA_DEBUGFS)
sudo ls /sys/kernel/debug/cma/
# dmesg에서 CMA 초기화 메시지 확인
dmesg | grep -i "cma"
struct cma {
unsigned long count; // 총 페이지 수
unsigned long available_count; // 사용 가능한 페이지 수
unsigned int order_per_bit; // 비트맵 1비트당 페이지 수 (2^order)
spinlock_t lock; // 비트맵 동시성 제어
struct mutex alloc_mutex; // 할당 시 직렬화
char name[CMA_MAX_NAME]; // 영역 이름 ("cma0", "linux,contiguous-region")
int nranges; // 실제 레인지 수 (1~8)
struct cma_memrange ranges[CMA_MAX_RANGES]; // 멀티 레인지 배열
unsigned long flags; // CMA_ACTIVATED 등 플래그
int nid; // NUMA 노드 ID
};
설명: count는 전체 영역 크기(페이지 수), available_count는 현재 Buddy에 반환되어 사용 가능한 페이지 수입니다. order_per_bit은 비트맵 해상도를 결정하며, 보통 0이면 비트맵 1비트 = 1페이지입니다. nranges는 멀티 레인지 지원으로, 하나의 CMA 영역이 여러 불연속 물리 범위를 가질 때 사용됩니다.
struct cma_memrange {
unsigned long base_pfn; // 범위 시작 PFN
unsigned long count; // 범위 내 페이지 수
union {
unsigned long early_pfn; // cma_reserve_early()에서 사용
unsigned long *bitmap; // 활성화 후 할당 비트맵
};
};
설명: early_pfn은 부팅 초기 cma_reserve_early() 호출 시 아직 비트맵이 할당되기 전에 예약된 PFN을 추적합니다. bitmap은 cma_activate_area() 이후 할당 상태를 관리합니다. 비트맵에서 1비트 = 해당 페이지가 CMA 할당됨을 나타냅니다.
enum cma_flags {
CMA_RESERVE_PAGES_ON_ERROR, // 초기화 실패 시 페이지 반환 안 함
CMA_ZONES_VALID, // 존 검증 통과
CMA_ZONES_INVALID, // 존 검증 실패 (cross-zone)
CMA_ACTIVATED, // cma_activate_area 완료
};
설명: CMA_ACTIVATED는 cma_reserve_early()가 동작하는 조건입니다 — 이 비트가 설정되면 초기 할당이 불가능합니다. CMA_ZONES_VALID/INVALID는 CMA 영역이 단일 존에 속하는지 검증한 결과를 캐싱합니다.
struct cma_init_memrange {
phys_addr_t base;
phys_addr_t size;
struct list_head list;
};
설명: cma_declare_contiguous_multi()에서 멀티 레인지 후보를 정렬하여 관리하는 임시 구조체입니다. 최대 CMA_MAX_RANGES(8개)까지 사용됩니다.
int __init cma_declare_contiguous_nid(phys_addr_t base,
phys_addr_t size, phys_addr_t limit,
phys_addr_t alignment, unsigned int order_per_bit,
bool fixed, const char *name, struct cma **res_cma,
int nid)
역할: 부팅 초기에 memblock에서 CMA 영역을 예약합니다. base=0이면 임의 위치, fixed=true면 정확한 주소에 예약합니다.
분기 로직:
1. __cma_declare_contiguous_nid() 호출 (내부 구현)
2. fixed=true → cma_fixed_reserve(): 지정 위치에서 직접 예약 시도
3. fixed=false → cma_alloc_mem(): 64비트 시스템에서는 4GB 위쪽부터 bottom-up 탐색, HIGHMEM 처리
4. cma_init_reserved_mem(): struct cma 초기화
struct page *cma_alloc(struct cma *cma, unsigned long count,
unsigned int align, bool no_warn)
struct page *cma_alloc_frozen(struct cma *cma, unsigned long count,
unsigned int align, bool no_warn)
역할: CMA 영역에서 연속 페이지를 할당합니다. cma_alloc()은 refcount 설정까지, cma_alloc_frozen()은 refcount 없이 반환합니다.
분기 로직 (cma_range_alloc 내부, mm/cma.c:780-858):
1. bitmap_find_next_zero_area_off(): 비트맵에서 연속 빈 영역 탐색
2. page_range_contiguous(): 물리적으로 연속인지 재검증
3. bitmap_set(): 해당 영역을 "사용 중"으로 표시
4. alloc_contig_frozen_range(): Buddy에서 실제 페이지를 동결(freeze)하고 할당
5. 할당 실패 시 cma_clear_bitmap()으로 되돌리고 -EBUSY 시 재시도
bool cma_release(struct cma *cma, const struct page *pages,
unsigned long count)
역할: cma_alloc()으로 할당된 페이지를 해제하고 Buddy에 반환합니다.
분기 로직:
1. find_cma_memrange(): 해당 페이지가 어느 레인지에 속하는지 탐색
2. put_page_testzero(): 모든 페이지의 refcount를 0으로 감소
3. __cma_release_frozen(): free_contig_frozen_range()로 Buddy 반환 + cma_clear_bitmap()
static void __init cma_activate_area(struct cma *cma)
역할: 부팅 시 core_initcall에서 호출되어 비트맵 할당, 존 검증, init_cma_reserved_pageblock() 호출로 pageblock을 CMA 전용으로 설정합니다.
분기 로직:
1. 각 레인지별 bitmap_zalloc()으로 비트맵 할당
2. cma_validate_zones(): CMA 영역이 단일 존에 속하는지 검증
3. early_pfn이 base_pfn과 다르면 → 차이분(이미 예약된 부분)을 비트맵에서 "사용 중"으로 표시
4. 실패 시 cleanup: 비트맵 해제 + Buddy에 페이지 반환
int __init cma_declare_contiguous_multi(phys_addr_t total_size,
phys_addr_t align, unsigned int order_per_bit,
const char *name, struct cma **res_cma, int nid)
역할: 단일 연속 영역 확보에 실패하면 4GB 위쪽 큰 메모리 블록들을 수집하여 멀티 레인지 CMA를 구성합니다.
분기 로직:
1. 먼저 __cma_declare_contiguous_nid()로 단일 레인지 시도
2. -ENOSPC 외 실패 → 종료
3. for_each_free_mem_range()로 4GB 위 빈 범위 수집
4. 크기순 정렬 후 CMA_MAX_RANGES개 선택
5. 주소순 재정렬 → 순차적으로 memblock_reserve() + cmr 초기화
firmware/reserved-memory
│
▼
cma_declare_contiguous_nid() ← arch 코드에서 호출
│
├── __cma_declare_contiguous_nid()
│ ├── cma_fixed_reserve() 또는 cma_alloc_mem()
│ │ └── memblock_alloc_range_nid()
│ └── cma_init_reserved_mem()
│ └── cma_new_area()
│
▼ (core_initcall)
cma_init_reserved_areas()
│
└── cma_activate_area() ← 비트맵 + 존 검증
├── bitmap_zalloc()
├── cma_validate_zones()
├── init_cma_reserved_pageblock()
└── set_bit(CMA_ACTIVATED)
장치 드라이버 (DMA, GPU, etc.)
│
▼
cma_alloc(cma, count, align, no_warn)
│
├── cma_alloc_frozen()
│ └── __cma_alloc_frozen()
│ └── cma_range_alloc() ← 레인지별 반복
│ ├── bitmap_find_next_zero_area_off()
│ ├── page_range_contiguous()
│ ├── bitmap_set()
│ └── alloc_contig_frozen_range() ← Buddy에서 동결
│ └── isolate_migratepages_block() 등
│
▼ 사용 완료
cma_release(cma, pages, count)
│
├── find_cma_memrange()
├── put_page_testzero() x count
└── __cma_release_frozen()
├── free_contig_frozen_range() ← Buddy에 반환
└── cma_clear_bitmap()
| 구분 | 단일 레인지 | 멀티 레인지 |
|---|---|---|
| **초기화 함수** | `cma_declare_contiguous_nid()` | `cma_declare_contiguous_multi()` |
| **레인지 수** | 1 | 2~8 (`CMA_MAX_RANGES`) |
| **사용 시나리오** | 메모리 홀 없는 시스템 | 4GB 위 large block 분산 시 |
| **비트맵** | 레인지당 1개 | 레인지별 독립 비트맵 |
| **할당 탐색** | 단일 비트맵 순회 | 레인지 순서대로 탐색 |
| **대표 사용처** | 기본 DMA 영역 | hugetlb CMA, 대용량 디스플레이 |
| 경로 | 함수 | 특징 |
|---|---|---|
| **일반 할당** | `cma_alloc()` | refcount 설정, 드라이버용 |
| **Frozen 할당** | `cma_alloc_frozen()` | refcount 없이 반환 |
| **Frozen Compound** | `cma_alloc_frozen_compound()` | `__GFP_COMP` 포함, huge page용 |
| **초기 할당** | `cma_reserve_early()` | 부팅 초기, 비트맵 없음, 단일 스레드 |
| 단계 | 함수 | 시점 |
|---|---|---|
| **메모리 예약** | `cma_declare_contiguous_nid()` | arch 초기화 (memblock 활성화 후) |
| **비트맵/검증** | `cma_activate_area()` | `core_initcall` (slab 활성화 후) |
| **초기 할당** | `cma_reserve_early()` | `CMA_ACTIVATED` 이전 |
대부분의 드라이버는 cma_alloc()을 직접 호출하지 않고 DMA API를 통해 간접적으로 사용합니다.
/* 드라이버에서 DMA 버퍼 할당 — CMA를 통해 자동 연속 확보 */
void *vaddr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* 반환 시 */
dma_free_coherent(dev, size, vaddr, dma_handle);
dma_alloc_coherent()는 CONFIG_DMA_CMA가 활성화되면 내부적으로 CMA 영역에서 연속 페이지를 할당합니다. 드라이버 개발자는 CMA 존재 여부를 알 필요 없이 DMA API만 사용하면 됩니다.
커널 부트 파라미터로 CMA 영역 크기와 위치를 지정할 수 있습니다.
# 기본 CMA 영역 256MB 설정
cma=256M
# 특정 물리 주소 범위에 256MB CMA 설정
cma=256M@0-4G
# Device Tree에서 CMA 설정 (임베디드)
# reserved-memory {
# linux,cma {
# compatible = "shared-dma-pool";
# reusable;
# size = <0x10000000>; /* 256MB */
# };
# };
| 구분 | CMA | HugeTLB |
|---|---|---|
| **평시 활용** | movable 페이지가 일반 메모리로 사용 (낭비 없음) | 예약 즉시 전용 (일반 사용 불가) |
| **유연성** | 높음 — 필요 시 마이그레이션으로 확보 | 낮음 — 미리 확보된 것만 사용 |
| **실패 원인** | unmovable 페이지 침투, 마이그레이션 타임아웃 | 예약 부족 시 즉시 실패 |
| **적합한 용도** | DMA, 디스플레이 프레임버퍼, GPU | 데이터베이스, 대용량 캐시 |
| **커널 파라미터** | `cma=256M` | `hugepagesz=2M hugepages=128` |
CMA 할당이 실패하는 대표적인 원인은 다음과 같습니다:
1. unmovable 페이지 침투: slab 할당자, 페이지 테이블 등 unmovable 페이지가 CMA 영역에 위치하면 마이그레이션이 불가능합니다. 사전에 MIGRATE_CMA 타입으로 분리하는 것이 중요합니다.
2. 마이그레이션 타임아웃: 대량의 movable 페이지를 이동해야 할 때 시간 초과가 발생할 수 있습니다.
3. 물리적 연속성 부족: 멀티 레인지 구성 시에도 각 레인지 내부에서 연속 영역을 확보하지 못할 수 있습니다.
# CMA 영역에 unmovable 페이지 침투 확인
cat /proc/pagetypeinfo | grep -A2 "MIGRATE_CMA"
# CMA 실패 시 dmesg 확인
dmesg | grep -i "cma"
minzkn.com 메모리 관리 개요 페이지의 "특수 메모리" 섹션에서 CMA를 간략히 다룹니다. minzkn은 CMA를 "DMA 디바이스를 위한 대용량 연속 물리 메모리 할당"으로 설명하며, 다음 핵심 포인트를 강조합니다:
dma_alloc_coherent()를 통한 자동 연결우리 문서는 이보다 더 상세하게 멀티 레인지 지원, 할당/해제 내부 흐름, 존 검증 로직, cma_reserve_early 초기 할당 메커니즘을 분석하고 있습니다.