Ryotta's Linux 7.0 MM

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

🔄 Swap / zswap

관련 소스: 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

개요 (Overview)

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 기본 타입

Swap / zswap 호출 흐름

Swap / zswap 호출 흐름

핵심 자료구조 관계도

핵심 자료구조 관계도

빠른 점검 명령

# 스왑 디바이스/파일 활성 상태 확인
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

핵심 자료구조

1. swap_info_struct — 스왑 디바이스 관리

스왑 파일/디바이스 하나당 하나의 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 성공

2. swap_extent — 디스크 블록 매핑

스왑 파일의 논리적 페이지 오프셋을 실제 디스크 블록으로 변환합니다. 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의 시작 디스크 블록 */
};

3. zswap_entry — 압축된 페이지 메타데이터

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 리스트 연결 */
};

4. zswap_pool — zswap 풀 관리

하나의 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];    /* 압축 알고리즘 이름 */
};

5. crypto_acomp_ctx — CPU별 압축 컨텍스트

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;             /* 컨텍스트 보호 */
};

6. zspage — zsmalloc 페이지

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 락 */
};

7. zswap_trees — xarray 기반 인덱싱

스왑 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];
}

8. swp_entry_t — PTE에 들어가는 스왑 위치

스왑 아웃된 익명 페이지의 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;
}

핵심 함수

1. zswap_store() — 페이지 압축 저장

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 비활성화 시 거부

2. zswap_load() — 압축된 페이지 로드

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는 지원하지 않음

3. zswap_compress() — 페이지 압축

실제 압축을 수행합니다. 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;

4. shrink_memcg_cb() — LRU 기반 쓰기백

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 = falseLRU_ROTATE

2. swpentry를 스택에 복사 (LRU 락 해제 후 엔트리 해제 가능)

3. list_move_tail() → LRU 끝으로 이동

4. zswap_writeback_entry() → 실제 writeback 시도

5. writeback 실패 시 LRU_RETRY, swapcache에서 이미 발견 시 LRU_STOP

5. swap_alloc_fast() / swap_alloc_slow() — 스왑 슬롯 할당

스왑 슬롯 할당의 두 가지 경로입니다.

/* mm/swapfile.c:1318 */
static bool swap_alloc_fast(struct folio *folio)
/* mm/swapfile.c:1348 */
static void swap_alloc_slow(struct folio *folio)
  • fast path: 현재 CPU의 per-CPU 클러스터에서 즉시 할당 시도
  • slow path: 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;

    6. swap_writeout() — zswap 앞단 캐시 진입

    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;
    }

    호출 흐름

    스왑 쓰기 경로 (zswap 저장)

    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에 저장)

    스왑 읽기 경로 (zswap 로드)

    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]

    Shrinker (압박 시 writeback)

    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 저장 경로 분기

    조건동작
    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 vs 기존 스왑 디스크 I/O

    항목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 / zswap / zram 역할 비교

    항목위치주 역할회수 시 동작
    Swap Cache일반 페이지 캐시/`swapper_space`swap in/out 중 같은 스왑 엔트리의 중복 I/O 방지folio 참조가 사라지면 일반 LRU 회수 대상
    zswapRAM 안의 압축 풀(`zsmalloc`)디스크 쓰기 전 압축 캐시shrinker가 cold entry를 디스크로 writeback
    zramRAM 기반 압축 블록 디바이스자체가 스왑 디바이스zram 디바이스 내부 압축 객체가 유지됨

    swappiness 해석

    의미사용 예
    `0`에 가까움익명 페이지 스왑을 최대한 늦추고 파일 캐시 회수를 선호지연에 민감한 DB, 충분한 RAM
    `60`일반 배포판의 균형값범용 서버/데스크톱
    `100` 이상익명 페이지와 파일 캐시 회수에서 스왑을 더 적극 활용압축 스왑/zswap 효과가 크고 I/O 병목이 낮은 환경

    zsmalloc 할당 흐름 비교

    단계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() → 새 zspagefix_fullness_group()
    **6**obj_malloc() → 객체 할당비어 있으면 free_zspage()
    **7**fix_fullness_group() → fullness 갱신handle 해제 (kmem_cache)

    zswap 파라미터 요약

    파라미터기본값설명
    `enabled`CONFIG_ZSWAP_DEFAULT_ONzswap 활성화/비활성화
    `compressor`CONFIG_ZSWAP_COMPRESSOR_DEFAULT압축 알고리즘 (zstd, lz4, lzo 등)
    `zpool`zsmalloc 계열 설정압축 객체를 담는 zpool 백엔드
    `max_pool_percent`20zswap 풀 최대 비율 (% of RAM)
    `accept_threshold_percent`90풀 제한 초과 후 재수용 기준 (%)
    `shrinker_enabled`CONFIG_ZSWAP_SHRINKER_DEFAULT_ON메모리 압박 시 shrinker 동작

    관련 문서

  • 메모리 관리 개요 — 전체 메모리 관리 조감도
  • Buddy Allocator — 물리 페이지 할당
  • 페이지 회수 — kswapd, direct reclaim, LRU
  • zsmalloc — 압축 데이터 전용 할당자 상세
  • OOM Killer — 스왑 부족 시 최후 수단
  • Memory Cgroup — 컨테이너별 메모리 제한
  • SLUB 할당자 — 커널 오브젝트 캐싱
  • Compaction — 메모리 단편화 해소
  • Folio / Page Cache — 파일 기반 페이지 관리