관련 소스: mm/swapfile.c, mm/page_io.c, mm/zswap.c, mm/zsmalloc.c, mm/memory.c
관련 헤더: include/linux/swap.h, include/linux/zswap.h, include/linux/zsmalloc.h, include/linux/swapops.h, include/linux/mm_types.h
관련 문서: 메모리 관리 개요 · Buddy Allocator · 페이지 회수 · zsmalloc · OOM Killer · Memory Cgroup
Linux의 Swap는 익명 페이지(Anonymous Page)가 물리 메모리 부족 시 디스크 스왑 영역으로 내보내는 메커니즘입니다. swapfile.c는 스왑 디바이스/파일 관리, 스왑 슬롯 할당/해제, swap cache 연산을 담당합니다.
zswap은 스왑 쓰기 경로를 가로채어, 페이지가 디스크에 기록되기 전에 RAM 기반 풀에 압축 저장하는 캐시입니다. 이를 통해 디스크 I/O를 크게 줄이고 스왑 접근 지연시간을 개선합니다. 압축된 페이지는 zsmalloc 할당자가 관리하는 메모리 풀에 저장됩니다.
일상적으로는 책상 위 공간(RAM)이 부족할 때 덜 쓰는 문서(익명 페이지)를 창고(스왑 디바이스)로 옮기는 과정과 비슷합니다. zswap은 창고로 보내기 전에 문서를 압축 봉투에 넣어 책상 옆 서랍에 잠시 보관하는 계층이며, 다시 필요해지면 디스크까지 가지 않고 압축을 풀어 빠르게 복원할 수 있습니다.
Swap Cache는 스왑으로 나간 페이지가 다시 접근될 때 중복 I/O를 줄이는 메모리 내 캐시입니다. 같은 익명 페이지가 fork() 이후 여러 프로세스에서 공유되거나, swap out 직후 다시 접근되는 경우 한 번 읽은 folio를 swap cache에 보관해 다른 fault가 같은 데이터를 재사용할 수 있게 합니다.
minzkn.com은 Swap/zswap을 "메모리 회수" 섹션에서 간략히 다루며, kswapd, LRU, OOM Killer와 함께 설명합니다. 우리 문서에서는 소스 코드 수준에서 swapfile 관리, zswap 저장/로드 경로, shrinker 메커니즘을 상세 분석합니다.
linux-7.0-source/
├── mm/
│ ├── swapfile.c ← 스왑 디바이스 관리, 슬롯 할당, swap cache
│ ├── page_io.c ← swap read/write I/O, zswap 진입점
│ ├── zswap.c ← zswap 캐시 (압축 저장, 로드, writeback)
│ ├── zsmalloc.c ← 압축 데이터 전용 메모리 할당자
│ └── memory.c ← do_swap_page() swap-in fault 처리
└── include/linux/
├── swap.h ← swap_info_struct, swap_extent, 플래그 정의
├── zswap.h ← zswap API (store/load/invalidate)
├── zsmalloc.h ← zs_pool, zs_malloc, zs_free API
├── swapops.h ← swp_entry_t type/offset 인코딩
└── mm_types.h ← swp_entry_t, folio 기본 타입
# 스왑 디바이스/파일 활성 상태 확인
cat /proc/swaps
# 시스템 전체 스왑 사용량
free -h | grep Swap
# 스왑 파티션 상세 정보
swapon -s
# zswap 통계 확인 (커널 4.7+)
cat /sys/kernel/debug/zswap/pool_total_size # zswap 풀 전체 크기
cat /sys/kernel/debug/zswap/stored_pages # zswap에 저장된 페이지 수
cat /sys/kernel/debug/zswap/same_filled_pages # 제로 페이지 수
cat /sys/kernel/debug/zswap/duplicate_entry # 중복 엔트리 수
# zswap 파라미터 확인
cat /sys/module/zswap/parameters/enabled # zswap 활성화 여부
cat /sys/module/zswap/parameters/max_pool_percent # 최대 풀 비율 (기본 20%)
# zswap compressor 확인
cat /sys/module/zswap/parameters/compressor # 압축 알고리즘 (zstd/lzo/lz4 등)
# cgroup별 swap/zswap 사용량 (cgroup v2)
find /sys/fs/cgroup -maxdepth 3 \( -name memory.swap.current -o -name memory.zswap.current \) -print -exec cat {} \; 2>/dev/null
# vmstat에서 스왑 활동 관찰
cat /proc/vmstat | grep -E 'pswpin|pswpout|zswpin|zswpout'
# 스왑 캐시 현황
cat /proc/meminfo | grep -E 'SwapTotal|SwapFree|SwapCached'
# 스왑 영역별 크기, 사용량, 우선순위
swapon --show --bytes --output=NAME,TYPE,SIZE,USED,PRIO
# 익명 페이지와 파일 캐시 회수 성향 확인
sysctl vm.swappiness
cat /proc/sys/vm/swappiness
# zswap 백엔드와 zpool 확인
cat /sys/module/zswap/parameters/zpool 2>/dev/null
cat /sys/module/zswap/parameters/accept_threshold_percent 2>/dev/null
# zswap debugfs 통계 일괄 확인
grep -r . /sys/kernel/debug/zswap/ 2>/dev/null || true
# cgroup v2 swap/zswap 계정 상세 확인
find /sys/fs/cgroup -maxdepth 3 \( -name memory.zswap.max -o -name memory.zswap.writeback \) -print -exec cat {} \; 2>/dev/null
# 메모리 압박과 스왑 관련 커널 로그
cat /proc/pressure/memory
dmesg | grep -Ei 'zswap|swap|swapon|swapoff|Out of memory' | tail -50
스왑 파일/디바이스 하나당 하나의 swap_info_struct이 존재합니다. priority 순서로 정렬된 plist(swap_active_head)에 연결됩니다.
/* include/linux/swap.h:261-303 */
struct swap_info_struct {
struct percpu_ref users; /* 스왑 디바이스 유효성 유지 */
unsigned long flags; /* SWP_USED, SWP_WRITEOK 등 */
signed short prio; /* 스왑 우선순위 (높을수록 먼저 사용) */
struct plist_node list; /* swap_active_head 연결 */
signed char type; /* swap_info[] 인덱스 */
unsigned int max; /* swap_map 최대 크기 */
unsigned char *swap_map; /* vmalloc 배열: 각 슬롯 참조 카운트 */
unsigned long *zeromap; /* 제로 페이지 추적 비트맵 */
struct swap_cluster_info *cluster_info; /* SSD 클러스터 관리 */
struct list_head free_clusters; /* 빈 클러스터 목록 */
struct list_head full_clusters; /* 가득 찬 클러스터 목록 */
struct list_head nonfull_clusters[SWAP_NR_ORDERS]; /* 부분 사용 클러스터 */
struct list_head frag_clusters[SWAP_NR_ORDERS]; /* 단편화된 클러스터 */
unsigned int pages; /* 사용 가능한 총 페이지 수 */
atomic_long_t inuse_pages; /* 현재 사용 중인 페이지 수 */
struct swap_sequential_cluster *global_cluster; /* 회전 디바이스용 */
spinlock_t global_cluster_lock; /* global_cluster 직렬화 */
struct rb_root swap_extent_root; /* swap extent rbtree */
struct block_device *bdev; /* 블록 디바이스 */
struct file *swap_file; /* 스왑 파일 */
struct completion comp; /* swapoff 완료 대기 */
spinlock_t lock; /* swap_map 보호 */
spinlock_t cont_lock; /* count continuation 보호 */
struct work_struct discard_work; /* discard 워커 */
struct work_struct reclaim_work; /* reclaim 워커 */
struct list_head discard_clusters; /* discard 대상 클러스터 */
struct plist_node avail_list; /* swap_avail_head 연결 */
};
주요 플래그 (flags):
| 플래그 | 의미 |
|---|---|
| `SWP_USED` | 슬롯 사용 중 |
| `SWP_WRITEOK` | 쓰기 가능 |
| `SWP_SOLIDSTATE` | SSD (seek 비용 없음) |
| `SWP_PAGE_DISCARD` | 사용 후 페이지 클러스터 discard |
| `SWP_SYNCHRONOUS_IO` | 동기 I/O 효율적 |
| `SWP_ACTIVATED` | swap_activate 성공 |
스왑 파일의 논리적 페이지 오프셋을 실제 디스크 블록으로 변환합니다. rbtree로 관리됩니다.
/* include/linux/swap.h:191-196 */
struct swap_extent {
struct rb_node rb_node;
pgoff_t start_page; /* 이 extent의 시작 논리 페이지 */
pgoff_t nr_pages; /* 이 extent의 페이지 수 */
sector_t start_block; /* 이 extent의 시작 디스크 블록 */
};
zswap에 저장된 압축된 페이지 하나의 메타데이터를 추적합니다.
/* mm/zswap.c:190-198 */
struct zswap_entry {
swp_entry_t swpentry; /* 대응하는 swap entry (type + offset) */
unsigned int length; /* 압축된 데이터 바이트 수 */
bool referenced; /* 최근 접근 여부 (second chance) */
struct zswap_pool *pool; /* 소속 zswap 풀 */
unsigned long handle; /* zsmalloc 할당 핸들 */
struct obj_cgroup *objcg; /* memcg 충전 대상 */
struct list_head lru; /* LRU 리스트 연결 */
};
하나의 zswap 풀은 하나의 압축 알고리즘과 하나의 zsmalloc 풀을 가집니다. 여러 풀이 존재할 수 있으며, RCU로 보호되는 리스트에 연결됩니다.
/* mm/zswap.c:153-161 */
struct zswap_pool {
struct zs_pool *zs_pool; /* zsmalloc 메모리 풀 */
struct crypto_acomp_ctx __percpu *acomp_ctx; /* CPU별 압축 컨텍스트 */
struct percpu_ref ref; /* 참조 카운트 */
struct list_head list; /* zswap_pools 리스트 */
struct work_struct release_work; /* 해제 워커 */
struct hlist_node node; /* CPU hotplug 노드 */
char tfm_name[CRYPTO_MAX_ALG_NAME]; /* 압축 알고리즘 이름 */
};
CPU마다 하나씩 존재하는 압축 컨텍스트입니다. 락 없이 병렬 압축을 가능하게 합니다.
/* mm/zswap.c:139-145 */
struct crypto_acomp_ctx {
struct crypto_acomp *acomp; /* 압축 알고리즘 핸들 */
struct acomp_req *req; /* 압축 요청 */
struct crypto_wait wait; /* 동기화 대기 */
u8 *buffer; /* 임시 압축 버퍼 (PAGE_SIZE) */
struct mutex mutex; /* 컨텍스트 보호 */
};
zsmalloc은 하나 이상의 페이지를 묶어 "zspage"를 만들고, 그 안에 동일 크기 객체를 배치합니다.
/* mm/zsmalloc.c:261-274 */
struct zspage {
struct {
unsigned int huge:HUGE_BITS; /* 단일 페이지 zspage 여부 */
unsigned int fullness:FULLNESS_BITS; /* 사용률 그룹 */
unsigned int class:CLASS_BITS + 1; /* 크기 클래스 인덱스 */
unsigned int magic:MAGIC_VAL_BITS; /* 매직 넘버 (0x58) */
};
unsigned int inuse; /* 사용 중인 객체 수 */
unsigned int freeobj; /* 첫 번째 빈 객체 인덱스 */
struct zpdesc *first_zpdesc; /* 첫 번째 페이지 디스크립터 */
struct list_head list; /* fullness 리스트 */
struct zs_pool *pool; /* 소속 풀 */
struct zspage_lock zsl; /* zspage 락 */
};
스왑 offset별로 zswap_entry를 빠르게 찾기 위해 xarray를 사용합니다. 64MB당 1개의 xarray로 분할됩니다.
/* mm/zswap.c:200-234 */
static struct xarray *zswap_trees[MAX_SWAPFILES];
static unsigned int nr_zswap_trees[MAX_SWAPFILES];
/* 64M 스왑 공간마다 하나의 스왑 주소 공간 */
#define ZSWAP_ADDRESS_SPACE_SHIFT 14
#define ZSWAP_ADDRESS_SPACE_PAGES (1 << ZSWAP_ADDRESS_SPACE_SHIFT)
static inline struct xarray *swap_zswap_tree(swp_entry_t swp)
{
return &zswap_trees[swp_type(swp)][swp_offset(swp)
>> ZSWAP_ADDRESS_SPACE_SHIFT];
}
스왑 아웃된 익명 페이지의 PTE에는 물리 PFN 대신 swp_entry_t가 들어갑니다. type은 어떤 스왑 영역인지 가리키고, offset은 그 영역 내부의 페이지 위치를 가리킵니다.
/* include/linux/mm_types.h:281-287 */
typedef struct {
unsigned long val;
} swp_entry_t;
/* include/linux/swapops.h:27-28,84-108 */
#define SWP_TYPE_SHIFT (BITS_PER_XA_VALUE - MAX_SWAPFILES_SHIFT)
#define SWP_OFFSET_MASK ((1UL << SWP_TYPE_SHIFT) - 1)
static inline swp_entry_t swp_entry(unsigned long type, pgoff_t offset)
{
swp_entry_t ret;
ret.val = (type << SWP_TYPE_SHIFT) | (offset & SWP_OFFSET_MASK);
return ret;
}
static inline unsigned swp_type(swp_entry_t entry)
{
return (entry.val >> SWP_TYPE_SHIFT);
}
static inline pgoff_t swp_offset(swp_entry_t entry)
{
return entry.val & SWP_OFFSET_MASK;
}
folio를 zswap에 압축 저장합니다. swap writeback 경로에서 호출됩니다.
/* mm/zswap.c:1488 */
bool zswap_store(struct folio *folio)
흐름:
1. zswap_enabled 확인 → 비활성화 시 실패 처리
2. get_obj_cgroup_from_folio() → memcg 충전 대상 확인
3. zswap_check_limits() → 풀 크기 제한 초과 시 거부
4. zswap_pool_current_get() → 현재 zswap 풀 획득
5. 각 페이지에 대해 zswap_store_page() 호출:
- zswap_entry_cache_alloc() → 엔트리 할당
- zswap_compress() → 압축 (crypto API)
- xa_store() → xarray에 저장
- zswap_lru_add() → LRU에 추가
6. 실패 시 이전에 저장된 stale 엔트리 정리
분기 조건:
| 조건 | 결과 |
|---|---|
| `!zswap_enabled` | false 반환, 이전 stale 엔트리 정리 |
| `obj_cgroup_may_zswap() == false` | `shrink_memcg()` 시도 후 재검사 |
| `zswap_check_limits() == true` | false 반환 (풀 가득 참) |
| `pool == NULL` | false 반환 |
| `zswap_store_page() 실패` | false 반환, 이전 엔트리 정리 |
| 압축 실패 또는 `dlen >= PAGE_SIZE` | writeback 허용 시 원본 PAGE_SIZE로 저장, writeback 비활성화 시 거부 |
xarray에서 zswap_entry를 찾아 압축 해제 후 folio에 복원합니다.
/* mm/zswap.c:1594 */
int zswap_load(struct folio *folio)
흐름:
1. zswap_never_enabled() 확인 → 한 번도 활성화된 적이 없으면 -ENOENT
2. folio_test_large() 확인 → large folio는 지원 안 함 (-EINVAL)
3. xa_load(tree, offset) → xarray에서 엔트리 검색
4. zswap_decompress() → 압축 해제
5. folio_mark_uptodate() → folio 최신 상태로 표시
6. swapcache에서 로드된 경우 xa_erase() + zswap_entry_free()로 엔트리 제거
반환값:
| 값 | 의미 |
|---|---|
| `0` | 성공 (folio unlocked, up-to-date) |
| `-ENOENT` | zswap에 해당 엔트리 없음 (디스크에서 읽어야 함) |
| `-EIO` | 압축 해제 실패 |
| `-EINVAL` | large folio는 지원하지 않음 |
실제 압축을 수행합니다. per-CPU 컨텍스트에서 동작합니다.
/* mm/zswap.c:853 */
static bool zswap_compress(struct page *page, struct zswap_entry *entry,
struct zswap_pool *pool)
흐름:
1. acomp_ctx_get_cpu_lock() → CPU별 압축 컨텍스트 획득
2. crypto_acomp_compress() → 비동기 압축 요청
3. crypto_wait_req() → 동기 대기
4. dlen >= PAGE_SIZE인 경우:
- writeback 비활성화 시 거부
- writeback 활성화 시 원본 그대로 저장
5. zs_malloc() → zsmalloc에서 메모리 할당
6. zs_obj_write() → 압축된 데이터 기록
/* mm/zswap.c:885-929 */
comp_ret = crypto_wait_req(crypto_acomp_compress(acomp_ctx->req), &acomp_ctx->wait);
dlen = acomp_ctx->req->dlen;
if (comp_ret || !dlen || dlen >= PAGE_SIZE) {
if (!mem_cgroup_zswap_writeback_enabled(
folio_memcg(page_folio(page)))) {
comp_ret = comp_ret ? comp_ret : -EINVAL;
goto unlock;
}
comp_ret = 0;
dlen = PAGE_SIZE;
dst = kmap_local_page(page);
mapped = true;
}
gfp = GFP_NOWAIT | __GFP_NORETRY | __GFP_HIGHMEM | __GFP_MOVABLE;
handle = zs_malloc(pool->zs_pool, dlen, gfp, page_to_nid(page));
if (IS_ERR_VALUE(handle)) {
alloc_ret = PTR_ERR((void *)handle);
goto unlock;
}
zs_obj_write(pool->zs_pool, handle, dst, dlen);
entry->handle = handle;
entry->length = dlen;
unlock:
if (mapped)
kunmap_local(dst);
if (comp_ret == -ENOSPC || alloc_ret == -ENOSPC)
zswap_reject_compress_poor++;
else if (comp_ret)
zswap_reject_compress_fail++;
else if (alloc_ret)
zswap_reject_alloc_fail++;
acomp_ctx_put_unlock(acomp_ctx);
return comp_ret == 0 && alloc_ret == 0;
zswap shrinker의 핵심 콜백 함수입니다. LRU에서 엔트리를 선택하여 writeback합니다.
/* mm/zswap.c:1100 */
static enum lru_status shrink_memcg_cb(struct list_head *item,
struct list_lru_one *l, void *arg)
흐름:
1. entry->referenced == true → second chance: referenced = false 후 LRU_ROTATE
2. swpentry를 스택에 복사 (LRU 락 해제 후 엔트리 해제 가능)
3. list_move_tail() → LRU 끝으로 이동
4. zswap_writeback_entry() → 실제 writeback 시도
5. writeback 실패 시 LRU_RETRY, swapcache에서 이미 발견 시 LRU_STOP
스왑 슬롯 할당의 두 가지 경로입니다.
/* mm/swapfile.c:1318 */
static bool swap_alloc_fast(struct folio *folio)
/* mm/swapfile.c:1348 */
static void swap_alloc_slow(struct folio *folio)
swap_avail_head plist를 순회하며 가용한 디바이스 탐색/* mm/swapfile.c:1519-1538 */
again:
local_lock(&percpu_swap_cluster.lock);
if (!swap_alloc_fast(folio))
swap_alloc_slow(folio);
local_unlock(&percpu_swap_cluster.lock);
if (!order && unlikely(!folio_test_swapcache(folio))) {
if (swap_sync_discard())
goto again;
}
/* 할당 실패도 MEMCG_SWAP_FAIL 계정을 위해 호출합니다. */
if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap)))
swap_cache_del_folio(folio);
if (unlikely(!folio_test_swapcache(folio)))
return -ENOMEM;
return 0;
swap_writeout()은 실제 디스크 writeback 전에 zeromap과 zswap을 먼저 검사합니다. zswap 저장이 성공하면 folio는 디스크로 쓰이지 않고 unlock됩니다.
/* mm/page_io.c:240-285 */
int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug)
{
int ret = 0;
if (folio_free_swap(folio))
goto out_unlock;
ret = arch_prepare_to_swap(folio);
if (ret) {
folio_mark_dirty(folio);
goto out_unlock;
}
if (is_folio_zero_filled(folio)) {
swap_zeromap_folio_set(folio);
goto out_unlock;
}
swap_zeromap_folio_clear(folio);
if (zswap_store(folio)) {
count_mthp_stat(folio_order(folio), MTHP_STAT_ZSWPOUT);
goto out_unlock;
}
if (!mem_cgroup_zswap_writeback_enabled(folio_memcg(folio))) {
folio_mark_dirty(folio);
return AOP_WRITEPAGE_ACTIVATE;
}
__swap_writepage(folio, swap_plug);
return 0;
out_unlock:
folio_unlock(folio);
return ret;
}
vmscan: shrink_folio_list()
└→ folio_alloc_swap() [swapfile.c:1493] (0이면 성공)
├→ swap_alloc_fast() [swapfile.c:1318] ← fast path
└→ swap_alloc_slow() [swapfile.c:1348] ← slow path
└→ swap_writeout() [page_io.c:240]
├→ is_folio_zero_filled() → zeromap 설정 후 I/O 생략
└→ zswap_store() [zswap.c:1488]
├→ zswap_check_limits()
├→ zswap_pool_current_get()
└→ zswap_store_page() [zswap.c:1408] (각 페이지)
├→ zswap_compress() [zswap.c:853]
│ └→ crypto_acomp_compress()
│ └→ zs_malloc() [zsmalloc.c:1297]
│ └→ zs_obj_write() [zsmalloc.c:1176]
└→ xa_store() (xarray에 저장)
do_swap_page() [memory.c:4706]
└→ swap_cache_get_folio() [memory.c:4785 → swap_state.c:87]
└→ swap_read_folio() [page_io.c:609]
├→ swap_read_folio_zeromap() [page_io.c:508]
├→ zswap_load() [zswap.c:1594]
│ ├→ xa_load(tree, offset) ← xarray 검색
│ ├→ zswap_decompress() [zswap.c:932]
│ │ ├→ zs_obj_read_sg_begin() [zsmalloc.c:1114]
│ │ └→ crypto_acomp_decompress()
│ ├→ folio_mark_uptodate()
│ └→ (swapcache 로드 시) xa_erase() + zswap_entry_free()
└→ zswap_load() == -ENOENT이면 swap_read_folio_bdev_*() [page_io.c:572/594]
kswapd / direct reclaim
└→ zswap_shrinker_scan() [zswap.c:1181]
└→ list_lru_shrink_walk()
└→ shrink_memcg_cb() [zswap.c:1100]
├→ referenced? → second chance (LRU_ROTATE)
└→ zswap_writeback_entry() [zswap.c:994]
├→ swap_cache_alloc_folio() [swap_state.c:551]
├→ zswap_decompress()
├→ xa_erase()
└→ __swap_writepage() [page_io.c:447] ← 디스크에 기록
| 조건 | fast path (swap_alloc_fast) | slow path (swap_alloc_slow) |
|---|---|---|
| **속도** | 매우 빠름 (per-CPU 클러스터 재사용) | 느림 (plist 순회) |
| **대상** | per-CPU 클러스터 | swap_avail_head plist |
| **잠금** | `percpu_swap_cluster.lock` + cluster lock | `swap_avail_lock` + cluster lock |
| **적합성** | SSD, 다중 프로세스 | 회전 디바이스, 단일 프로세스 |
| **실패 시** | slow path로 폴백 | 모든 디바이스 순회 후 실패 |
| 조건 | 동작 |
|---|---|
| zswap 비활성화 | false 반환, stale 엔트리 정리 |
| 풀 크기 제한 초과 | false 반환 + shrink_worker 큐잉 |
| obj_cgroup 미허용 | memcg shrink 후 재시도 |
| 압축 실패 (`comp_ret != 0`) | 거부 (zswap_reject_compress_fail++) |
| 압축 결과가 PAGE_SIZE 이상 | writeback 허용 시 원본 PAGE_SIZE 저장, 비활성화 시 거부 |
| zsmalloc 할당 실패 | 거부 (zswap_reject_alloc_fail++) |
| 모든 성공 | xarray 저장 + LRU 추가 |
| 항목 | zswap | 기존 스왑 |
|---|---|---|
| **저장 위치** | RAM (zsmalloc 풀) | 디스크/블록 디바이스 |
| **지연시간** | μs (RAM 접근) | ms (디스크 I/O) |
| **대역폭** | 메모리 대역폭 | 디스크 대역폭 |
| **용량 제한** | 최대 메모리의 20% (기본) | 스왑 디바이스 전체 |
| **CPU 오버헤드** | 압축/해제 비용 | I/O 대기 비용 |
| **적합 워크로드** | 높은 압축률 페이지 | 낮은 압축률 페이지 |
| **writeback** | shrinker에 의해 디스크로 전송 | - |
| 구성 | 장점 | 단점 | 대표 확인 명령 |
|---|---|---|---|
| 파티션 스왑 | 파일시스템 경로를 거치지 않아 단순하고 예측 가능 | 용량 변경이 번거로움 | `swapon --show --output=NAME,TYPE,SIZE,USED,PRIO` |
| 파일 스왑 | 운영 중 생성·확장·삭제가 쉬움 | 파일시스템/블록 매핑 특성을 함께 고려해야 함 | `findmnt -T /swapfile; swapon --show` |
| zram 스왑 | 디스크 I/O 없이 RAM 안에서 압축 블록 디바이스로 동작 | 압축에 CPU를 쓰고 RAM을 소비 | `zramctl; swapon --show` |
| zswap | 기존 스왑 앞단의 압축 캐시라 스왑 디바이스와 함께 사용 | 압축률이 낮으면 writeback으로 밀려남 | `cat /sys/module/zswap/parameters/enabled` |
| 항목 | 위치 | 주 역할 | 회수 시 동작 |
|---|---|---|---|
| Swap Cache | 일반 페이지 캐시/`swapper_space` | swap in/out 중 같은 스왑 엔트리의 중복 I/O 방지 | folio 참조가 사라지면 일반 LRU 회수 대상 |
| zswap | RAM 안의 압축 풀(`zsmalloc`) | 디스크 쓰기 전 압축 캐시 | shrinker가 cold entry를 디스크로 writeback |
| zram | RAM 기반 압축 블록 디바이스 | 자체가 스왑 디바이스 | zram 디바이스 내부 압축 객체가 유지됨 |
| 값 | 의미 | 사용 예 |
|---|---|---|
| `0`에 가까움 | 익명 페이지 스왑을 최대한 늦추고 파일 캐시 회수를 선호 | 지연에 민감한 DB, 충분한 RAM |
| `60` | 일반 배포판의 균형값 | 범용 서버/데스크톱 |
| `100` 이상 | 익명 페이지와 파일 캐시 회수에서 스왑을 더 적극 활용 | 압축 스왑/zswap 효과가 크고 I/O 병목이 낮은 환경 |
| 단계 | zs_malloc() | zs_free() |
|---|---|---|
| **1** | handle 할당 (kmem_cache) | handle → zspage 조회 |
| **2** | 크기 클래스 결정 | class->lock 획득 |
| **3** | class->lock 획득 | 통계 감소 (ZS_OBJS_INUSE) |
| **4** | find_get_zspage() → 기존 zspage 탐색 | obj_free() → freelist 연결 |
| **5** | 없으면 alloc_zspage() → 새 zspage | fix_fullness_group() |
| **6** | obj_malloc() → 객체 할당 | 비어 있으면 free_zspage() |
| **7** | fix_fullness_group() → fullness 갱신 | handle 해제 (kmem_cache) |
| 파라미터 | 기본값 | 설명 |
|---|---|---|
| `enabled` | CONFIG_ZSWAP_DEFAULT_ON | zswap 활성화/비활성화 |
| `compressor` | CONFIG_ZSWAP_COMPRESSOR_DEFAULT | 압축 알고리즘 (zstd, lz4, lzo 등) |
| `zpool` | zsmalloc 계열 설정 | 압축 객체를 담는 zpool 백엔드 |
| `max_pool_percent` | 20 | zswap 풀 최대 비율 (% of RAM) |
| `accept_threshold_percent` | 90 | 풀 제한 초과 후 재수용 기준 (%) |
| `shrinker_enabled` | CONFIG_ZSWAP_SHRINKER_DEFAULT_ON | 메모리 압박 시 shrinker 동작 |