Ryotta's Linux 7.0 MM

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

CMA (Contiguous Memory Allocator)

개요 (Overview)

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 호출 흐름
CMA 자료구조 관계도

빠른 점검 명령

# 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 — CMA 영역 관리자 (`mm/cma.h:39-65`)

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 — 개별 물리 범위 (`mm/cma.h:26-36`)

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을 추적합니다. bitmapcma_activate_area() 이후 할당 상태를 관리합니다. 비트맵에서 1비트 = 해당 페이지가 CMA 할당됨을 나타냅니다.

cma_flags — 상태 플래그 (`mm/cma.h:67-72`)

enum cma_flags {
    CMA_RESERVE_PAGES_ON_ERROR,  // 초기화 실패 시 페이지 반환 안 함
    CMA_ZONES_VALID,             // 존 검증 통과
    CMA_ZONES_INVALID,           // 존 검증 실패 (cross-zone)
    CMA_ACTIVATED,               // cma_activate_area 완료
};

설명: CMA_ACTIVATEDcma_reserve_early()가 동작하는 조건입니다 — 이 비트가 설정되면 초기 할당이 불가능합니다. CMA_ZONES_VALID/INVALID는 CMA 영역이 단일 존에 속하는지 검증한 결과를 캐싱합니다.

struct cma_init_memrange — 초기화 작업 구조체 (`mm/cma.c:311-315`)

struct cma_init_memrange {
    phys_addr_t base;
    phys_addr_t size;
    struct list_head list;
};

설명: cma_declare_contiguous_multi()에서 멀티 레인지 후보를 정렬하여 관리하는 임시 구조체입니다. 최대 CMA_MAX_RANGES(8개)까지 사용됩니다.


핵심 함수

1. `cma_declare_contiguous_nid()` — CMA 영역 예약 (`mm/cma.c:733-751`)

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=truecma_fixed_reserve(): 지정 위치에서 직접 예약 시도

3. fixed=falsecma_alloc_mem(): 64비트 시스템에서는 4GB 위쪽부터 bottom-up 탐색, HIGHMEM 처리

4. cma_init_reserved_mem(): struct cma 초기화

2. `cma_alloc()` / `cma_alloc_frozen()` — 페이지 할당 (`mm/cma.c:943-953, 918-924`)

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 시 재시도

3. `cma_release()` — 페이지 해제 (`mm/cma.c:1012-1032`)

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

4. `cma_activate_area()` — 초기화 완료 (`mm/cma.c:140-200`)

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_pfnbase_pfn과 다르면 → 차이분(이미 예약된 부분)을 비트맵에서 "사용 중"으로 표시

4. 실패 시 cleanup: 비트맵 해제 + Buddy에 페이지 반환

5. `cma_declare_contiguous_multi()` — 멀티 레인지 예약 (`mm/cma.c:528-711`)

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 초기화


호출 흐름

CMA 영역 초기화 흐름

 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)

CMA 할당/해제 흐름

 장치 드라이버 (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 영역 유형 비교

구분단일 레인지멀티 레인지
**초기화 함수**`cma_declare_contiguous_nid()``cma_declare_contiguous_multi()`
**레인지 수**12~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` 이전

관련 문서

  • 00-overview.html — 메모리 관리 전체 개요
  • 01-page_alloc.html — Buddy Allocator (CMA의 하위 할당자)
  • 04-memblock.html — Memblock 할당자 (부팅 초기 CMA 예약 기반)
  • 11-compaction.html — Memory Compaction (CMA 할당 시 페이지 재배치)
  • 13-numa.html — NUMA (CMA의 nid 설정)
  • 20-migrate.html — 페이지 마이그레이션 (CMA의 핵심 메커니즘)
  • 22-memory_hotplug.html — 메모리 핫플러그

  • DMA API를 통한 CMA 사용

    대부분의 드라이버는 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 영역 크기와 위치를 지정할 수 있습니다.

    # 기본 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 vs HugeTLB 비교

    구분CMAHugeTLB
    **평시 활용**movable 페이지가 일반 메모리로 사용 (낭비 없음)예약 즉시 전용 (일반 사용 불가)
    **유연성**높음 — 필요 시 마이그레이션으로 확보낮음 — 미리 확보된 것만 사용
    **실패 원인**unmovable 페이지 침투, 마이그레이션 타임아웃예약 부족 시 즉시 실패
    **적합한 용도**DMA, 디스플레이 프레임버퍼, GPU데이터베이스, 대용량 캐시
    **커널 파라미터**`cma=256M``hugepagesz=2M hugepages=128`

    CMA 실패 원인과 대응

    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과의 차이

    minzkn.com 메모리 관리 개요 페이지의 "특수 메모리" 섹션에서 CMA를 간략히 다룹니다. minzkn은 CMA를 "DMA 디바이스를 위한 대용량 연속 물리 메모리 할당"으로 설명하며, 다음 핵심 포인트를 강조합니다:

  • CMA 영역은 평시에 movable 페이지로 활용되어 메모리 낭비가 없음
  • dma_alloc_coherent()를 통한 자동 연결
  • IOMMU 없는 디바이스의 scatter-gather 불가 시나리오에서 필수
  • CMA는 HugeTLB와 달리 평시 일반 사용 가능 (유연성)
  • 우리 문서는 이보다 더 상세하게 멀티 레인지 지원, 할당/해제 내부 흐름, 존 검증 로직, cma_reserve_early 초기 할당 메커니즘을 분석하고 있습니다.