Ryotta's Linux 7.0 MM

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

# DMA Pool 🔌

관련 소스: mm/dmapool.c, include/linux/dmapool.h, include/linux/dma-mapping.h

개요 (Overview)

DMA Pool은 작고 고정된 크기의 DMA-able(일관성 있는, coherent) 메모리 블록을 효율적으로 할당하기 위한 풀 할당자입니다. 드라이버가 반복적으로 작은 DMA 버퍼를 필요로 할 때마다 전체 페이지 단위로 dma_alloc_coherent()를 호출하는 것은 비효율적이므로, DMA Pool은 페이지를 한 번 할당한 후 미리 잘라놓은 블록으로 재사용합니다.

내부적으로 dma_pool 구조체가 할당된 페이지들의 이중 연결 리스트(page_list)를 관리하며, 각 페이지는 boundary 경계에 따라 블록으로 분할됩니다. 해제된 블록은 단일 연결 리스트(next_block)로 추적되며, 사용 중인 블록은 별도로 추적하지 않고 nr_active 카운트로 관리합니다.

// mm/dmapool.c
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/slab.h>

빠른 점검 명령

# DMA pool sysfs 정보 확인 (장치별)
cat /sys/bus/platform/devices/*/dma_pool/pools 2>/dev/null

# 모든 DMA pool 정보 출력
for f in /sys/bus/*/devices/*/dma_pool/pools; do echo "=== $f ==="; cat "$f" 2>/dev/null; done

# DMA 할당 실패 확인
dmesg | grep -i "dma" | grep -i "error\|fail"

# IOMMU DMA 관련 로그 확인
dmesg | grep -i "swiotlb\|dma-mapping"

# DMA 할당 통계 (DMA API debug 활성화 시)
cat /sys/kernel/debug/dma/dma_bufstats 2>/dev/null

# 커널 DMA 관련 설정 확인
cat /proc/iomem | grep -i "dma\|swiotlb"

# 특정 드라이버 DMA pool 사용 확인 (예: xHCI)
cat /sys/bus/pci/devices/*/dma_pool/pools 2>/dev/null

# DMA coherent 할당량 확인
cat /proc/meminfo | grep -i "DMA"

# DMA API debug 활성화 확인
grep -r "DMA_API_DEBUG" /boot/config-* 2>/dev/null

# DMA 장치 마스크 확인
cat /sys/bus/pci/devices/*/dma_mask 2>/dev/null

핵심 자료구조

struct dma_pool (풀 관리 구조체)

// mm/dmapool.c:48-62
struct dma_pool {          /* DMA 풀 메타데이터 */
    struct list_head page_list;   /* 할당된 dma_page 목록 (이중 연결) */
    spinlock_t lock;              /* 동시성 보호용 스핀락 */
    struct dma_block *next_block; /* 다음 사용 가능한 블록 포인터 */
    size_t nr_blocks;             /* 총 블록 수 */
    size_t nr_active;             /* 사용 중인 블록 수 */
    size_t nr_pages;              /* 할당된 페이지 수 */
    struct device *dev;           /* 이 풀이 속한 장치 */
    unsigned int size;            /* 각 블록의 크기 (alignment 적용 후) */
    unsigned int allocation;      /* 한 번에 할당하는 메모리 크기 (max(size, PAGE_SIZE)) */
    unsigned int boundary;        /* 블록이 경계를 넘지 않도록 하는 값 (2의 거듭제곱) */
    int node;                     /* NUMA 노드 번호 */
    char name[32];                /* 풀 이름 (진단용) */
    struct list_head pools;       /* dev->dma_pools 목록에 연결되는 노드 */
};
  • page_list: DMA Pool이 관리하는 모든 페이지의 리스트. 각 페이지는 최소 PAGE_SIZE 크기로 dma_alloc_coherent()로 할당됨.
  • next_block: 해제된 블록의 단일 연결 리스트 헤드. free 블록이 없으면 NULL.
  • boundary: 특정 장치의 DMA 전송 시 경계 제약. 예: 4KB 경계를 지정하면 블록이 4KB 경계를 넘지 않음.
  • allocation: max(size, PAGE_SIZE). 한 번에 할당하는 메모리 양.
  • struct dma_block (개별 블록 헤더)

    // mm/dmapool.c:43-46
    struct dma_block {
        struct dma_block *next_block; /* 다음 free 블록을 가리키는 포인터 */
        dma_addr_t dma;               /* 이 블록의 DMA 물리 주소 */
    };

    각 블록의 시작 부분에 오버헤드로 존재. 할당 시 이 구조체 크기만큼 size가 증가하여 실제 사용 가능 영역은 block + sizeof(struct dma_block) 부터 시작.

    struct dma_page (페이지 관리 구조체)

    // mm/dmapool.c:64-68
    struct dma_page {            /* 'allocation' 바이트짜리 캐시 가능한 헤더 */
        struct list_head page_list; /* pool->page_list에 연결되는 노드 */
        void *vaddr;                /* 코어 CPU 가상 주소 */
        dma_addr_t dma;             /* DMA 주소 */
    };

    실제 DMA 메모리의 관리 헤더. dma_alloc_coherent()로 할당된 메모리의 vaddr과 dma 주소를 보관.

    DMA Pool 자료구조 관계도

    핵심 함수

    1. dma_pool_create_node() — 풀 생성

    // mm/dmapool.c:226-301
    struct dma_pool *dma_pool_create_node(const char *name, struct device *dev,
            size_t size, size_t align, size_t boundary, int node)

    역할: 지정된 크기의 DMA coherent 메모리 풀을 생성합니다.

    분기 로직:

  • dev == NULL → NULL 반환
  • align이 0이면 1로 설정, 2의 거듭제곱이 아니면 NULL 반환
  • size == 0 또는 size > INT_MAX → NULL 반환
  • size < sizeof(struct dma_block)sizeof(struct dma_block)으로 증가 (오버헤드 확보)
  • size = ALIGN(size, align)allocation = max(size, PAGE_SIZE)
  • boundary == 0boundary = allocation (경계 제약 없음)
  • boundary < size 또는 비-2의 거듭제곱 → NULL 반환
  • 장치의 첫 번째 풀 생성 시 device_create_file()로 sysfs pools 파일 생성
  • 2. dma_pool_alloc() — 블록 할당

    // mm/dmapool.c:407-441
    void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags,
                         dma_addr_t *handle)

    역할: 풀에서 사용 가능한 DMA 블록을 할당합니다.

    분기 로직:

    1. pool->lock 획득 후 pool_block_pop()으로 free 블록 시도

    2. free 블록이 없으면:

    - pool->lock 해제 (스핀락 하에서 sleep 불가)

    - pool_alloc_page()로 새 페이지 할당 (dma_alloc_coherent())

    - pool->lock 재획득 후 pool_initialise_page()로 페이지를 블록으로 분할

    - 다시 pool_block_pop()으로 블록 할당

    3. *handle = block->dma (DMA 주소 반환)

    4. DMAPOOL_DEBUG 활성화 시 pool_check_block()으로 품질 검증

    3. dma_pool_free() — 블록 해제

    // mm/dmapool.c:453-465
    void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t dma)

    역할: 할당된 블록을 풀에 반환합니다.

    분기 로직:

    1. pool->lock 획득

    2. pool_block_err()로 오류 검증 (잘못된 DMA 주소, 이미 해제된 블록 확인)

    3. 오류 없으면 pool_block_push()로 free 리스트에 추가, nr_active--

    4. DMAPOOL_DEBUG 미활성화 시 want_init_on_free()이면 0으로 초기화

    4. dma_pool_destroy() — 풀 파괴

    // mm/dmapool.c:363-395
    void dma_pool_destroy(struct dma_pool *pool)

    역할: 풀을 완전히 해제합니다.

    분기 로직:

    1. pool == NULL → 즉시 반환

    2. pools_lock 하에서 dev->dma_pools에서 제거

    3. 장치에 더 이상 풀이 없으면 sysfs pools 파일 제거

    4. nr_active > 0이면 busy 경고 출력, 실제 DMA 메모리 해제는 건너뜀

    5. 모든 dma_page에 대해 dma_free_coherent() 호출 후 kfree

    6. dma_pool 자체 kfree

    5. pool_initialise_page() — 페이지 분할

    // mm/dmapool.c:303-335
    static void pool_initialise_page(struct dma_pool *pool, struct dma_page *page)

    역할: 할당된 페이지를 boundary 경계를 고려하여 블록 단위로 분할합니다.

    분기 로직:

  • offset + size > next_boundary → boundary 경계를 건너뜀 (블록 배치 안 함)
  • 그 외: 블록을 연속으로 배치하고 next_block 리스트에 연결
  • nr_blocks 증가
  • DMA Pool 호출 흐름

    호출 흐름

    드라이버
      │
      ├─ dma_pool_create(name, dev, size, align, boundary)
      │    └─ dma_pool_create_node(..., NUMA_NO_NODE)
      │         ├─ kzalloc_node()              ← dma_pool 구조체 할당
      │         ├─ INIT_LIST_HEAD(page_list)
      │         ├─ list_add(pools, dev->dma_pools)
      │         └─ device_create_file()        ← sysfs pools 파일 생성
      │
      ├─ dma_pool_alloc(pool, GFP_KERNEL, &handle)
      │    ├─ spin_lock_irqsave(pool->lock)
      │    ├─ pool_block_pop(pool)             ← free 블록 있으면 반환
      │    │    └─ (없으면)
      │    ├─ spin_unlock_irqrestore(pool->lock)
      │    ├─ pool_alloc_page(pool, mem_flags)
      │    │    ├─ kmalloc_node()              ← dma_page 구조체 할당
      │    │    └─ dma_alloc_coherent()        ← 실제 DMA 메모리 할당
      │    ├─ spin_lock_irqsave(pool->lock)
      │    ├─ pool_initialise_page(pool, page) ← 페이지를 블록으로 분할
      │    │    └─ 반복: block = page->vaddr + offset
      │    ├─ pool_block_pop(pool)             ← 새 블록 할당
      │    └─ spin_unlock_irqrestore(pool->lock)
      │
      ├─ dma_pool_free(pool, vaddr, dma)
      │    ├─ spin_lock_irqsave(pool->lock)
      │    ├─ pool_block_err(pool, vaddr, dma) ← 오류 검증
      │    ├─ pool_block_push(pool, block, dma) ← free 리스트에 추가
      │    └─ spin_unlock_irqrestore(pool->lock)
      │
      └─ dma_pool_destroy(pool)
           ├─ list_del(pools, dev->dma_pools)
           ├─ device_remove_file()             ← sysfs pools 파일 제거
           ├─ (busy가 아니면) dma_free_coherent() 반복
           ├─ kfree(page) 반복
           └─ kfree(pool)

    조건별 비교

    DMA Pool API 비교

    APINUMA 지원관리 방식주요 사용처
    `dma_pool_create()`❌ (NUMA_NO_NODE)수동 생성/파괴일반 드라이버
    `dma_pool_create_node()`✅ (node 파라미터)수동 생성/파괴NUMA 인식 드라이버
    `dmam_pool_create()`자동 해제 (devres)리소스 관리가 필요한 드라이버

    DMA 메모리 할당 방식 비교

    방식크기 단위오버헤드재사용사용 시나리오
    `dma_alloc_coherent()`페이지 단위없음없음대형 DMA 버퍼
    DMA Pool고정 크기 블록`sizeof(dma_block)`작고 반복적인 DMA 할당
    `dma_alloc_pages()`페이지 단위없음없음SG DMA
    vmalloc임의 크기페이지 테이블없음커널 내부 사용

    boundary 설정 동작 비교

    boundary 값블록 배치 규칙예시
    0 (미지정)allocation 단위로 자유롭게 배치기본 동작
    40964KB 경계를 넘지 않음xHCI HCI command ring
    6553664KB 경계를 넘지 않음고성능 NIC descriptor ring

    DMAPOOL_DEBUG 동작 비교

    조건할당 시해제 시페이지 초기화
    DMAPOOL_DEBUG ON`POOL_POISON_ALLOCATED`로 덮어쓰기`POOL_POISON_FREED` 검증 + 덮어쓰기`POOL_POISON_FREED`로 초기화
    DMAPOOL_DEBUG OFF`want_init_on_alloc`이면 0 초기화`want_init_on_free`이면 0 초기화없음

    관련 문서

  • 메모리 관리 개요
  • Buddy Allocator
  • SLUB 할당자
  • vmalloc
  • CMA
  • HugeTLB
  • Per-CPU 메모리