# Mempool (메모리 버퍼 풀)
관련 소스:mm/mempool.c,include/linux/mempool.h
Mempool은 Linux 커널에서 극심한 메모리 압박 상황에서도 안전하게 메모리를 할당할 수 있도록 보장하는 비상 예약 풀(emergency reserve pool)입니다. 일반적인 슬럼 할당자(SLUB)나 페이지 할당자가 실패하더라도, mempool은 미리 할당해둔 요소를 제공하여 deadlock을 방지합니다.
일상 비유: 비상 휴대전화 충전기를 상상해보세요. 평소에는 일반 충전기를 사용하지만, 정전이나 재난 상황에서 일반 전기가 끊기면 미리 충전해둔 비상 충전기로 긴급하게 전화를 할 수 있습니다. Mempool도 이와 같은 원리로, 일반 할당자가 메모리 부족으로 실패해도 미리 확보된 비상 메모리로 핵심 기능이 동작하도록 보장합니다.
핵심 원리: mempool_alloc()은 먼저 사용자 정의 alloc_fn을 호출하고, 이것이 실패하면 미리 확보된 풀에서 요소를 꺼냅니다. 반대로 mempool_free()는 풀이 min_nr 미만이면 요소를 풀에 반환하고, 그 외에는 직접 해제합니다. 이렇게 하여 풀의 요소 수가 일정 수준 이상 유지되어, OOM 상황에서도 핵심 할당이 보장됩니다.
소스 파일:
mm/mempool.c ← mempool 전체 구현
include/linux/mempool.h ← struct mempool 정의, API 매크로
# mempool 관련 커널 심볼 확인
cat /proc/kallsyms | grep mempool
# slab에서 mempool 할당 현황 확인
cat /proc/slabinfo | grep mempool
# 커널 설정에서 mempool 관련 옵션 확인
grep -i mempool /boot/config-$(uname -r)
# mempool 관련 모듈/드라이버 사용 현황
lsmod | grep -i mempool
# mempool fault injection debugfs 확인 (CONFIG_FAULT_INJECTION 활성화 시)
ls /sys/kernel/debug/fail_mempool_alloc/ 2>/dev/null
# 커널 로그에서 mempool 관련 메시지 확인
dmesg | grep -i mempool
# SLUB debug가 활성화된 경우 mempool poison 오류 확인
dmesg | grep "mempool element poison"
# KASAN이 활성화된 경우 mempool 관련 메모리 오류 확인
dmesg | grep -i "mempool.*kasan"
# mempool 사용 커널 모듈 목록 확인
cat /proc/modules | grep -i mempool
# sysfs에서 mempool 관련 정보 확인 (해당 모듈 로드 시)
find /sys -name "*mempool*" 2>/dev/null
# 커널 빌드 설정에서 mempool 디버그 옵션 확인
grep -E "CONFIG_MEMPOOL|CONFIG_SLUB_DEBUG" /boot/config-$(uname -r)
# 메모리 압박 상황에서 mempool 동작 모니터링
vmstat 1 | grep -E "allocstall|pgfault"
/* include/linux/mempool.h:18-28 */
typedef struct mempool {
spinlock_t lock; /* 요소 배열 보호용 스핀락 */
int min_nr; /* 풀에 보장할 최소 요소 수 */
int curr_nr; /* 현재 풀에 보유한 요소 수 */
void **elements; /* 요소 포인터 배열 (스택 역할) */
void *pool_data; /* alloc/free 콜백에 전달할 사용자 데이터 */
mempool_alloc_t *alloc; /* 요소 할당 함수 포인터 */
mempool_free_t *free; /* 요소 해제 함수 포인터 */
wait_queue_head_t wait; /* 요소 부족 시 대기할 wait queue */
} mempool_t;
필드 설명:
| 필드 | 타입 | 역할 |
|---|---|---|
| `lock` | `spinlock_t` | `elements` 배열과 `curr_nr` 변경을 보호. IRQ에서 사용 가능하도록 `spin_lock_irqsave` 사용 |
| `min_nr` | `int` | 풀이 유지해야 할 최소 요소 수. 이 값 이하로 내려가면 할당 함수에서 반환된 요소를 풀에 보관 |
| `curr_nr` | `int` | 현재 풀에 저장된 요소 수. `curr_nr == 0`이면 풀이 비어있음 |
| `elements` | `void **` | LIFO 스택 역할의 포인터 배열. 인덱스 `0`~`curr_nr-1`에 요소 저장 |
| `pool_data` | `void *` | 콜백 함수에 전달되는 컨텍스트. kmalloc이면 size, slab이면 `kmem_cache *`, 페이지면 order |
| `alloc` | `mempool_alloc_t *` | 요소 할당 콜백. 실패 시 NULL 반환 가능 |
| `free` | `mempool_free_t *` | 요소 해제 콜백 |
| `wait` | `wait_queue_head_t` | `curr_nr == 0`이고 `__GFP_DIRECT_RECLAIM`일 때 요소가 반환될 때까지 대기 |
/* include/linux/mempool.h:15-16 */
typedef void * (mempool_alloc_t)(gfp_t gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
/* include/linux/mempool.h:30-38 — mempool 상태 확인 */
static inline bool mempool_initialized(struct mempool *pool)
{
return pool->elements != NULL; /* elements 배열 할당 여부로 초기화 확인 */
}
static inline bool mempool_is_saturated(struct mempool *pool)
{
return READ_ONCE(pool->curr_nr) >= pool->min_nr; /* 풀이 충분히 차 있는지 확인 */
}
mempool_initialized(): mempool이 정상적으로 초기화되었는지 확인 (elements 배열 존재 여부)mempool_is_saturated(): 풀의 요소 수가 min_nr 이상인지 확인 (풀이 포화 상태인지)/* mm/mempool.c:175-191 — 요소 추가/제거 */
static __always_inline void add_element(struct mempool *pool, void *element)
{
BUG_ON(pool->min_nr != 0 && pool->curr_nr >= pool->min_nr);
poison_element(pool, element); /* SLUB debug: 해제된 메모리 중독 */
if (kasan_poison_element(pool, element)) /* KASAN: 접근 불가 영역 표시 */
pool->elements[pool->curr_nr++] = element;
}
static void *remove_element(struct mempool *pool)
{
void *element = pool->elements[--pool->curr_nr];
BUG_ON(pool->curr_nr < 0);
kasan_unpoison_element(pool, element); /* KASAN: 접근 허용 영역 표시 */
check_element(pool, element); /* SLUB debug: 중독 패턴 검증 */
return element;
}
/* mm/mempool.c:313-331 — 메모리 풀 생성 (NUMA 인식) */
struct mempool *mempool_create_node_noprof(int min_nr,
mempool_alloc_t *alloc_fn, mempool_free_t *free_fn,
void *pool_data, gfp_t gfp_mask, int node_id)
{
struct mempool *pool;
pool = kmalloc_node_noprof(sizeof(*pool), gfp_mask | __GFP_ZERO, node_id);
if (!pool)
return NULL;
if (mempool_init_node(pool, min_nr, alloc_fn, free_fn, pool_data,
gfp_mask, node_id)) {
kfree(pool);
return NULL;
}
return pool;
}
kmalloc 실패 → NULL 반환, mempool_init_node 실패 → kfree 후 NULL 반환/* mm/mempool.c:233-269 — mempool 초기화 (기본 요소 사전 할당 포함) */
int mempool_init_node(struct mempool *pool, int min_nr,
mempool_alloc_t *alloc_fn, mempool_free_t *free_fn,
void *pool_data, gfp_t gfp_mask, int node_id)
{
spin_lock_init(&pool->lock);
pool->min_nr = min_nr;
pool->pool_data = pool_data;
pool->alloc = alloc_fn;
pool->free = free_fn;
init_waitqueue_head(&pool->wait);
pool->elements = kmalloc_array_node(max(1, min_nr), sizeof(void *),
gfp_mask, node_id);
if (!pool->elements)
return -ENOMEM;
while (pool->curr_nr < max(1, pool->min_nr)) {
void *element;
element = pool->alloc(gfp_mask, pool->pool_data);
if (unlikely(!element)) {
mempool_exit(pool);
return -ENOMEM;
}
add_element(pool, element);
}
return 0;
}
min_nr개 요소를 미리 할당kmalloc_array 실패 → -ENOMEM, alloc_fn 실패 → mempool_exit 후 -ENOMEMmin_nr == 0이어도 최소 1개 요소를 할당 (안전장치)/* mm/mempool.c:552-590 — 요소 1개 할당 (핵심 할당 경로) */
void *mempool_alloc_noprof(struct mempool *pool, gfp_t gfp_mask)
{
gfp_t gfp_temp = mempool_adjust_gfp(&gfp_mask);
void *element;
VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
might_alloc(gfp_mask);
repeat_alloc:
if (should_fail_ex(&fail_mempool_alloc, 1, FAULT_NOWARN)) {
pr_info("forcing mempool usage for %pS\n", (void *)_RET_IP_);
element = NULL;
} else {
element = pool->alloc(gfp_temp, pool->pool_data);
}
if (unlikely(!element)) {
if (!mempool_alloc_from_pool(pool, &element, 1, 0, gfp_temp)) {
if (gfp_temp != gfp_mask) {
gfp_temp = gfp_mask;
goto repeat_alloc;
}
if (gfp_mask & __GFP_DIRECT_RECLAIM) {
goto repeat_alloc;
}
}
}
return element;
}
1. mempool_adjust_gfp로 GFP 플래그 조정 (emergency reserve 사용 방지)
2. alloc_fn 호출 시도
3. 실패 시 → mempool_alloc_from_pool로 풀에서 할당
4. 풀도 비어있으면 → 재시도 여부 결정 (__GFP_DIRECT_RECLAIM 유무)
/* mm/mempool.c:708-713 — 요소 반환 */
void mempool_free(void *element, struct mempool *pool)
{
if (likely(element) && !mempool_free_bulk(pool, &element, 1))
pool->free(element, pool->pool_data);
}
mempool_free_bulk가 풀에 넣었으면 종료, 아니면 free_fn 호출/* mm/mempool.c:351-409 — 풀 크기 동적 조절 */
int mempool_resize(struct mempool *pool, int new_min_nr)
min_nr를 변경 - 축소 (new_min_nr < min_nr): 초과 요소를 free_fn으로 해제
- 확장 (new_min_nr > min_nr): 새 배열 할당, 요소 추가 확보
/* mm/mempool.c:412-462 — 풀 내부 요소에서 할당 */
static unsigned int mempool_alloc_from_pool(struct mempool *pool, void **elems,
unsigned int count, unsigned int allocated, gfp_t gfp_mask)
{
/* ... */
spin_lock_irqsave(&pool->lock, flags);
if (unlikely(pool->curr_nr < count - allocated))
goto fail;
for (i = 0; i < count; i++) {
if (!elems[i]) {
elems[i] = remove_element(pool);
allocated++;
}
}
spin_unlock_irqrestore(&pool->lock, flags);
/* ... */
fail:
if (gfp_mask & __GFP_DIRECT_RECLAIM) {
/* 대기 후 재시도 */
DEFINE_WAIT(wait);
prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
spin_unlock_irqrestore(&pool->lock, flags);
io_schedule_timeout(5 * HZ);
finish_wait(&pool->wait, &wait);
} else {
spin_unlock_irqrestore(&pool->lock, flags);
}
return allocated;
}
__GFP_DIRECT_RECLAIM → 5초 타임아웃 대기 후 재시도/* mm/mempool.c:471-475 — GFP 플래그 조정 */
static inline gfp_t mempool_adjust_gfp(gfp_t *gfp_mask)
{
*gfp_mask |= __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN;
return *gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_IO);
}
/* mm/mempool.c:492-534 — 여러 요소를 한번에 할당 */
int mempool_alloc_bulk_noprof(struct mempool *pool, void **elems,
unsigned int count, unsigned int allocated)
{
gfp_t gfp_mask = GFP_KERNEL;
gfp_t gfp_temp = mempool_adjust_gfp(&gfp_mask);
unsigned int i = 0;
VM_WARN_ON_ONCE(count > pool->min_nr); /* min_nr 이내로만 배치 할당 허용 */
might_alloc(gfp_mask);
/* fault injection 테스트용 분기 */
if (should_fail_ex(&fail_mempool_alloc_bulk, 1, FAULT_NOWARN)) {
pr_info("forcing mempool usage for %pS\n", (void *)_RET_IP_);
goto use_pool;
}
repeat_alloc:
/* 1차: alloc_fn으로 각 요소 할당 시도 */
for (i = 0; i < count; i++) {
if (elems[i])
continue; /* 이미 할당된 슬롯은 건너뜀 */
elems[i] = pool->alloc(gfp_temp, pool->pool_data);
if (unlikely(!elems[i]))
goto use_pool;
allocated++;
}
return 0;
use_pool:
/* 2차: 풀에서 할당 후 재시도 */
allocated = mempool_alloc_from_pool(pool, elems, count, allocated, gfp_temp);
gfp_temp = gfp_mask; /* 다음 반복부터는 모든 GFP 플래그 허용 */
goto repeat_alloc;
}
count > pool->min_nr 경고, fault injection 테스트 지원/* mm/mempool.c:604-611 — 사전 할당된 요소에서만 할당 */
void *mempool_alloc_preallocated(struct mempool *pool)
{
void *element = NULL;
/* GFP_NOWAIT로 즉시 할당 시도, 실패 시 NULL 반환 */
mempool_alloc_from_pool(pool, &element, 1, 0, GFP_NOWAIT);
return element;
}
GFP_NOWAIT 사용으로 대기 없이 즉시 반환/* mm/mempool.c:627-696 — 여러 요소를 한번에 반환 */
unsigned int mempool_free_bulk(struct mempool *pool, void **elems,
unsigned int count)
{
unsigned long flags;
unsigned int freed = 0;
bool added = false;
/* mempool_alloc과의 메모리 배리어 페어링 */
smp_rmb();
/* 풀이 부족한 경우 요소를 풀에 반환 */
if (unlikely(READ_ONCE(pool->curr_nr) < pool->min_nr)) {
spin_lock_irqsave(&pool->lock, flags);
while (pool->curr_nr < pool->min_nr && freed < count) {
add_element(pool, elems[freed++]);
added = true;
}
spin_unlock_irqrestore(&pool->lock, flags);
} else if (unlikely(pool->min_nr == 0 &&
READ_ONCE(pool->curr_nr) == 0)) {
/* min_nr == 0 엣지 케이스: 최소 1개 요소 보장 */
spin_lock_irqsave(&pool->lock, flags);
if (likely(pool->curr_nr == 0)) {
add_element(pool, elems[freed++]);
added = true;
}
spin_unlock_irqrestore(&pool->lock, flags);
}
/* 대기 중인 프로세스가 있으면 깨우기 */
if (unlikely(added) && wq_has_sleeper(&pool->wait))
wake_up(&pool->wait);
return freed; /* 풀에 반환된 요소 수 */
}
smp_rmb() 메모리 배리어로 할당/반환 순서 보장, 대기 중인 프로세스 깨우기mempool_create(min_nr, alloc_fn, free_fn, pool_data)
└─ mempool_create_node_noprof()
└─ mempool_init_node()
├─ kmalloc_array_node() ← elements 배열 할당
└─ while (curr_nr < min_nr)
└─ pool->alloc() ← 미리 요소 채우기
└─ add_element()
mempool_alloc(pool, gfp_mask)
└─ mempool_alloc_noprof()
├─ mempool_adjust_gfp() ← GFP 플래그 조정
├─ pool->alloc(gfp_temp) ← 1차: alloc_fn 호출
└─ (실패 시) mempool_alloc_from_pool()
├─ spin_lock_irqsave()
├─ remove_element() ← 풀 스택에서 꺼내기
├─ spin_unlock_irqrestore()
└─ (부족 시) io_schedule_timeout(5*HZ) 대기
mempool_alloc_bulk(pool, elems, count, allocated)
└─ mempool_alloc_bulk_noprof()
├─ mempool_adjust_gfp() ← GFP 플래그 조정
├─ for each elem: pool->alloc() ← 1차: 각 요소 할당 시도
└─ (실패 시) mempool_alloc_from_pool() + 재시도
mempool_alloc_preallocated(pool)
└─ mempool_alloc_from_pool(GFP_NOWAIT) ← 즉시 할당, 없으면 NULL 반환
mempool_free(element, pool)
└─ mempool_free_bulk(pool, &element, 1)
├─ (curr_nr < min_nr) → add_element() ← 풀에 반환
└─ (풀 꽉 참) → pool->free() ← 직접 해제
mempool_free_bulk(pool, elems, count)
├─ smp_rmb() ← 메모리 배리어
├─ (curr_nr < min_nr) → add_element() ← 풀에 반환
├─ (min_nr == 0 && curr_nr == 0) → add_element() ← 엣지 케이스
└─ wake_up(&pool->wait) ← 대기 프로세스 깨우기
mempool_destroy(pool)
└─ mempool_exit()
├─ while (curr_nr) → remove_element() + pool->free()
└─ kfree(elements)
└─ kfree(pool)
| 조건 | mempool_adjust_gfp 적용 | 할당 시도 | 풀 사용 후 동작 |
|---|---|---|---|
| `GFP_KERNEL` (기본) | `__GFP_DIRECT_RECLAIM` 제거, `__GFP_NOMEMALLOC` 추가 | alloc_fn 1회 시도 | 실패 시 풀에서 꺼내고, still 실패 시 대기 후 재시도 |
| `__GFP_DIRECT_RECLAIM` 포함 | 제거 후 복원 | alloc_fn + reclaim 가능 | 풀 부족 시 `io_schedule_timeout(5*HZ)` 대기 |
| `__GFP_NOWAIT` | 그대로 | alloc_fn 1회만 시도 | 풀 실패 시 즉시 NULL 반환 |
| 콜백 쌍 | pool_data | 사용 용도 | 내부 할당자 |
|---|---|---|---|
| `mempool_alloc_slab` / `mempool_free_slab` | `struct kmem_cache *` | slab 캐시 기반 할당 | `kmem_cache_alloc` / `kmem_cache_free` |
| `mempool_kmalloc` / `mempool_kfree` | `size_t` (바이트 수) | kmalloc 크기 지정 할당 | `kmalloc` / `kfree` |
| `mempool_alloc_pages` / `mempool_free_pages` | `int order` | 페이지 할당자 기반 할당 | `alloc_pages` / `__free_pages` |
| 풀 상태 (`curr_nr` vs `min_nr`) | 동작 |
|---|---|
| `curr_nr < min_nr` | 요소를 풀 스택에 반환 (재사용 준비) |
| `curr_nr >= min_nr` | `free_fn`으로 직접 해제 |
| `min_nr == 0` && `curr_nr == 0` | 최소 1개 요소를 풀에 반환 (엣지 케이스 처리) |
| 시나리오 | alloc_fn 결과 | 풀 상태 | 다음 동작 |
|---|---|---|---|
| 정상 할당 | 성공 | 무관 | 요소 반환 |
| 할당 실패 1차 | 실패 | 요소 있음 | 풀에서 꺼내기 + 재시도 |
| 할당 실패 2차 (풀 비어있음) | 실패 | `curr_nr == 0` | `__GFP_DIRECT_RECLAIM` → 대기 후 재시도, 아니면 NULL |
Mempool은 CONFIG_FAULT_INJECTION이 활성화된 경우 fault injection을 지원하여, 할당 실패 상황을 테스트할 수 있습니다.
/* mm/mempool.c:21-22 — fault injection 속성 정의 */
static DECLARE_FAULT_ATTR(fail_mempool_alloc);
static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk);
/* mm/mempool.c:24-38 — debugfs 인터페이스 초기화 */
static int __init mempool_faul_inject_init(void)
{
int error;
error = PTR_ERR_OR_ZERO(fault_create_debugfs_attr("fail_mempool_alloc",
NULL, &fail_mempool_alloc));
if (error)
return error;
return PTR_ERR_OR_ZERO(
fault_create_debugfs_attr("fail_mempool_alloc_bulk", NULL,
&fail_mempool_alloc_bulk));
}
late_initcall(mempool_faul_inject_init);
debugfs 인터페이스:
/sys/kernel/debug/fail_mempool_alloc/ — 단일 할당 실패 테스트/sys/kernel/debug/fail_mempool_alloc_bulk/ — 배치 할당 실패 테스트/* mm/mempool.c:40-150 — CONFIG_SLUB_DEBUG_ON 활성화 시 동작 */
#ifdef CONFIG_SLUB_DEBUG_ON
static void poison_error(struct mempool *pool, void *element, size_t size,
size_t byte); /* 중독 패턴 불일치 오류 출력 */
static void __check_element(struct mempool *pool, void *element, size_t size); /* 요소 무결성 검증 */
static void check_element(struct mempool *pool, void *element); /* KASAN 상태 확인 후 검증 */
static void __poison_element(void *element, size_t size); /* 해제된 메모리에 중독 패턴 기록 */
static void poison_element(struct mempool *pool, void *element); /* KASAN 상태 확인 후 중독 */
#endif
/* KASAN 메모리 중독/해제 함수 */
static __always_inline bool kasan_poison_element(struct mempool *pool, void *element);
static void kasan_unpoison_element(struct mempool *pool, void *element);
동작 원리:
1. poison_element(): 요소를 풀에 넣을 때 POISON_FREE 패턴으로 덮어쓰기
2. check_element(): 요소를 풀에서 꺼낼 때 중독 패턴 검증
3. kasan_poison_element(): KASAN에게 해당 메모리 영역이 접근 불가임을 알림
4. kasan_unpoison_element(): KASAN에게 해당 메모리 영역이 접근 가능함을 알림
| 서브시스템 | 사용 목적 | 예시 | 관련 소스 |
|---|---|---|---|
| I/O 스케줄러 | 요청 풀이 소진되지 않도록 보장 | `elevator_mempool_init` | block/elevator.c |
| 블록 장치 | bio 할당 비상 예약 | `blk_rq_mempool_init` | block/blk-mq.c |
| 네트워크 | sk_buff 할당 보장 | `skb_mempool_init` | net/core/skbuff.c |
| 파일 시스템 | journal/head 할당 | ext4, XFS 등 | fs/ext4/, fs/xfs/ |
| DM (Device Mapper) | 작업 구조체 할당 | `dm_mempool_create` | drivers/md/dm.c |
| SCSI | 요청 풀 할당 | `scsi_mempool_create` | drivers/scsi/scsi.c |
| USB | 요청 풀 할당 | `usb_mempool_create` | drivers/usb/core/ |
| InfiniBand | 작업 요청 풀 | `ib_mempool_create` | drivers/infiniband/ |
/* 네트워크 스택: sk_buff 할당을 위한 mempool 사용 예시 */
/* net/core/skbuff.c에서 sk_buff_mempool 생성 */
/* 블록 장치: blk_rq_mempool 생성 예시 */
/* block/blk-mq.c에서 request 할당을 위한 mempool 사용 */