카메라, 비디오 코덱, 디스플레이 같은 DMA 디바이스는 종종 물리적으로 연속된(contiguous) 대용량 버퍼가 필요하다. CPU와 달리 이런 디바이스는 MMU로 흩어진 페이지를 하나의 연속 주소 공간처럼 매핑하는 게 불가능한 경우가 많기 때문이다. 문제는 시스템이 오래 켜져 있을수록 메모리가 파편화돼서, 필요한 시점에 동적으로 큰 연속 공간을 확보하려고 하면 실패하기 쉽다는 점이다. 그렇다고 부팅 시점에 그 공간을 통째로 예약(reserve)해버리면, 디바이스가 실제로 안 쓰는 동안 그 메모리는 그냥 낭비된다. CMA(Contiguous Memory Allocator)는 이 둘을 절충한다 — 부팅 시 영역은 미리 확보해두되, 평소엔 그 공간을 페이지 캐시 같은 movable 페이지로 활용하다가 디바이스가 요청하면 그 페이지들을 몰아내고 넘겨주는 방식이다. 이 글에서는 CMA가 실제로 어떻게 동작하는지, 설정 방법과 우선순위, 그리고 현재 상태를 확인하는 방법을 정리한다.
CMA는 어떻게 낭비 없이 예약 공간을 유지하는가
CMA로 예약된 영역은 MIGRATE_CMA라는 별도의 pageblock 타입으로 관리된다. 여기에는 딱 한 가지 규칙이 있다 — movable로 분류된 할당(페이지 캐시, 익명 페이지 등)만 이 영역을 쓸 수 있고, unmovable/reclaimable 할당은 절대 들어올 수 없다. 커널 문서가 이 규칙의 이유를 명확히 설명한다.
if an unmovable kernel allocation landed in a CMA region, the region could never be fully reclaimed
즉 unmovable 페이지는 다른 곳으로 옮길 방법이 없으니, 이런 페이지가 CMA 영역에 섞여 들어가면 나중에 디바이스가 그 공간을 통째로 회수해야 할 때 실패한다. movable 페이지만 허용하면 언제든 다른 곳으로 마이그레이션해서 영역을 비울 수 있다는 게 보장된다.
드라이버가 dma_alloc_coherent()를 호출하면 내부적으로 다음 흐름을 거친다.
dma_alloc_coherent(dev, size, ...)
-> dma_alloc_from_contiguous()
-> cma_alloc(cma, count, align, no_warn)
-> alloc_contig_range(start, end, MIGRATE_CMA, ...)
1. isolate : 요청 범위를 격리해 새 할당이 못 들어오게 막는다
2. migrate : 범위 안의 movable 페이지를 전부 다른 곳으로 이동시킨다
(메모리 compaction과 동일한 마이그레이션 코드를 재사용)
3. drain : per-CPU 페이지 리스트를 정리한다
4. return : 비워진 연속 페이지를 호출자에게 반환한다
요청한 범위 안에 마이그레이션할 수 없는 페이지가 있어 비우기에 실패하면, CMA는 비트맵으로 추적하던 다른 청크를 다시 시도한다. 그래도 전부 실패하면 결국 할당 자체가 실패로 끝난다.
드라이버에서 CMA 사용하기
대부분의 드라이버는 CMA를 직접 다루지 않고 표준 DMA API를 쓰면 충분하다.
#include <linux/dma-mapping.h>
dma_addr_t dma_handle;
void *vaddr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* ... 사용 ... */
dma_free_coherent(dev, size, vaddr, dma_handle);
CMA 영역을 직접 다뤄야 하는 드문 경우(디바이스 전용 CMA 풀 등)에는 cma_alloc()/cma_release()를 직접 쓸 수도 있다.
#include <linux/cma.h>
struct page *page = cma_alloc(cma, count, align, no_warn);
/* ... 사용 ... */
cma_release(cma, page, count);
CMA 영역 크기 설정과 우선순위
CMA를 쓰려면 커널이 CONFIG_CMA, DMA 연동까지 쓰려면 CONFIG_DMA_CMA가 켜져 있어야 한다. 기본(default) CMA 풀의 크기는 세 가지 방법으로 정할 수 있는데, 우선순위는 다음과 같다.
| 우선순위 | 설정 위치 | 비고 |
|---|---|---|
| 1 (최우선) | 커널 커맨드라인 cma=nn[MG]@[start[MG][-end[MG]]] | 지정되면 디바이스트리의 기본 CMA 풀 노드를 무시한다 |
| 2 | 디바이스트리 reserved-memory 노드 (linux,cma-default) | 커맨드라인이 없을 때 적용 |
| 3 (기본값) | 커널 설정 CONFIG_CMA_SIZE_MBYTES / CONFIG_CMA_SIZE_PERCENTAGE | 둘 다 없을 때의 폴백 |
주의할 점은 이 우선순위가 “기본(default) CMA 풀”에만 적용된다는 것이다. 특정 디바이스 노드에 memory-region 속성으로 지정한 전용 CMA 영역은 이름이 다른 별개의 풀이라 cma= 커맨드라인의 영향을 받지 않는다.
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
camera_mem: camera-buffer {
compatible = "shared-dma-pool";
reusable;
reg = <0 0x78000000 0 0x8000000>; /* 128MB */
};
};
camera@0 {
memory-region = <&camera_mem>;
};
};
현재 CMA 상태 확인하기
$ grep -i cma /proc/meminfo
CmaTotal: 262144 kB
CmaFree: 245760 kB
$ grep cma /proc/vmstat
cma_alloc_success 42
cma_alloc_fail 0
CmaTotal은 전체 예약 크기, CmaFree는 현재 디바이스에 할당되지 않고 남아 있는(즉 movable 용도로 쓰일 수 있는) 페이지 양이다. cma_alloc_success/cma_alloc_fail(커널 5.19 이후)은 지금까지 CMA 할당이 성공·실패한 누적 횟수라 실패가 잦은지 추이를 보기에 좋다.
더 자세한 상태는 debugfs에서 볼 수 있다. 디렉터리 이름은 CMA 영역이 등록될 때 붙는 이름을 따르므로(기본 풀은 흔히 reserved), 실제 이름은 ls /sys/kernel/debug/cma/로 먼저 확인한다.
$ ls /sys/kernel/debug/cma/
reserved/
$ ls /sys/kernel/debug/cma/reserved/
alloc base_pfn bitmap count free maxchunk order_per_bit used
$ cat /sys/kernel/debug/cma/reserved/count # 영역 내 총 페이지 수
$ cat /sys/kernel/debug/cma/reserved/used # 현재 디바이스에 할당된 페이지 수
$ cat /sys/kernel/debug/cma/reserved/maxchunk # 가장 큰 연속 여유 청크(페이지 단위)
# 4MB(1024페이지) 테스트 할당 — 실제로 CMA에서 페이지를 빼내 마이그레이션이 도는지 확인할 때 유용
$ echo 1024 > /sys/kernel/debug/cma/reserved/alloc
주의사항
CmaFree가 작다고 시스템 전체 메모리가 부족한 건 아니다.CmaTotal/CmaFree는 이미MemTotal/MemFree에 포함된 값이라, CMA 영역이 페이지 캐시로 꽉 차 있어도 그 메모리는 손실된 게 아니라 movable 용도로 정상 사용 중인 것이다.- 큰 연속 할당을 요청했는데 그 범위 안에 마이그레이션 불가능한 페이지(예: 특정 드라이버가 pin해둔 페이지, mlock된 페이지)가 끼어 있으면
alloc_contig_range가 실패하고cma_alloc_fail이 올라간다. 실패가 잦다면 CMA 영역 크기 자체보다 무엇이 페이지를 pin하고 있는지부터 의심해야 한다. - CMA 영역을 너무 작게 잡으면 대형 DMA 버퍼 할당이 실패하고, 너무 크게 잡으면 평소 movable 메모리(페이지 캐시 등) 여유가 줄어드는 트레이드오프가 있다. 실제 워크로드에서 필요한 최대 버퍼 크기를 기준으로 잡는 게 안전하다.
- 디바이스 전용 CMA 풀(
memory-region으로 지정한 것)은 커맨드라인cma=의 영향을 받지 않는 별개의 풀이므로, 여러 CMA 영역이 섞여 있는 시스템에서는/sys/kernel/debug/cma/아래 디렉터리별로 따로 확인해야 한다.
마무리
CMA는 “예약은 미리 하되 평소엔 놀리지 않는다”는 아이디어로 신뢰성과 메모리 낭비 사이의 트레이드오프를 없앤다. movable 페이지만 CMA 영역에 들어올 수 있다는 규칙 하나로 디바이스가 언제든 그 공간을 통째로 회수할 수 있다는 걸 보장하는 구조가 핵심이다. 실제 운영 중에는 /proc/meminfo의 CmaFree와 /proc/vmstat의 cma_alloc_fail을 같이 보면서, 할당 실패가 잦아지면 영역 크기보다 먼저 페이지 pin 여부를 의심해보는 순서로 접근하면 된다.