# DMA Pool 🔌
관련 소스:mm/dmapool.c,include/linux/dmapool.h,include/linux/dma-mapping.h
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
// 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). 한 번에 할당하는 메모리 양.// 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) 부터 시작.
// 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 주소를 보관.
// 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 == 0 → boundary = allocation (경계 제약 없음)boundary < size 또는 비-2의 거듭제곱 → NULL 반환device_create_file()로 sysfs pools 파일 생성// 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()으로 품질 검증
// 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으로 초기화
// 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
// 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_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)
| API | NUMA 지원 | 관리 방식 | 주요 사용처 |
|---|---|---|---|
| `dma_pool_create()` | ❌ (NUMA_NO_NODE) | 수동 생성/파괴 | 일반 드라이버 |
| `dma_pool_create_node()` | ✅ (node 파라미터) | 수동 생성/파괴 | NUMA 인식 드라이버 |
| `dmam_pool_create()` | ❌ | 자동 해제 (devres) | 리소스 관리가 필요한 드라이버 |
| 방식 | 크기 단위 | 오버헤드 | 재사용 | 사용 시나리오 |
|---|---|---|---|---|
| `dma_alloc_coherent()` | 페이지 단위 | 없음 | 없음 | 대형 DMA 버퍼 |
| DMA Pool | 고정 크기 블록 | `sizeof(dma_block)` | ✅ | 작고 반복적인 DMA 할당 |
| `dma_alloc_pages()` | 페이지 단위 | 없음 | 없음 | SG DMA |
| vmalloc | 임의 크기 | 페이지 테이블 | 없음 | 커널 내부 사용 |
| boundary 값 | 블록 배치 규칙 | 예시 |
|---|---|---|
| 0 (미지정) | allocation 단위로 자유롭게 배치 | 기본 동작 |
| 4096 | 4KB 경계를 넘지 않음 | xHCI HCI command ring |
| 65536 | 64KB 경계를 넘지 않음 | 고성능 NIC descriptor ring |
| 조건 | 할당 시 | 해제 시 | 페이지 초기화 |
|---|---|---|---|
| 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 초기화 | 없음 |