RAM 디스크 (Ramdisk)

리눅스 커널의 RAM 디스크 서브시스템을 심층 분석합니다. BRD 블록 드라이버(drivers/block/brd.c), ramfs 파일시스템, 레거시 initrd와 현대적 initramfs 부팅 메커니즘, zram 압축 블록 디바이스, 메모리 백킹 전략, 블록 레이어 통합, 커널 부트 파라미터와 실전 활용 가이드까지 RAM 기반 스토리지의 모든 것을 다룹니다.

전제 조건: 블록 레이어메모리 관리 기초 문서를 먼저 읽으세요. RAM 디스크는 블록 디바이스(/dev/ram0) 또는 메모리 기반 파일시스템(ramfs/tmpfs)으로 동작하며, 부팅 과정을 이해하려면 부팅 과정도 함께 보시면 좋습니다.
일상 비유: RAM 디스크는 칠판과 비슷합니다. 하드디스크가 "노트에 적어두는 것"이라면, RAM 디스크는 "칠판에 바로 적는 것"입니다. 속도는 빠르지만 전원이 꺼지면(칠판을 지우면) 데이터가 사라집니다. zram은 "작은 글씨로 빽빽하게 적는 것"(압축)으로 더 많은 내용을 담을 수 있습니다.

핵심 요약

  • BRD (/dev/ram0) — 고정 크기 RAM 블록 디바이스. 페이지 단위로 메모리를 할당하며, 전통적인 블록 디바이스 인터페이스를 제공합니다.
  • ramfs — 페이지 캐시 기반 메모리 파일시스템. 크기 제한이 없어 메모리를 모두 소진할 수 있습니다.
  • tmpfs — ramfs에 크기 제한과 스왑 지원을 추가한 파일시스템. /tmp, /run 등에 널리 사용됩니다.
  • initrd — 레거시 방식으로 부트로더가 RAM에 로드한 디스크 이미지를 /dev/ram0에 기록하여 초기 루트 파일시스템으로 사용합니다.
  • initramfs — cpio 아카이브를 커널이 직접 rootfs(tmpfs)에 풀어 /init을 실행하는 현대적 방식입니다.
  • zram — 메모리 내 압축 블록 디바이스. 주로 스왑 파티션으로 사용하여 메모리 효율을 2~3배 높입니다.

RAM 디스크 개요

RAM 디스크(Ramdisk)는 시스템 메모리(DRAM)의 일부를 디스크처럼 사용하는 기술입니다. 물리적 디스크 대비 수십~수백 배 빠른 I/O 성능을 제공하지만, 전원이 꺼지면 데이터가 소실됩니다. 리눅스 커널에서 RAM 디스크는 크게 네 가지 형태로 존재합니다:

유형인터페이스소스 위치핵심 용도
BRD블록 디바이스 (/dev/ram*)drivers/block/brd.c범용 RAM 블록 디바이스, initrd 백엔드
ramfsVFS 파일시스템fs/ramfs/rootfs 백엔드, 초기 파일시스템
tmpfsVFS 파일시스템mm/shmem.c/tmp, /run, /dev/shm, initramfs
zram블록 디바이스 (/dev/zram*)drivers/block/zram/압축 스왑, 압축 RAM 디스크

역사적 배경

RAM 디스크의 역사는 리눅스 커널 초기부터 시작됩니다:

시기사건의의
1991Linux 0.01 — 램디스크 미포함초기 커널은 플로피에서 직접 부팅
1995Linux 1.3.x — rd.c RAM 디스크 드라이버initrd 지원 시작, 모듈 로딩 전 루트 마운트 가능
2000Linux 2.4 — ramfs 도입페이지 캐시 기반 메모리 FS, VFS 예제 코드
2001Linux 2.4.4 — tmpfs(shmem) 도입크기 제한 + 스왑 지원
2002Linux 2.5.x — initramfs 도입cpio 기반, BRD 블록 디바이스 불필요
2009Linux 2.6.33 — brd.c 리팩토링기존 rd.c를 현대화, XArray 도입 준비
2014Linux 3.14 — zram mainlinestaging에서 승격, drivers/block/zram/
2019Linux 5.x — zram writeback콜드 페이지를 backing device로 이동
2023Linux 6.x — BRD 멀티큐 전환blk-mq 기반으로 현대화

RAM 디스크 아키텍처 총괄

리눅스 커널에서 RAM 기반 스토리지는 두 가지 경로로 제공됩니다: 블록 디바이스 계층(BRD, zram)과 VFS 파일시스템 계층(ramfs, tmpfs). 각각의 커널 스택 위치와 데이터 흐름을 살펴봅시다.

RAM 디스크 아키텍처 총괄 BRD, ramfs, tmpfs, zram 네 가지 유형의 커널 스택 위치와 관계를 보여주는 다이어그램 사용자 공간 (User Space) dd, mkfs, mount open/read/write open/read/write swapon, dd VFS (Virtual File System) 블록 레이어 (Block Layer) 파일시스템 직접 경로 BRD 드라이버 /dev/ram0..N drivers/block/brd.c zram 드라이버 /dev/zram0..N drivers/block/zram/ ramfs 크기 제한 없음 fs/ramfs/inode.c tmpfs (shmem) 크기 제한 + 스왑 mm/shmem.c XArray 페이지 매핑 PAGE_SIZE 단위 할당 zsmalloc + 압축 lzo/lz4/zstd 압축 저장 페이지 캐시 address_space 직접 저장 shmem 페이지 + 스왑 스왑 아웃 가능 물리 메모리 (Physical RAM / DRAM)
그림 1. RAM 디스크 아키텍처 총괄 — BRD/zram은 블록 레이어를, ramfs/tmpfs는 VFS를 직접 경유
핵심 차이: BRD와 zram은 블록 디바이스이므로 그 위에 ext4, xfs 등 어떤 파일시스템이든 마운트할 수 있습니다. 반면 ramfs/tmpfs는 자체가 파일시스템이므로 별도의 블록 디바이스가 필요 없습니다.

BRD 드라이버 (drivers/block/brd.c)

BRD(Block RAM Device)는 리눅스 커널의 전통적인 RAM 디스크 블록 드라이버입니다. /dev/ram0, /dev/ram1, ... 형태의 블록 디바이스를 생성하며, 요청 시 페이지를 동적으로 할당하여 데이터를 저장합니다.

주요 자료구조

/* drivers/block/brd.c */

struct brd_device {
    int                 brd_number;     /* 디바이스 번호 (ram0, ram1, ...) */
    struct request_queue *brd_queue;    /* 블록 요청 큐 */
    struct gendisk      *brd_disk;     /* 제네릭 디스크 구조체 */

    struct list_head    brd_list;      /* 전역 BRD 리스트 */

    /*
     * 데이터 저장: XArray(Radix Tree)로 섹터 → 페이지 매핑
     * 키: 섹터 번호 >> PAGE_SECTORS_SHIFT
     * 값: struct page *
     */
    spinlock_t          brd_lock;      /* 페이지 트리 보호 락 */
    struct xarray       brd_pages;     /* 섹터→페이지 매핑 XArray */
};

/* 전역 변수 */
static struct list_head brd_devices;   /* 모든 BRD 디바이스 리스트 */
static int rd_nr = CONFIG_BLK_DEV_RAM_COUNT;  /* 디바이스 개수 (기본 16) */
static int rd_size = CONFIG_BLK_DEV_RAM_SIZE; /* 각 디바이스 크기 (KB, 기본 4096) */
static int max_part = 1;                       /* 파티션 수 */

모듈 파라미터

파라미터기본값설명설정 방법
rd_nr16생성할 RAM 디스크 수modprobe brd rd_nr=4 또는 커널 파라미터 ramdisk_size=
rd_size4096 (KB)각 디스크의 최대 크기modprobe brd rd_size=65536 (64MB)
max_part1파티션 최대 수modprobe brd max_part=15
팁: rd_size최대 크기입니다. BRD는 실제 쓰기가 발생할 때만 페이지를 할당하므로, 생성 직후에는 메모리를 거의 소비하지 않습니다. 이를 온디맨드(on-demand) 할당이라 합니다.

블록 연산

/* BRD 블록 연산 구조체 */
static const struct block_device_operations brd_fops = {
    .owner      = THIS_MODULE,
    .submit_bio = brd_submit_bio,  /* BIO 제출 콜백 (no-queue 방식) */
};

/*
 * brd_submit_bio() — BIO 제출 핸들러
 * 블록 레이어가 이 함수를 직접 호출 (요청 큐 바이패스)
 */
static void brd_submit_bio(struct bio *bio)
{
    struct brd_device *brd = bio->bi_bdev->bd_disk->private_data;
    struct bio_vec bvec;
    struct bvec_iter iter;

    bio_for_each_segment(bvec, bio, iter) {
        unsigned int len = bvec.bv_len;
        struct page *page = bvec.bv_page;
        /* 세그먼트별로 페이지 복사 수행 */
        err = brd_do_bvec(brd, page, len,
                          bvec.bv_offset, iter);
        if (err) { ... }
    }
    bio_endio(bio);  /* BIO 완료 통지 */
}

/*
 * brd_do_bvec() — 실제 데이터 전송
 * READ: BRD 페이지 → BIO 페이지 (memcpy_from_page)
 * WRITE: BIO 페이지 → BRD 페이지 (memcpy_to_page)
 */
static int brd_do_bvec(struct brd_device *brd,
                       struct page *page, unsigned int len,
                       unsigned int off, struct bvec_iter *iter)
{
    sector_t sector = iter->bi_sector;
    struct page *brd_page;

    if (op_is_write(iter->bi_opf)) {
        /* 쓰기: 페이지가 없으면 새로 할당 */
        brd_page = brd_insert_page(brd, sector);
        memcpy_to_page(brd_page, offset, kaddr + off, len);
    } else {
        /* 읽기: 페이지가 없으면 0 반환 */
        brd_page = brd_lookup_page(brd, sector);
        if (brd_page)
            memcpy_from_page(kaddr + off, brd_page, offset, len);
        else
            memzero_page(page, off, len);
    }
    return 0;
}

페이지 관리

BRD는 XArray(구 Radix Tree)를 사용하여 섹터 번호를 페이지에 매핑합니다. 쓰기 시 처음 접근하는 섹터에 대해서만 페이지를 할당하므로, 메모리 사용이 효율적입니다.

BRD XArray 페이지 매핑 XArray 기반 섹터에서 페이지로의 매핑 구조와 brd_lookup_page/brd_insert_page 흐름 섹터 번호 (sector) 예: sector = 16 인덱스 변환 idx = sector >> PAGE_SECTORS_SHIFT XArray (brd_pages) [0] [1] [2] [3] ... Page 0 4096 bytes Page 1 4096 bytes Page 2 4096 bytes brd_lookup_page() xa_load(&brd->brd_pages, idx) → NULL이면 0 반환 (읽기) brd_insert_page() 1. alloc_page(GFP_NOIO) 2. xa_store(&brd->brd_pages, idx, page, GFP_NOIO) READ WRITE DISCARD → __free_page() + xa_erase()
그림 2. BRD XArray 페이지 매핑 — 섹터 번호를 인덱스로 변환하여 페이지를 관리
/*
 * brd_lookup_page() — 섹터에 해당하는 페이지 조회
 */
static struct page *brd_lookup_page(struct brd_device *brd,
                                     sector_t sector)
{
    unsigned long idx;
    struct page *page;

    idx = sector >> PAGE_SECTORS_SHIFT;  /* 섹터 → 페이지 인덱스 */
    page = xa_load(&brd->brd_pages, idx);
    BUG_ON(page && page->index != idx);

    return page;  /* NULL이면 해당 섹터에 아직 쓰기 없음 */
}

/*
 * brd_insert_page() — 새 페이지 할당 후 XArray에 삽입
 */
static struct page *brd_insert_page(struct brd_device *brd,
                                     sector_t sector)
{
    unsigned long idx;
    struct page *page, *cur;

    page = brd_lookup_page(brd, sector);
    if (page)
        return page;  /* 이미 존재하면 그대로 반환 */

    page = alloc_page(GFP_NOIO | __GFP_ZERO);  /* 0으로 초기화된 페이지 */
    if (!page)
        return NULL;

    idx = sector >> PAGE_SECTORS_SHIFT;
    page->index = idx;

    spin_lock(&brd->brd_lock);
    cur = xa_store(&brd->brd_pages, idx, page, GFP_NOIO);
    spin_unlock(&brd->brd_lock);

    if (cur) {
        __free_page(page);  /* 경합 시 중복 페이지 해제 */
        page = cur;
    }
    return page;
}

DISCARD 지원

BRD는 DISCARD(TRIM) 연산을 지원합니다. DISCARD가 들어오면 해당 섹터 범위의 페이지를 XArray에서 제거하고 메모리를 해제합니다. 이를 통해 fstrim이나 blkdiscard 명령으로 사용하지 않는 영역의 메모리를 회수할 수 있습니다.

/*
 * brd_do_discard() — 페이지 해제
 */
static void brd_do_discard(struct brd_device *brd,
                           sector_t sector, u32 size)
{
    while (size >= PAGE_SIZE) {
        unsigned long idx = sector >> PAGE_SECTORS_SHIFT;
        struct page *page;

        spin_lock(&brd->brd_lock);
        page = xa_erase(&brd->brd_pages, idx);  /* XArray에서 제거 */
        spin_unlock(&brd->brd_lock);

        if (page)
            __free_page(page);  /* 메모리 해제 */

        sector += PAGE_SECTORS;
        size -= PAGE_SIZE;
    }
}
실전 팁: BRD 위에 파일시스템을 마운트하고 파일을 삭제한 뒤 fstrim /mnt/ramdisk을 실행하면, 실제 메모리가 반환됩니다. 이는 ext4의 DISCARD 지원(-o discard)과 결합하여 자동화할 수도 있습니다.

ramfs 파일시스템

ramfs는 리눅스 커널에서 가장 단순한 파일시스템 중 하나입니다. 페이지 캐시를 직접 데이터 저장소로 사용하며, VFS의 simple_* 헬퍼로 구현됩니다. 커널 소스에서 "VFS 구현 예제"로도 자주 인용됩니다.

ramfs 내부 구현

/* fs/ramfs/inode.c — 핵심 구조 */

static const struct super_operations ramfs_ops = {
    .statfs     = simple_statfs,
    .drop_inode = generic_delete_inode,
    .show_options = ramfs_show_options,
};

static const struct inode_operations ramfs_dir_inode_operations = {
    .create     = ramfs_create,      /* 파일 생성 */
    .lookup     = simple_lookup,     /* 디렉터리 조회 */
    .link       = simple_link,       /* 하드링크 */
    .unlink     = simple_unlink,     /* 파일 삭제 */
    .symlink    = ramfs_symlink,     /* 심볼릭 링크 */
    .mkdir      = ramfs_mkdir,       /* 디렉터리 생성 */
    .rmdir      = simple_rmdir,      /* 디렉터리 삭제 */
    .mknod      = ramfs_mknod,       /* 특수 파일 생성 */
    .rename     = simple_rename,     /* 이름 변경 */
};

/* ramfs의 address_space_operations — 페이지 캐시 직접 사용 */
static const struct address_space_operations ramfs_aops = {
    .read_folio     = simple_read_folio,      /* 페이지 읽기 */
    .write_begin    = simple_write_begin,      /* 쓰기 시작 */
    .write_end      = simple_write_end,        /* 쓰기 완료 */
    .dirty_folio    = noop_dirty_folio,        /* dirty 마킹 (no-op) */
};

/*
 * 핵심 포인트: ramfs는 writeback 하지 않음
 * dirty_folio가 noop이므로 페이지가 dirty로 표시되지 않고,
 * 따라서 커널의 writeback 메커니즘이 동작하지 않습니다.
 * 데이터는 페이지 캐시에 영원히 남습니다.
 */
/* fs/ramfs/inode.c — ramfs_fill_super(): 슈퍼블록 초기화 */

static int ramfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
    struct ramfs_fs_info *fsi = sb->s_fs_info;
    struct inode *inode;

    /* 슈퍼블록 기본 설정 */
    sb->s_maxbytes    = MAX_LFS_FILESIZE;  /* 최대 파일 크기 */
    sb->s_blocksize   = PAGE_SIZE;         /* 블록 크기 = 페이지 크기 */
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic       = RAMFS_MAGIC;       /* 0x858458f6 */
    sb->s_op          = &ramfs_ops;
    sb->s_time_gran   = 1;                 /* 1나노초 해상도 */

    /* 루트 inode 생성 */
    inode = ramfs_get_inode(sb, NULL,
                S_IFDIR | fsi->mount_opts.mode, 0);
    /* ramfs_get_inode():
     *   inode = new_inode(sb);
     *   inode->i_mapping->a_ops = &ramfs_aops;  ← 핵심!
     *   inode->i_atime = inode->i_mtime = inode->i_ctime
     *       = current_time(inode);
     */

    /* 루트 dentry 생성 */
    sb->s_root = d_make_root(inode);
    if (!sb->s_root)
        return -ENOMEM;
    return 0;
}

/* init/do_mounts.c — rootfs로서의 ramfs 등록 */
/*
 * 커널 부팅 초기에 rootfs가 마운트됩니다.
 * CONFIG_TMPFS가 활성화되면 tmpfs가, 아니면 ramfs가 rootfs로 사용됩니다.
 * initramfs의 cpio 내용은 이 rootfs 위에 풀립니다.
 */
static struct file_system_type rootfs_fs_type = {
    .name       = "rootfs",
    .init_fs_context = rootfs_init_fs_context,
    /* CONFIG_TMPFS=y → shmem_init_fs_context()
     * CONFIG_TMPFS=n → ramfs_init_fs_context() */
};
address_space_operations 함수ramfs 구현동작
read_foliosimple_read_folio()folio를 0으로 초기화 후 uptodate 마킹 (디스크 I/O 없음)
write_beginsimple_write_begin()grab_cache_page_write_begin()으로 페이지 캐시에 folio 할당/조회
write_endsimple_write_end()쓰기 완료, inode 크기 갱신, folio를 uptodate 마킹
dirty_folionoop_dirty_folio()아무것도 안 함 → writeback 대상에서 제외 → 영구 메모리 상주
ramfs가 커널에 남아있는 이유: ramfs는 약 200줄의 극히 단순한 파일시스템이지만, 커널 내부에서 rootfs 백엔드로 사용됩니다. CONFIG_TMPFS=n인 임베디드 환경에서 initramfs를 풀어놓을 rootfs가 필요하기 때문입니다. 또한 VFS 구현의 최소 참조 예제로서 커널 문서(Documentation/filesystems/ramfs-rootfs-initramfs.rst)에서 공식 권장됩니다.

마운트와 옵션

# ramfs 마운트 (크기 제한 없음!)
mount -t ramfs ramfs /mnt/ramdisk

# 마운트 옵션
mount -t ramfs -o mode=0755 ramfs /mnt/ramdisk

# ramfs는 size 옵션을 무시합니다 (크기 제한이 없음)
# 아래 명령의 size=100m은 효과 없음
mount -t ramfs -o size=100m ramfs /mnt/ramdisk

ramfs vs tmpfs 핵심 차이

속성ramfstmpfs
크기 제한없음 (메모리 전체 소진 가능)있음 (size= 옵션, 기본 RAM 50%)
스왑 지원불가가능 (메모리 부족 시 스왑 아웃)
df 표시항상 0 (사용량 추적 없음)정확한 사용량/가용량 표시
구현 복잡도~200줄~4000줄 (mm/shmem.c)
OOM 위험높음 (제한 없으므로)낮음 (제한 + 스왑)
용도rootfs 내부 백엔드, 개발 테스트/tmp, /run, /dev/shm
소스 위치fs/ramfs/mm/shmem.c
주의: ramfs는 크기 제한이 없으므로, root 사용자도 실수로 전체 메모리를 소진할 수 있습니다. 실무에서는 항상 tmpfs를 사용하세요. ramfs는 커널 내부(rootfs 백엔드)와 VFS 학습 용도로 남아있습니다.

tmpfs/shmem 개요

tmpfsramfs를 확장한 메모리 기반 파일시스템으로, 내부적으로 shmem(Shared Memory) 인프라를 사용합니다. ramfs와 달리 크기 제한, 스왑 지원, xattr, POSIX ACL 등 완전한 파일시스템 기능을 제공합니다.

shmem 자료구조 관계도 shmem_inode_info에서 address_space를 거쳐 folio와 swap_entry로 이어지는 구조 shmem(tmpfs) 자료구조 관계도 shmem_inode_info lock: rwsem swapped: unsigned long flags: (HUGE, NOSWAP...) alloced: unsigned long vfs_inode: struct inode i_mapping address_space i_pages: xarray nrpages: unsigned long a_ops: shmem_aops host: inode * writeback_index: pgoff_t 메모리 상주 스왑 아웃 shmem folio page: struct page mapping: address_space * index: pgoff_t _folio_order: 0 (4KB) / 9 (2MB) swap_entry_t val: unsigned long ┣ type: swap area index ┗ offset: slot in swap XArray에 shadow entry로 저장 THP (huge=always) 2MB folio 할당 시도 물리 메모리 (RAM) folio 데이터 실체 저장 스왑 디바이스 /dev/sda2 또는 swapfile 핵심 함수 호출 흐름 shmem_get_folio() ├ filemap_get_folio() → 캐시 히트 └ shmem_swapin_folio() → 스왑인 ※ tmpfs는 메모리 부족 시 shmem_writepage()를 통해 folio를 스왑 영역으로 이동하고, XArray에 swap_entry를 shadow로 기록합니다
그림 7. shmem(tmpfs) 자료구조 관계 — shmem_inode_info → address_space → folio/swap_entry 이중 경로
/* mm/shmem.c — shmem_get_folio() 핵심 흐름 */

/*
 * shmem_get_folio_gfp() — tmpfs 페이지 조회/할당의 핵심
 * 1. XArray에서 folio 조회
 * 2. 캐시 히트 → 바로 반환
 * 3. swap entry 발견 → shmem_swapin_folio()로 스왑인
 * 4. 없음 → 새 folio 할당
 */
static int shmem_get_folio_gfp(struct inode *inode,
    pgoff_t index, struct folio **foliop,
    enum sgp_type sgp, gfp_t gfp,
    struct mm_struct *fault_mm, int *fault_type)
{
    struct address_space *mapping = inode->i_mapping;
    struct shmem_inode_info *info = SHMEM_I(inode);
    struct folio *folio;

repeat:
    /* 1. XArray에서 folio 조회 */
    folio = filemap_get_folio(mapping, index);
    if (!IS_ERR(folio)) {
        /* 캐시 히트: folio가 이미 메모리에 있음 */
        if (sgp == SGP_WRITE)
            folio_set_referenced(folio);
        return 0;
    }

    /* 2. XArray에 swap entry가 있는 경우 → 스왑인 */
    folio = shmem_swapin_folio(inode, index, ...);
    if (folio)
        goto got_folio;

    /* 3. 새 folio 할당 */
    folio = shmem_alloc_folio(gfp, info, index);
    if (!folio)
        return -ENOMEM;

    /* THP: huge=always이면 2MB folio 할당 시도 */
    if (shmem_huge_enabled(inode)) {
        folio = shmem_alloc_and_add_folio(mapping, gfp | __GFP_COMP,
                    index, HPAGE_PMD_ORDER, ...);
    }
    ...
}

/* shmem_swapin_folio() — 스왑에서 folio 복구 */
static int shmem_swapin_folio(struct inode *inode, pgoff_t index, ...)
{
    swp_entry_t swap;

    /* XArray에서 swap entry 추출 */
    swap = radix_to_swp_entry(
        xa_load(&inode->i_mapping->i_pages, index));

    /* swap 영역에서 데이터 읽기 */
    folio = swap_cache_get_folio(swap, NULL, 0);
    if (!folio) {
        folio = shmem_swapin(swap, gfp, info, index);
        /* 디스크/zram에서 4KB 데이터 읽기 */
    }

    /* XArray에서 swap entry를 folio로 교체 */
    xa_store(&mapping->i_pages, index, folio, GFP_KERNEL);
    info->swapped--;

    return 0;
}

/* shmem_writepage() — 메모리 부족 시 스왑 아웃 */
static int shmem_writepage(struct page *page,
    struct writeback_control *wbc)
{
    struct shmem_inode_info *info = SHMEM_I(inode);
    swp_entry_t swap;

    /* 1. 스왑 슬롯 할당 */
    swap = get_swap_page(folio);
    if (!swap.val)
        return 0;  /* 스왑 공간 부족 → 메모리에 유지 */

    /* 2. XArray에 swap entry 저장 (shadow) */
    xa_store(&mapping->i_pages, folio->index,
             swp_to_radix_entry(swap), GFP_ATOMIC);

    /* 3. folio를 스왑 캐시로 이동 */
    set_page_dirty(page);
    add_to_swap_cache(folio, swap, ...);

    info->swapped++;
    /* → kswapd가 나중에 실제 디스크/zram에 기록 */
    return 0;
}
# tmpfs 마운트 — 다양한 옵션
mount -t tmpfs -o size=512m,nr_inodes=10k,mode=1777 tmpfs /tmp

# 주요 마운트 옵션
#   size=512m       — 최대 크기 (기본: RAM의 50%)
#   nr_inodes=10k   — 최대 inode 수
#   mode=1777       — 루트 디렉터리 권한
#   uid=0,gid=0     — 소유자/그룹
#   huge=always      — Transparent Huge Pages 사용
#   noswap           — 스왑 비활성화 (5.18+)

# 시스템에서 기본 사용되는 tmpfs
df -h -t tmpfs
# tmpfs           7.8G  1.2G  6.7G  15% /run
# tmpfs           7.8G     0  7.8G   0% /dev/shm
# tmpfs           7.8G  4.0K  7.8G   1% /tmp
tmpfs 마운트 포인트용도일반적 크기
/tmp임시 파일 저장소RAM의 50%
/run (/var/run)런타임 데이터 (PID 파일, 소켓)RAM의 20%
/dev/shmPOSIX 공유 메모리 (shm_open)RAM의 50%
/sys/fs/cgroupcgroup 파일시스템 (cgroup v1)제한 없음
/devdevtmpfs (디바이스 노드)제한 없음
마운트 옵션기본값설명예시
size=RAM의 50%최대 사용 공간 (bytes, k, m, g, %)size=2g, size=50%
nr_inodes=RAM 기반 자동최대 inode 수nr_inodes=1m
mode=01777루트 디렉터리 권한mode=0755
uid= / gid=0루트 디렉터리 소유자uid=1000,gid=1000
huge=neverTHP 정책: never/always/within_size/advisehuge=always
noswap(스왑 허용)이 tmpfs 인스턴스의 스왑 비활성화 (5.18+)noswap
inode32 / inode64inode64inode 번호 범위 (32비트 호환)inode32
mpol=defaultNUMA 메모리 정책mpol=interleave
nr_blocks=size 기반최대 블록 수 (PAGE_SIZE 단위)nr_blocks=262144
# /proc/meminfo에서 tmpfs(shmem) 메모리 사용 확인
grep -E 'Shmem|SwapCached|SwapTotal|SwapFree' /proc/meminfo
# Shmem:           1234568 kB   ← tmpfs가 사용하는 총 메모리
# SwapCached:        45678 kB   ← 스왑에서 다시 읽어온 페이지
# SwapTotal:       4194304 kB   ← 전체 스왑 공간
# SwapFree:        3891200 kB   ← 사용 가능한 스왑

# shmem이 스왑 아웃되는 과정 관찰
# 1. tmpfs에 대량 데이터 쓰기
dd if=/dev/urandom of=/tmp/bigfile bs=1M count=2048

# 2. 메모리 압박 유발 → shmem 페이지 스왑 아웃
cat /proc/vmstat | grep pswp
# pswpout 12345   ← 스왑 아웃된 페이지 수
# pswpin  6789    ← 스왑 인 된 페이지 수

# 3. 스왑 아웃된 shmem 페이지 수 확인
cat /proc/meminfo | grep Shmem
# Shmem:           1234568 kB   ← 메모리 상주 shmem
# ShmemHugePages:     0 kB      ← THP로 할당된 shmem
# ShmemPmdMapped:     0 kB      ← PMD로 매핑된 shmem
tmpfs 스왑 아웃 내부 동작: 메모리가 부족해지면 kswapd가 shmem 페이지를 스왑 후보로 선정합니다. shmem_writepage()가 호출되면 해당 folio의 데이터는 스왑 영역(디스크 또는 zram)에 기록되고, XArray에는 swap_entry가 shadow entry로 남습니다. 다음에 해당 파일 오프셋을 읽으면 shmem_swapin_folio()가 스왑에서 데이터를 복구합니다. noswap 옵션(5.18+)을 사용하면 해당 tmpfs 인스턴스의 페이지는 절대 스왑 아웃되지 않습니다.

initrd (레거시 RAM 디스크)

initrd(Initial RAM Disk)는 커널 2.0부터 사용된 레거시 초기 루트 파일시스템 메커니즘입니다. 부트로더가 압축된 디스크 이미지를 메모리에 로드하면, 커널이 이를 /dev/ram0(BRD)에 기록하고 파일시스템으로 마운트하여 초기 사용자 공간을 제공합니다.

initrd 부팅 흐름

initrd 부팅 흐름 부트로더에서 커널 로드, /dev/ram0 기록, 마운트, pivot_root, 실제 루트 파일시스템 전환까지의 흐름 부트로더 GRUB/U-Boot initrd 이미지 로드 커널 시작 start_kernel() initrd 주소 전달받음 /dev/ram0 기록 rd_load_image() gz 해제 → BRD에 복사 마운트 mount /dev/ram0 / ext2/minix 파일시스템 /linuxrc 실행 모듈 로드, HW 탐색 실제 루트 준비 pivot_root() 루트 교체 old root → /initrd/ 실제 루트 마운트 mount /dev/sda1 / ext4, xfs, btrfs ... initrd 해제 umount /initrd RAM 메모리 반환 /sbin/init 실행 systemd, SysV init 정상 부팅 완료 커널 코드 흐름 (init/do_mounts_rd.c, init/do_mounts.c) start_kernel() → kernel_init() → kernel_init_freeable() → prepare_namespace() → rd_load_image("/initrd.image") → mount_root() [/dev/ram0] → /linuxrc → pivot_root() → init ※ initrd는 메모리를 2배 사용: 원본 이미지 + /dev/ram0 복사본
그림 3. initrd 부팅 흐름 — 부트로더 → 커널 → /dev/ram0 기록 → 마운트 → pivot_root → 실제 루트

커널 코드 분석

/* init/do_mounts_rd.c — initrd 이미지 로딩 */

/*
 * rd_load_image() — initrd 이미지를 /dev/ram0에 기록
 * 부트로더가 메모리에 로드한 initrd를 BRD 디바이스에 복사
 */
static int __init rd_load_image(char *from)
{
    int nblocks, i;
    struct file *in_file, *out_file;

    /* initrd 원본 열기 (커널이 물리 주소를 /initrd.image로 매핑) */
    in_file = filp_open(from, O_RDONLY, 0);

    /* /dev/ram0 출력 열기 */
    out_file = filp_open("/dev/ram0", O_RDWR, 0);

    /* 이미지가 gzip/bzip2 압축인지 확인 */
    identify_ramdisk_image(in_file, &decompressor);

    /* 블록 단위로 복사 */
    for (i = 0; i < nblocks; i++) {
        kernel_read(in_file, buf, BLOCK_SIZE, &in_pos);
        kernel_write(out_file, buf, BLOCK_SIZE, &out_pos);
    }

    return 1;
}

/* init/do_mounts.c — initrd 마운트와 전환 */
void __init prepare_namespace(void)
{
    if (initrd_load()) {
        /* /dev/ram0을 루트로 마운트 성공 */
        /* /linuxrc 실행 → 실제 루트 탐색 */
        handle_initrd();
    }
    mount_root();  /* 실제 루트 파일시스템 마운트 */
}

/*
 * handle_initrd() 흐름:
 * 1. mount /dev/ram0 as root
 * 2. execute /linuxrc (사용자 스크립트)
 * 3. mount real root device
 * 4. pivot_root(new_root, put_old)
 * 5. umount old ramdisk, free memory
 */

initrd 이미지 포맷

initrd ext2 이미지 바이너리 레이아웃 ext2 기반 initrd 이미지의 슈퍼블록, 그룹 디스크립터, 비트맵, inode 테이블 구조 initrd ext2 이미지 바이너리 레이아웃 Boot Block 0~1023 Superblock 1024~2047 magic: 0xEF53 Group Desc Table 블록 2~ Block Bitmap Inode Bitmap Inode Table 128B/inode inode 1~N Data Blocks 파일/디렉터리 데이터 linuxrc, /bin, ... ext2 슈퍼블록 핵심 필드 s_magic = 0xEF53 (오프셋 56~57) s_inodes_count: 전체 inode 수 s_blocks_count: 전체 블록 수 s_log_block_size: 블록 크기 (0=1KB, 1=2KB, 2=4KB) s_first_data_block: 첫 데이터 블록 번호 initrd vs initramfs 바이너리 감지 로직 커널은 이미지의 첫 바이트로 유형을 판별합니다: • ext2 매직: 오프셋 1080에 0x53 0xEF → initrd (ext2 이미지) • cpio 매직: "070701" (ASCII) → initramfs (newc cpio) • gzip 매직: 0x1F 0x8B → 압축 해제 후 재판별 • 기타 압축: zstd(0x28B52FFD), lz4(0x02214C18), xz(0xFD377A585A) identify_ramdisk_image() 감지 순서 (init/do_mounts_rd.c) 1. 압축 매직 확인 → 해당 decompressor로 해제 → 재귀적으로 내용 확인 2. cpio 아카이브 매직 "070701" 확인 → initramfs로 처리 (rootfs에 unpack) 3. ext2 슈퍼블록 매직 0xEF53 확인 → initrd로 처리 (/dev/ram0에 복사 후 마운트)
그림 8. initrd ext2 이미지 바이너리 레이아웃과 initrd/initramfs 감지 로직
포맷매직 바이트파일시스템커널 지원
비압축파일시스템 매직ext2, minix, romfs항상
gzip 압축1f 8bext2 (일반적)CONFIG_RD_GZIP
bzip2 압축42 5aext2CONFIG_RD_BZIP2
xz 압축fd 37 7a 58 5aext2CONFIG_RD_XZ
lz4 압축02 21 4c 18ext2CONFIG_RD_LZ4
zstd 압축28 b5 2f fdext2CONFIG_RD_ZSTD
# initrd 이미지의 매직 바이트 확인 (hexdump)
hexdump -C /boot/initrd.img | head -3
# 00000000  1f 8b 08 00 00 00 00 00  00 03 ...  ← gzip 압축 (0x1F 0x8B)

# 압축 해제 후 cpio인지 ext2인지 확인
file /boot/initrd.img
# /boot/initrd.img: gzip compressed data, ...

# gzip 해제 후 내부 확인
zcat /boot/initrd.img | file -
# /dev/stdin: ASCII cpio archive (SVR4 with no CRC)  ← initramfs (cpio)

# 레거시 initrd ext2 이미지의 슈퍼블록 매직 확인
# 오프셋 1080 (0x438)에 2바이트 리틀엔디언 = 0xEF53
hexdump -C initrd-legacy.img -s 0x438 -n 2
# 00000438  53 ef                                  ← ext2 매직!
/* init/do_mounts_rd.c — identify_ramdisk_image() 핵심 로직 */

static int __init identify_ramdisk_image(int fd, int start_block,
                                          decompress_fn *decompressor)
{
    const int size = 512;  /* 첫 512바이트 읽기 */
    struct ext2_super_block *ext2sb;
    int nblocks = -1;
    unsigned char *buf;

    buf = kmalloc(size, GFP_KERNEL);
    sys_lseek(fd, start_block * BLOCK_SIZE, 0);
    sys_read(fd, buf, size);

    /* 1단계: 압축 감지 — 매직 바이트로 decompressor 선택 */
    *decompressor = decompress_method(buf, size, NULL);
    if (*decompressor) {
        printk(KERN_NOTICE "RAMDISK: compressed image found "
               "at block %d\n", start_block);
        nblocks = 0;  /* 압축 → 크기 알 수 없음 */
        goto done;
    }

    /* 2단계: ext2 슈퍼블록 확인 (오프셋 1024) */
    sys_lseek(fd, start_block * BLOCK_SIZE + 1024, 0);
    sys_read(fd, buf, sizeof(struct ext2_super_block));
    ext2sb = (struct ext2_super_block *)buf;

    if (ext2sb->s_magic == cpu_to_le16(EXT2_SUPER_MAGIC)) {
        /* ext2 이미지 → initrd로 처리 */
        nblocks = le32_to_cpu(ext2sb->s_blocks_count);
        printk(KERN_NOTICE "RAMDISK: ext2 filesystem found "
               "at block %d\n", start_block);
        goto done;
    }

    /* 3단계: romfs, minix 등 기타 파일시스템 확인 */
    ...

done:
    kfree(buf);
    return nblocks;  /* -1 = 인식 불가, 0 = 압축, >0 = 블록 수 */
}

initrd 생성 방법

# 레거시 initrd 이미지 수동 생성 (ext2 기반)
dd if=/dev/zero of=initrd.img bs=1M count=32
mkfs.ext2 -F initrd.img
mkdir /tmp/initrd_mount
mount -o loop initrd.img /tmp/initrd_mount

# 필요한 파일 복사
cp -a /bin/busybox /tmp/initrd_mount/bin/
# /linuxrc 스크립트 생성
cat > /tmp/initrd_mount/linuxrc << 'SCRIPT'
#!/bin/busybox sh
/bin/busybox --install -s /bin
mount -t proc proc /proc
mount -t sysfs sysfs /sys
# 드라이버 모듈 로드
insmod /lib/modules/scsi_mod.ko
insmod /lib/modules/sd_mod.ko
# 실제 루트 디바이스 설정
echo 0x0801 > /proc/sys/kernel/real-root-dev
SCRIPT
chmod +x /tmp/initrd_mount/linuxrc

umount /tmp/initrd_mount
gzip initrd.img  # → initrd.img.gz
레거시 주의: initrd는 현재 거의 사용되지 않습니다. 메모리를 이중으로 사용하고(원본 + /dev/ram0 복사), pivot_root가 필요하며, 파일시스템 드라이버가 커널에 내장되어야 합니다. 새로운 시스템에서는 항상 initramfs를 사용하세요.

initramfs (cpio 기반)

initramfs는 커널 2.6에서 도입된 현대적 초기 루트 파일시스템 메커니즘입니다. cpio 아카이브를 커널이 직접 rootfs(tmpfs 인스턴스)에 풀어놓고 /init을 실행합니다. BRD 블록 디바이스가 불필요하고, 메모리 사용도 효율적입니다.

커널 언패킹 코드

initramfs 언패킹 흐름 cpio 아카이브 파싱에서 rootfs에 파일 생성, /init 실행까지의 흐름 initramfs 언패킹 파이프라인 빌트인 initramfs CONFIG_INITRAMFS_SOURCE .init.ramfs 섹션에 링크 외부 initramfs 부트로더가 메모리에 로드 initrd_start ~ initrd_end 압축 해제 gunzip / unlz4 / unzstd decompress_fn() cpio 파서 unpack_to_rootfs() 헤더: "070701" (newc) 파일명, 모드, uid, 크기 → 파일 데이터 추출 rootfs 생성 (tmpfs 인스턴스) do_mkdir() → 디렉터리 do_mknod() → 디바이스 노드 do_symlink() → 심볼릭 링크 init_write() → 파일 데이터 do_chmod/chown() → 권한 /init 실행 (PID 1) run_init_process("/init") switch_root 실제 루트로 전환 실제 /sbin/init 실행 systemd / SysV init init/initramfs.c 핵심 함수 호출 체인 populate_rootfs() → unpack_to_rootfs(__initramfs_start, __initramfs_size) → [외부 initramfs] unpack_to_rootfs(initrd_start, initrd_end - initrd_start) ※ initramfs는 블록 디바이스 없이 직접 tmpfs에 풀어놓으므로 메모리 이중 사용 없음
그림 4. initramfs 언패킹 — cpio 아카이브를 파싱하여 rootfs(tmpfs)에 직접 파일 생성
/* init/initramfs.c — 핵심 언패킹 코드 */

/*
 * populate_rootfs() — initramfs 메인 진입점
 * 빌트인 + 외부 initramfs를 순서대로 rootfs에 풀어놓음
 */
static int __init populate_rootfs(void)
{
    /* 1. 빌트인 initramfs (항상 존재, 최소 /dev/console 포함) */
    err = unpack_to_rootfs(__initramfs_start, __initramfs_size);

    /* 2. 외부 initramfs (부트로더가 전달한 경우) */
    if (initrd_start) {
        err = unpack_to_rootfs((char *)initrd_start,
                               initrd_end - initrd_start);
        if (!err) {
            free_initrd();  /* 원본 메모리 해제 */
            return 0;
        }
        /* cpio가 아니면 레거시 initrd로 폴백 */
        ...
    }
    return 0;
}

/*
 * unpack_to_rootfs() — cpio 아카이브 언패킹
 * newc 포맷 (070701) 파싱 → 파일/디렉터리/링크 생성
 */
static int __init unpack_to_rootfs(char *buf, unsigned long len)
{
    /* 압축 감지 및 해제 */
    decompress_fn decompress = decompress_method(buf, len, &compress_name);
    if (decompress) {
        /* gz/lz4/zstd 등 압축 해제 → cpio 스트림 */
        int res = decompress(buf, len, NULL, flush_buffer, ...);
    }

    /* cpio 엔트리 순회 */
    while (!eof) {
        if (S_ISDIR(mode))
            do_mkdir(name, mode);           /* 디렉터리 생성 */
        else if (S_ISREG(mode))
            do_create(name, mode, body);    /* 일반 파일 생성 */
        else if (S_ISLNK(mode))
            do_symlink(name, target);       /* 심볼릭 링크 */
        else
            do_mknod(name, mode, rdev);     /* 디바이스 노드 */
    }
    return 0;
}

빌트인 initramfs

모든 리눅스 커널에는 최소한의 빌트인 initramfs가 포함됩니다. CONFIG_INITRAMFS_SOURCE를 설정하면 커널 이미지에 직접 initramfs를 내장할 수 있습니다.

# .config에서 빌트인 initramfs 설정
CONFIG_INITRAMFS_SOURCE="/path/to/initramfs_list"

# initramfs_list 예시 (gen_init_cpio 포맷)
# 형식: file/dir/nod/slink <name> <mode> <uid> <gid> [추가인자]
dir  /dev              0755  0  0
nod  /dev/console      0600  0  0  c  5  1
nod  /dev/null         0666  0  0  c  1  3
dir  /bin              0755  0  0
file /init             /path/to/init       0755  0  0
file /bin/busybox      /path/to/busybox    0755  0  0
slink /bin/sh          /bin/busybox        0777  0  0

# 또는 디렉터리를 직접 지정
CONFIG_INITRAMFS_SOURCE="/my/initramfs/root"

# 빌드 시 커널 이미지에 포함됨
make bzImage  # vmlinuz 안에 initramfs 포함

외부 initramfs

대부분의 배포판은 외부 initramfs 파일을 별도로 생성하여 부트로더에서 커널과 함께 로드합니다.

# GRUB 설정 예시
menuentry 'Linux' {
    linux  /vmlinuz root=/dev/sda2 ro
    initrd /initramfs.img    # 외부 initramfs
}

# initramfs 내용 확인
file /boot/initramfs-$(uname -r).img
# → /boot/initramfs-6.1.0.img: gzip compressed data

# 내용 추출
mkdir /tmp/initramfs && cd /tmp/initramfs
zcat /boot/initramfs-$(uname -r).img | cpio -idmv
# 또는 (microcode + main cpio 결합 형태)
skipcpio /boot/initramfs-$(uname -r).img | zcat | cpio -idmv

# 디렉터리 구조 확인
ls -la
# init -> /usr/lib/systemd/systemd  (또는 /init 스크립트)
# bin/ sbin/ lib/ etc/ dev/ proc/ sys/ ...
# microcode + initramfs 결합 이미지 분석
# 최신 배포판은 CPU microcode + 실제 initramfs를 하나의 파일에 결합
# 첫 번째 cpio = microcode, 두 번째 cpio = initramfs

# 크기별 분석
cpio -t < /boot/initramfs-$(uname -r).img 2>/dev/null | head -5
# early_cpio 부분 → kernel/x86/microcode/AuthenticAMD.bin

# initramfs 전체 크기와 구성 파악
lsinitrd /boot/initramfs-$(uname -r).img 2>/dev/null | tail -20
# 또는 Debian/Ubuntu:
lsinitramfs /boot/initrd.img-$(uname -r) 2>/dev/null | wc -l
# → 일반적으로 500~3000개 파일

initramfs 생성

# Debian/Ubuntu: mkinitramfs
mkinitramfs -o /boot/initramfs-$(uname -r).img $(uname -r)

# RHEL/Fedora: dracut
dracut /boot/initramfs-$(uname -r).img $(uname -r)

# dracut 옵션 예시
dracut --force --verbose \
    --add "lvm dm crypt" \
    --drivers "ahci nvme" \
    /boot/initramfs-custom.img $(uname -r)

# 수동 cpio 생성
cd /my/initramfs/root
find . | cpio -o -H newc | gzip > /boot/initramfs-custom.img

# 수동 cpio 생성 (zstd 압축, 더 빠름)
find . | cpio -o -H newc | zstd -19 > /boot/initramfs-custom.img.zst
#!/bin/sh
# === 최소 initramfs /init 스크립트 예시 ===
# initramfs 내 /init으로 저장 (실행 권한 필수)

mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev

# 커널 모듈 로드 (필요시)
modprobe ext4
modprobe nvme

# 실제 루트 마운트
ROOTDEV=$(cat /proc/cmdline | sed 's/.*root=\([^ ]*\).*/\1/')
mount -o ro "$ROOTDEV" /mnt/root

# switch_root: rootfs를 정리하고 새 루트로 전환
exec switch_root /mnt/root /sbin/init
# dracut 모듈 구조 (모듈 방식 initramfs 생성)
# /usr/lib/dracut/modules.d/
#   90lvm/          — LVM 볼륨 활성화
#   90crypt/        — LUKS 암호화 볼륨 해제
#   95rootfs-block/  — 블록 디바이스 루트 마운트
#   99base/          — 기본 init 프레임워크

# dracut으로 특정 모듈만 포함한 최소 initramfs 생성
dracut --no-hostonly --force \
    --modules "base rootfs-block" \
    --drivers "ext4 nvme" \
    /boot/initramfs-minimal.img $(uname -r)

initrd vs initramfs 비교

속성initrd (레거시)initramfs (현대)
이미지 포맷파일시스템 이미지 (ext2)cpio 아카이브
커널 저장소/dev/ram0 (BRD 블록 디바이스)rootfs (tmpfs 인스턴스)
메모리 사용이중 사용 (원본 + ram0 복사)단일 사용 (직접 rootfs에 풀기)
루트 전환pivot_root()switch_root (busybox/klibc)
초기 실행 파일/linuxrc/init
블록 디바이스 필요예 (CONFIG_BLK_DEV_RAM)아니오
파일시스템 드라이버커널 내장 필요 (ext2 등)불필요 (tmpfs 자동 지원)
크기 제한rd_size 파라미터없음 (가용 메모리까지)
소스 코드init/do_mounts_rd.cinit/initramfs.c
CONFIGCONFIG_BLK_DEV_RAMCONFIG_BLK_DEV_INITRD
현재 상태레거시 (비권장)표준 (모든 배포판)
마이그레이션 가이드: 레거시 initrd에서 initramfs로 전환하려면: ① /linuxrc/init으로 변경, ② pivot_rootswitch_root로 교체, ③ ext2 이미지 대신 find . | cpio -o -H newc | gzip으로 cpio 아카이브 생성, ④ CONFIG_BLK_DEV_RAM 의존성 제거. 대부분의 배포판은 dracut(Fedora/RHEL) 또는 mkinitramfs(Debian/Ubuntu)로 자동 생성합니다.
/* init/initramfs.c — initramfs vs initrd 자동 판별 핵심 코드 */

/*
 * populate_rootfs()에서 initramfs와 initrd를 자동 판별:
 * 1. __initramfs_start에 빌트인 initramfs가 있으면 먼저 풀기
 * 2. initrd_start가 설정되어 있으면:
 *    a. cpio 매직("070701")이면 → initramfs로 풀기
 *    b. ext2 매직(0xEF53)이면 → /dev/ram0에 복사 (레거시 initrd)
 *    c. 압축되어 있으면 → 해제 후 재판별
 */
static int __init populate_rootfs(void)
{
    /* 빌트인 initramfs 풀기 (항상 존재, 최소 빈 cpio) */
    unpack_to_rootfs(__initramfs_start, __initramfs_size);

    if (initrd_start) {
        /* 외부 initrd/initramfs 처리 */
        err = unpack_to_rootfs((char *)initrd_start,
                               initrd_end - initrd_start);
        if (err) {
            /* cpio가 아님 → 레거시 initrd로 처리 */
            /* /initrd.image에 저장 후 rd_load_image()로 /dev/ram0 복사 */
            clean_rootfs();
            populate_initrd_image(initrd_start);
        }
        free_initrd();
    }
    return 0;
}

zram 압축 블록 디바이스

zram은 메모리 내 압축 블록 디바이스입니다. 데이터를 쓰면 실시간으로 압축하여 저장하고, 읽을 때 해제하여 반환합니다. 주로 스왑 파티션으로 사용하여 메모리 용량을 실질적으로 2~3배 늘리는 효과를 얻습니다. Android, Chrome OS, Fedora 등에서 기본 활성화되어 있습니다.

zram 아키텍처

zram 압축 파이프라인 zram의 쓰기(압축) 및 읽기(해제) 데이터 흐름과 zsmalloc 메모리 관리 zram 압축/해제 파이프라인 WRITE PATH (압축 저장) BIO (4KB 페이지) submit_bio(WRITE) zram_write_page() 페이지 데이터 추출 zcomp 압축 lzo_rle / lz4 / zstd 4KB → ~1.5KB (평균) same_page 체크 (0-filled?) zsmalloc zs_malloc(size) 압축 데이터 저장 완료 bio_endio READ PATH (해제 반환) BIO (READ) submit_bio(READ) zram_read_page() zram_table 조회 zsmalloc 읽기 zs_map_object(handle) zcomp 해제 decompress → 4KB 4KB 반환 bio_endio zram_table_entry 구조 (섹터별 메타데이터) entry[0] handle | flags entry[1] handle | flags entry[2] SAME_PAGE entry[3] handle | flags ... zsmalloc 풀 크기 클래스별 슬랩 페이지 관리 작은 객체를 페이지에 빽빽하게 배치 handle → zsmalloc 내 오프셋
그림 5. zram 압축 파이프라인 — WRITE: 페이지→압축→zsmalloc 저장, READ: zsmalloc→해제→페이지 반환

주요 자료구조

/* drivers/block/zram/zram_drv.h */

struct zram {
    struct zram_table_entry *table;  /* 섹터별 메타데이터 배열 */
    struct zs_pool *mem_pool;        /* zsmalloc 메모리 풀 */
    struct zcomp *comps[ZRAM_MAX_COMPS]; /* 압축 알고리즘 */
    struct gendisk *disk;            /* 블록 디바이스 */
    struct rw_semaphore init_lock;   /* 초기화 락 */

    /* 통계 */
    atomic64_t stats[NR_ZRAM_STAT_ITEM];
    u64 disksize;                    /* 디스크 크기 (비압축 기준) */

    /* Writeback 관련 */
    struct block_device *backing_dev;  /* 백킹 디바이스 */
    spinlock_t wb_limit_lock;
    unsigned long *bitmap;             /* writeback 비트맵 */
    ...
};

struct zram_table_entry {
    union {
        unsigned long handle;     /* zsmalloc 핸들 (압축 데이터 위치) */
        unsigned long element;    /* same_element 값 */
    };
    unsigned int flags;           /* ZRAM_SAME, ZRAM_WB, ZRAM_HUGE 등 */
#ifdef CONFIG_ZRAM_TRACK_ENTRY_ACTIME
    ktime_t ac_time;              /* 접근 시간 (writeback 판단용) */
#endif
};

/* 플래그 비트 */
#define ZRAM_SAME       (1 << 0)  /* 모든 바이트가 동일 (예: 0으로 채워진 페이지) */
#define ZRAM_WB         (1 << 1)  /* backing device로 writeback됨 */
#define ZRAM_HUGE       (1 << 2)  /* 압축이 비효율적 (원본 저장) */
#define ZRAM_IDLE       (1 << 3)  /* 유휴 상태 (writeback 후보) */

압축 알고리즘

알고리즘CONFIG압축률속도특징
lzo-rleCONFIG_CRYPTO_LZO중간 (~2:1)빠름커널 기본값, RLE 최적화
lz4CONFIG_CRYPTO_LZ4낮음 (~1.8:1)매우 빠름압축/해제 속도 최우선
zstdCONFIG_CRYPTO_ZSTD높음 (~2.8:1)보통Facebook 개발, 높은 압축률
lzoCONFIG_CRYPTO_LZO중간 (~2:1)빠름레거시, lzo-rle 권장
842CONFIG_CRYPTO_842중간보통IBM POWER 하드웨어 가속
deflateCONFIG_CRYPTO_DEFLATE높음 (~3:1)느림zlib, CPU 부하 높음
# zram 압축 알고리즘 변경
echo lz4 > /sys/block/zram0/comp_algorithm

# 사용 가능한 알고리즘 확인 (현재 선택은 [ ] 표시)
cat /sys/block/zram0/comp_algorithm
# lzo lzo-rle [lz4] zstd 842 deflate

# 다중 압축 스트림 (커널 6.2+)
# 기본(빠른) + 보조(높은 압축률) 이중 압축
echo zstd > /sys/block/zram0/recomp_algorithm
echo type=huge > /sys/block/zram0/recompress  # 비효율 페이지 재압축

zram writeback

zram writeback은 유휴(idle) 페이지나 비압축(huge) 페이지를 실제 디스크로 이동하여 메모리를 추가로 확보하는 기능입니다. 커널 4.14에서 도입되었습니다.

zram writeback 파이프라인 idle marking부터 backing device 기록, bitmap 갱신까지의 writeback 흐름 zram writeback 파이프라인 1. Idle 마킹 echo all > idle 모든 entry에 IDLE 플래그 2. 접근 추적 읽기/쓰기 시 IDLE 해제 활발한 페이지 보호 3. Writeback 실행 echo idle > writeback IDLE 페이지만 대상 4. 해제 + 기록 zsmalloc → 해제 → backing_dev 쓰기 5. 메타 갱신 ZRAM_WB 플래그 bitmap 갱신 zram_table_entry 상태 변화 Normal handle + flags=0 Idle handle + IDLE Written Back bd_idx + WB writeback 후: handle 해제 → zsmalloc 메모리 반환 → bd_idx로 교체 Backing Device 구조 블록 0 블록 1 (free) 블록 3 bitmap: [1][1][0][1]... (사용중/free 추적) 각 블록 = PAGE_SIZE (4KB), 비압축 데이터 저장 Writeback된 페이지 읽기 경로 (read-back) zram_read_page() ZRAM_WB 확인 flags & ZRAM_WB? backing_dev에서 읽기 bio_read(bd_idx * PAGE_SIZE) 페이지 반환 ※ 디스크 I/O 발생!
그림 9. zram writeback 파이프라인 — idle 마킹 → backing device 기록 → bitmap 관리
# 1. backing device 설정 (초기화 전에)
echo /dev/sdb1 > /sys/block/zram0/backing_dev

# 2. zram 디스크 크기 설정 및 활성화
echo 4G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon /dev/zram0

# 3. 유휴 페이지 마킹
echo all > /sys/block/zram0/idle  # 모든 페이지를 idle로 마킹

# 4. writeback 실행
echo idle > /sys/block/zram0/writeback     # idle 페이지 writeback
echo huge > /sys/block/zram0/writeback     # huge(비압축) 페이지 writeback
echo huge_idle > /sys/block/zram0/writeback # 둘 다

# 5. writeback 제한 (바이트 단위)
echo $((512*1024*1024)) > /sys/block/zram0/writeback_limit  # 512MB 제한
/* drivers/block/zram/zram_drv.c — writeback 핵심 함수 */

static int zram_writeback_page(struct zram *zram, u32 index)
{
    struct bio bio;
    struct bio_vec bvec;
    struct page *page;
    unsigned long handle;
    int ret;

    /* 1. 플래그 확인: IDLE 또는 HUGE만 writeback 대상 */
    if (!zram_test_flag(zram, index, ZRAM_IDLE) &&
        !zram_test_flag(zram, index, ZRAM_HUGE))
        return 0;

    /* 이미 writeback 된 페이지는 건너뜀 */
    if (zram_test_flag(zram, index, ZRAM_WB))
        return 0;

    /* 2. backing device의 빈 블록 찾기 (bitmap 검색) */
    unsigned long blk_idx;
    blk_idx = alloc_block_bdev(zram);
    if (!blk_idx)
        return -ENOSPC;  /* backing 공간 부족 */

    /* 3. zsmalloc에서 압축 데이터 읽기 → 해제 → 임시 페이지 */
    page = alloc_page(GFP_NOIO);
    handle = zram->table[index].handle;
    src = zs_map_object(zram->mem_pool, handle, ZS_MM_RO);
    zcomp_decompress(zram->comps[ZRAM_PRIMARY_COMP],
                     src, PAGE_SIZE, page_address(page));
    zs_unmap_object(zram->mem_pool, handle);

    /* 4. backing device에 페이지 크기(4KB) 기록 */
    bio_init(&bio, zram->backing_dev, &bvec, 1,
             REQ_OP_WRITE | REQ_SYNC);
    bio.bi_iter.bi_sector = blk_idx * (PAGE_SIZE >> 9);
    bio_add_page(&bio, page, PAGE_SIZE, 0);
    ret = submit_bio_wait(&bio);

    /* 5. 메타데이터 갱신 */
    if (!ret) {
        /* zsmalloc 핸들 해제 → 메모리 반환! */
        zs_free(zram->mem_pool, handle);

        /* entry를 WB 상태로 전환 */
        zram_set_flag(zram, index, ZRAM_WB);
        zram_set_element(zram, index, blk_idx);
        /* handle → blk_idx (backing device 오프셋) */

        atomic64_inc(&zram->stats[ZRAM_WB_COUNT]);
    }

    __free_page(page);
    return ret;
}
/* 커널 6.2+: recompression — huge 페이지를 더 강한 알고리즘으로 재압축 */

/*
 * lz4로 1차 압축한 페이지 중 압축률이 낮은(huge) 것을
 * zstd로 재압축하여 메모리 절약. writeback 전 시도.
 *
 * echo algo=zstd > /sys/block/zram0/recomp_algorithm
 * echo type=huge > /sys/block/zram0/recompress
 */
static int zram_recompress(struct zram *zram, u32 index,
                           struct page *page, int *comp_len_p,
                           u32 prio)
{
    /* 1차 압축 데이터를 해제 */
    src = zs_map_object(zram->mem_pool, handle, ZS_MM_RO);
    zcomp_decompress(zram->comps[ZRAM_PRIMARY_COMP], ...);

    /* 보조 알고리즘(zstd 등)으로 재압축 */
    ret = zcomp_compress(zram->comps[prio], page, dst, comp_len_p);

    if (*comp_len_p < old_comp_len) {
        /* 재압축 성공: 새 핸들 할당 후 교체 */
        new_handle = zs_malloc(zram->mem_pool, *comp_len_p, ...);
        zs_free(zram->mem_pool, old_handle);
        /* → 메모리 절약 */
    }
    return ret;
}
Backing Device장점단점권장 시나리오
HDD저렴, 대용량랜덤 I/O 매우 느림순차 writeback 위주, 읽기 빈도 낮은 경우
SSD (SATA)빠른 랜덤 I/O쓰기 수명(WAF)일반 데스크톱/서버
NVMe SSD최고 성능비용고성능 워크로드
파일 (루프)유연한 크기이중 파일시스템 오버헤드테스트/개발 환경
# writeback 제한(limit) 관리
# writeback_limit은 backing device 사용량 제한 (4KB 페이지 단위)
echo 131072 > /sys/block/zram0/writeback_limit  # 512MB 제한 (131072 * 4KB)
echo 1 > /sys/block/zram0/writeback_limit_enable

# backing device 통계 확인
cat /sys/block/zram0/bd_stat
# bd_count  bd_reads  bd_writes
# 12345     6789      12345
# writeback된 페이지 수 / backing에서 읽은 횟수 / 쓴 횟수
Writeback 메모리 절약 효과: 일반적인 데스크톱 워크로드에서 zram 4GB 중 약 30~40%가 idle 상태입니다. 이를 writeback하면 1.2~1.6GB의 RAM을 추가 확보할 수 있습니다. 단, 해당 페이지를 다시 읽을 때 디스크 I/O가 발생하므로, backing device가 SSD인 경우에 효과적입니다. HDD를 사용하면 성능이 급격히 저하될 수 있으므로 writeback_limit을 반드시 설정하세요.

zsmalloc 내부 구조

zsmalloc은 zram 전용 메모리 할당자로, 가변 크기 압축 데이터를 효율적으로 저장합니다. 일반 slab/slub 할당자는 고정 크기 객체에 최적화되어 있어 가변 크기 압축 데이터에는 내부 단편화가 심합니다. zsmalloc은 여러 물리 페이지를 하나의 zspage로 결합하여 단편화를 최소화합니다.

zsmalloc 아키텍처 size_class에서 zspage, 물리 페이지, object slots까지의 계층 구조 zsmalloc 메모리 할당자 아키텍처 zs_pool (zram->mem_pool) size_class[0..254] — 16바이트 단위 크기 클래스 배열 size_class[0] size: 32B pages/zspage: 1 size_class[8] size: 160B pages/zspage: 1 size_class[64] size: 1056B pages/zspage: 2 size_class[128] size: 2080B pages/zspage: 3 size_class[254] size: 4096B pages/zspage: 1 ... Fullness Groups (size_class[0]) EMPTY A_EMPTY A_FULL FULL zspage (size_class[64], 2 pages) 물리 페이지 0 (4KB) [obj0][obj1][obj2][obj3|→ 물리 페이지 1 (4KB) →obj3][obj4][obj5][obj6] ← 객체가 페이지 경계를 넘을 수 있음 handle 인코딩 (unsigned long) handle = (zspage 첫 페이지 PFN << OBJ_INDEX_BITS) | obj_index → zs_map_object()로 가상 주소 변환 (kmap 사용) zs_compact() — 메모리 컴팩션 ALMOST_EMPTY zspage의 객체를 ALMOST_FULL zspage로 이동하여 빈 페이지를 회수합니다. echo 1 > /sys/block/zram0/compact 로 수동 실행 가능. pages_compacted (mm_stat 필드)로 확인합니다.
그림 10. zsmalloc 아키텍처 — size_class → zspage → 물리 페이지 → object 슬롯 계층
/* mm/zsmalloc.c — 핵심 API */

/* zs_malloc: 압축 데이터 저장 공간 할당 */
unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t gfp)
{
    struct size_class *class;
    struct zspage *zspage;
    unsigned long handle;

    /* 1. 크기 클래스 결정 (16바이트 단위 올림) */
    class = pool->size_class[get_size_class_index(size)];

    /* 2. ALMOST_EMPTY에서 빈 슬롯이 있는 zspage 검색 */
    zspage = find_get_zspage(class);
    if (!zspage) {
        /* 3. 새 zspage 할당 (class->pages_per_zspage개의 페이지) */
        zspage = alloc_zspage(pool, class, gfp);
    }

    /* 4. 빈 슬롯에서 obj_index 할당 */
    obj = obj_malloc(pool, zspage, handle);

    /* 5. fullness group 재분류 */
    fix_fullness_group(class, zspage);

    return handle;  /* PFN + obj_index 인코딩 */
}

/* zs_map_object: handle → 가상 주소 매핑 */
void *zs_map_object(struct zs_pool *pool, unsigned long handle,
                    enum zs_mapmode mm)
{
    /* handle에서 PFN, obj_index 디코딩 */
    obj_to_location(handle, &page, &obj_idx);

    /* 객체가 페이지 경계를 넘는 경우:
     * 두 페이지를 연속 가상 주소로 매핑 (kmap) */
    if (ZS_HANDLE_SIZE + size > PAGE_SIZE) {
        /* cross-page mapping */
        area->vm_buf = kmap_local_page(page);
        area->vm_buf2 = kmap_local_page(next_page);
        memcpy(area->vm_buf, ...);  /* 임시 버퍼로 복사 */
    }
    return area->vm_addr + obj_offset;
}

/* zs_free: 객체 해제 */
void zs_free(struct zs_pool *pool, unsigned long handle)
{
    obj_to_location(handle, &page, &obj_idx);
    zspage = get_zspage(page);
    class = zspage_class(pool, zspage);

    obj_free(class->size, handle);  /* 프리리스트에 반환 */

    /* zspage가 완전히 비었으면 페이지 해제 */
    if (get_fullness_group(class, zspage) == ZS_EMPTY) {
        free_zspage(pool, class, zspage);
    }
}
크기 범위size_class indexpages_per_zspageobjects_per_zspage외부 단편화율
32~48B0~1185~128<2%
64~256B2~14116~64<5%
272~1024B15~621~24~15<10%
1040~2048B63~1262~33~7<15%
2064~3072B127~1903~44~5<20%
3088~4096B191~2541 (PAGE_SIZE)10% (전체 페이지)
zsmalloc vs slab/slub: slab/slub은 고정 크기 객체(예: struct inode 전용 캐시)에 최적화되어 있습니다. 그러나 zram의 압축 데이터는 32B~4KB까지 크기가 극도로 다양합니다. slab을 사용하면 예를 들어 500B 압축 데이터를 512B 슬랩 객체에 넣어야 하고, 다른 크기의 데이터는 각각 다른 캐시가 필요합니다. zsmalloc은 16B 단위의 세밀한 크기 클래스와 페이지 경계를 넘는 객체 배치로 내부 단편화를 최대 15%에서 최대 3%로 줄입니다.

zcomp 압축 스트림

zram의 압축/해제는 zcomp(zram compression) 계층을 통해 수행됩니다. 각 CPU에 전용 압축 스트림(zcomp_strm)을 할당하여 락 경합 없이 병렬 처리합니다.

per-CPU zcomp 압축 스트림 CPU별 zcomp_strm 할당과 crypto_comp 연결 구조 zcomp per-CPU 압축 스트림 구조 struct zcomp (per zram device) per_cpu_ptr CPU 0 — zcomp_strm tfm: crypto_comp * buffer: void * (PAGE_SIZE*2) ← 압축 출력 버퍼 CPU 1 — zcomp_strm tfm: crypto_comp * buffer: void * (PAGE_SIZE*2) ← 독립 인스턴스 CPU 2 — zcomp_strm tfm: crypto_comp * buffer: void * (PAGE_SIZE*2) CPU N — zcomp_strm tfm: crypto_comp * buffer: void * (PAGE_SIZE*2) 커널 Crypto API (crypto_comp) — lzo-rle / lz4 / zstd / ... ※ per-CPU 구조로 락 없이 병렬 압축/해제. get_cpu_ptr()/put_cpu_ptr()로 현재 CPU의 스트림을 가져옵니다.
그림 11. zcomp per-CPU 압축 스트림 — CPU마다 독립적인 crypto_comp 인스턴스와 작업 버퍼
/* drivers/block/zram/zcomp.c — per-CPU 스트림 관리 */

struct zcomp_strm {
    struct crypto_comp *tfm;  /* 커널 crypto API 핸들 */
    void *buffer;             /* 압축 출력 버퍼 (PAGE_SIZE * 2) */
};

struct zcomp {
    struct zcomp_strm __percpu *stream;  /* per-CPU 스트림 배열 */
    const char *name;                     /* 알고리즘 이름 */
};

/* 현재 CPU의 스트림 획득 (preempt 비활성화) */
struct zcomp_strm *zcomp_stream_get(struct zcomp *comp)
{
    return get_cpu_ptr(comp->stream);
    /* preempt_disable() → 현재 CPU의 zcomp_strm 반환
     * 다른 CPU로 마이그레이션 방지 → 락 불필요 */
}

void zcomp_stream_put(struct zcomp *comp)
{
    put_cpu_ptr(comp->stream);
    /* preempt_enable() */
}

/* 압축 실행 */
int zcomp_compress(struct zcomp *comp, struct page *page,
                   void *dst, unsigned int *dst_len)
{
    struct zcomp_strm *strm = zcomp_stream_get(comp);

    /* 커널 crypto API로 압축 */
    *dst_len = PAGE_SIZE * 2;  /* 최대 출력 크기 */
    ret = crypto_comp_compress(strm->tfm,
            page_address(page), PAGE_SIZE,  /* 입력: 4KB */
            strm->buffer, dst_len);          /* 출력: 압축 데이터 */

    memcpy(dst, strm->buffer, *dst_len);
    zcomp_stream_put(comp);
    return ret;
}
알고리즘압축 처리량 (MB/s)해제 처리량 (MB/s)압축률 (평균)레이턴시 (us/page)권장 환경
lzo-rle~600~9002.0:1~7범용 (커널 기본값)
lz4~750~12001.8:1~5지연 민감 (Android, 데스크톱)
zstd~350~7002.8:1~12메모리 제약 (임베디드, 서버)
lzo~550~8502.0:1~8레거시 호환
deflate~150~4003.0:1~27최대 압축 (CPU 여유 시)
842~500 (HW)~800 (HW)2.2:1~6 (HW)IBM POWER (하드웨어 가속)

sysfs 인터페이스

경로R/W설명
/sys/block/zram0/disksizeRW디스크 크기 (비압축 기준, 예: 4G)
/sys/block/zram0/comp_algorithmRW압축 알고리즘 선택
/sys/block/zram0/mem_used_totalR실제 사용 메모리 (압축 후)
/sys/block/zram0/mem_limitRW최대 메모리 사용량 제한
/sys/block/zram0/max_comp_streamsRW동시 압축 스트림 수
/sys/block/zram0/mm_statR메모리 통계 (원본/압축/사용/제한/max/같은/페이지)
/sys/block/zram0/io_statRI/O 통계 (읽기/쓰기/실패)
/sys/block/zram0/bd_statRbacking device 통계
/sys/block/zram0/backing_devRWwriteback 대상 블록 디바이스
/sys/block/zram0/idleW유휴 페이지 마킹 (all)
/sys/block/zram0/writebackWwriteback 실행 (idle/huge)
/sys/block/zram0/resetWzram 리셋 (1 기록)
# mm_stat 해석 (공백 구분, 9개 필드)
cat /sys/block/zram0/mm_stat
# orig_data_size  compr_data_size  mem_used_total  mem_limit  max_used
# same_pages  pages_compacted  huge_pages  huge_pages_since

# 예시 해석:
# 2147483648  715827882  734003200  0  891289600  12345  0  567  890
# 원본 2GB → 압축 후 682MB → 실제 메모리 700MB (오버헤드 포함)
# 압축률: 715827882 / 2147483648 ≈ 33% (3:1 압축)

zram swap 설정

# === 기본 zram swap 설정 ===

# 1. 모듈 로드 (num_devices로 디바이스 수 지정)
modprobe zram num_devices=1

# 2. 압축 알고리즘 선택
echo lz4 > /sys/block/zram0/comp_algorithm

# 3. 디스크 크기 설정 (물리 RAM의 50~100%)
echo 4G > /sys/block/zram0/disksize

# 4. 스왑으로 활성화 (높은 우선순위)
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# 5. 확인
swapon --show
# NAME       TYPE      SIZE  USED PRIO
# /dev/zram0 partition 4G    1.2G 100

# === systemd-zram-setup (Fedora/Arch) ===
# /etc/systemd/zram-generator.conf
# [zram0]
# zram-size = ram / 2
# compression-algorithm = zstd
# swap-priority = 100

# === 해제 ===
swapoff /dev/zram0
echo 1 > /sys/block/zram0/reset
성능 팁: zram swap의 우선순위(-p)를 디스크 swap보다 높게 설정하세요. 커널은 높은 우선순위의 swap을 먼저 사용합니다. 일반적으로 zram은 100, 디스크는 1로 설정합니다.

메모리 백킹 메커니즘

RAM 디스크의 네 가지 유형은 각각 다른 메모리 관리 전략을 사용합니다. 이 차이가 성능, 메모리 효율, 기능의 근본적인 차이를 만듭니다.

메모리 백킹 메커니즘 비교 페이지 캐시, XArray, zsmalloc 3가지 백킹 메커니즘의 데이터 경로 비교 메모리 백킹 메커니즘 비교 (3가지 경로) ramfs/tmpfs 페이지 캐시 기반 BRD XArray 기반 zram zsmalloc 기반 inode → address_space i_pages (XArray) struct folio (4KB/2MB) 물리 페이지 (1:1) 오버헤드: ~64B/page (struct page 메타데이터) brd_device → brd_pages XArray (섹터→페이지) struct page (4KB 고정) 물리 페이지 (1:1) 오버헤드: ~72B/page (page + XArray 노드) zram → zram_table[] handle (zsmalloc) zspage (압축 객체) 물리 페이지 (N:1 압축) 오버헤드: ~12B/entry (table_entry + handle)
그림 12. 메모리 백킹 비교 — 페이지 캐시(1:1), XArray(1:1), zsmalloc(N:1 압축)
항목페이지 캐시 (ramfs/tmpfs)XArray (BRD)zsmalloc (zram)
per-page 메타데이터~64B (struct page)~72B (page + xa_node)~12B (table_entry)
데이터 저장 비율1:1 (비압축)1:1 (비압축)2~3:1 (압축)
4GB 데이터의 실제 메모리4GB + 64MB4GB + 72MB~1.5GB + 12MB
할당 단위PAGE_SIZE (4KB/2MB THP)PAGE_SIZE (4KB)16B 단위 가변
해제 메커니즘truncate/unlinkDISCARD/xa_erasezs_free/reset
NUMA 인식예 (mpol= 옵션)NUMA_NO_NODENUMA_NO_NODE
# NUMA 환경에서 tmpfs 메모리 할당 정책 설정
# mpol=interleave: 모든 NUMA 노드에 균등 분배
mount -t tmpfs -o size=4g,mpol=interleave tmpfs /mnt/balanced

# mpol=bind:0: 특정 노드에만 할당 (지역성 최적화)
mount -t tmpfs -o size=2g,mpol=bind:0 tmpfs /mnt/node0

# mpol=prefer:1: 노드 1 선호, 불가능하면 다른 노드
mount -t tmpfs -o size=2g,mpol=prefer:1 tmpfs /mnt/prefer1

# BRD와 zram은 NUMA 비인식 (NUMA_NO_NODE로 할당)
# → 어떤 NUMA 노드에서 페이지가 할당될지 예측 불가
어떤 백킹을 선택해야 하는가:
  • 최대 속도 필요tmpfs (블록 레이어 바이패스, VFS 직접 경로)
  • 블록 디바이스 필요 (ext4/xfs 마운트, 디바이스 매퍼) → BRD
  • 메모리 효율 필요 (스왑, 메모리 제약 환경) → zram
  • 임시 데이터, 크기 제한 필요tmpfs
  • 커널 내부/rootfsramfs (자동, 직접 사용하지 않음)

페이지 캐시 기반 (ramfs/tmpfs)

/*
 * ramfs: 페이지 캐시(address_space)를 직접 데이터 저장소로 사용
 *
 * 일반 파일시스템에서 페이지 캐시는 "디스크 데이터의 캐시"이지만,
 * ramfs에서는 페이지 캐시가 "유일한 데이터 저장소"입니다.
 * writeback이 없으므로 dirty 페이지가 영원히 메모리에 남습니다.
 */

/* 읽기: 페이지 캐시에서 직접 반환 */
static int ramfs_read_folio(struct file *file, struct folio *folio)
{
    folio_zero_range(folio, 0, folio_size(folio));
    folio_mark_uptodate(folio);
    folio_unlock(folio);
    return 0;
}

/* 쓰기: 페이지 캐시에 직접 기록 (디스크 writeback 없음) */
static int ramfs_write_begin(struct file *file,
    struct address_space *mapping, loff_t pos, unsigned len,
    struct page **pagep, void **fsdata)
{
    return simple_write_begin(file, mapping, pos, len, pagep, fsdata);
    /* simple_write_begin은 grab_cache_page_write_begin() 호출 */
    /* → 페이지 캐시에 페이지 할당/조회 */
}

/*
 * tmpfs (shmem): 유사하지만 스왑 지원
 * - shmem_get_folio()로 페이지 조회/할당
 * - 메모리 부족 시 shmem 페이지를 스왑 파일/파티션으로 이동
 * - shmem_swapin_folio()로 스왑에서 복구
 */

XArray 기반 (BRD)

/*
 * BRD: XArray로 섹터→페이지 매핑 관리
 *
 * 파일시스템의 페이지 캐시와 독립적으로 동작.
 * 블록 디바이스이므로 그 위에 어떤 파일시스템이든 마운트 가능.
 *
 * 메모리 레이아웃:
 *   XArray[0] → struct page (섹터 0~7, 4KB)
 *   XArray[1] → struct page (섹터 8~15, 4KB)
 *   XArray[2] → NULL (아직 미사용)
 *   XArray[3] → struct page (섹터 24~31, 4KB)
 *   ...
 *
 * 특징:
 * - 쓰기 시에만 페이지 할당 (온디맨드)
 * - DISCARD로 페이지 해제 가능
 * - 읽기 시 NULL이면 0 반환 (할당 없이)
 */

/* BRD 위에 ext4 마운트 예시 */
// dd → brd_submit_bio → brd_insert_page → XArray 저장
// mount → ext4 → BIO → brd_submit_bio → brd_lookup_page → XArray 조회
// fstrim → DISCARD → brd_do_discard → xa_erase → __free_page

zsmalloc 기반 (zram)

/*
 * zram: zsmalloc으로 압축 데이터 관리
 *
 * zsmalloc은 "가변 크기 작은 객체"를 효율적으로 저장하는 할당자.
 * 일반 slab/slub은 고정 크기 객체에 최적화되어 있지만,
 * 압축 데이터는 크기가 가변적이므로 zsmalloc이 적합.
 *
 * zsmalloc 내부 동작:
 * 1. 크기 클래스(size class) 결정 — 16바이트 단위
 * 2. 해당 클래스의 zspage에서 빈 슬롯 할당
 * 3. 여러 물리 페이지를 하나의 zspage로 결합 (외부 단편화 최소화)
 * 4. handle 반환 (페이지 + 오프셋 인코딩)
 */

/* 쓰기 흐름 */
static int zram_write_page(struct zram *zram, struct page *page, u32 index)
{
    unsigned int comp_len;
    unsigned long handle;
    void *dst, *src;

    src = kmap_local_page(page);

    /* 1. 전체 0인 페이지 체크 (same_page 최적화) */
    if (page_same_filled(src, &element)) {
        zram_set_flag(zram, index, ZRAM_SAME);
        zram_set_element(zram, index, element);
        kunmap_local(src);
        return 0;  /* 메모리 할당 없이 플래그만 저장 */
    }

    /* 2. 압축 */
    comp_len = PAGE_SIZE;  /* 최대 출력 크기 */
    zcomp_compress(zram->comps[ZRAM_PRIMARY_COMP],
                   src, dst, &comp_len);

    /* 3. 압축 효과 없으면 원본 저장 (huge) */
    if (comp_len >= PAGE_SIZE) {
        comp_len = PAGE_SIZE;
        zram_set_flag(zram, index, ZRAM_HUGE);
    }

    /* 4. zsmalloc 할당 + 복사 */
    handle = zs_malloc(zram->mem_pool, comp_len, GFP_NOIO);
    dst = zs_map_object(zram->mem_pool, handle, ZS_MM_WO);
    memcpy(dst, compressed_data, comp_len);
    zs_unmap_object(zram->mem_pool, handle);

    /* 5. 메타데이터 저장 */
    zram->table[index].handle = handle;
    zram->table[index].flags = ...;

    return 0;
}

블록 레이어 통합

BRD와 zram은 블록 디바이스로서 리눅스 블록 레이어와 통합됩니다. 그러나 실제 하드웨어 디스크와 달리 I/O 스케줄링이 불필요하며, BIO를 직접 처리하는 특수한 방식을 사용합니다.

BIO 처리 흐름

BRD BIO 처리 흐름 submit_bio에서 brd_submit_bio를 거쳐 페이지 복사까지의 블록 레이어 처리 흐름 파일시스템 ext4_submit_bio() BIO 생성 bio_alloc() + bio_vec submit_bio() 블록 레이어 진입 brd_submit_bio() BIO 세그먼트 순회 bio_for_each_segment() ※ 스케줄러 바이패스 brd_do_bvec() READ → brd_lookup_page() → memcpy_from_page() WRITE → brd_insert_page() → memcpy_to_page() XArray (brd_pages) xa_load() / xa_store() 섹터→페이지 인덱싱 물리 페이지 (4KB) alloc_page() / __free_page() bio_endio(bio) 완료 콜백 → 상위 레이어 통지 ※ BRD는 submit_bio 콜백을 사용하여 I/O 스케줄러와 요청 큐를 완전히 바이패스합니다 (no-queue 방식)
그림 6. BRD BIO 처리 흐름 — submit_bio → brd_submit_bio → 페이지 복사 (스케줄러 바이패스)

요청 큐 설정

zram BIO 처리 흐름 submit_bio에서 zram_submit_bio를 거쳐 압축/해제, zsmalloc 저장까지의 흐름 zram BIO 처리 흐름 (BRD rd-6과 대비) submit_bio() 블록 레이어 zram_submit_bio() bio_for_each_segment() 섹터→페이지 인덱스 변환 READ WRITE zram_read_page() table[idx] 조회 zs_map_object() 압축 데이터 읽기 zcomp 해제 → 4KB 반환 zram_write_page() same_page 체크 zcomp 압축 4KB → ~1.5KB zs_malloc() 압축 저장 BRD vs zram 큐 설정 비교 BRD (brd.c) submit_bio = brd_submit_bio physical_block_size = PAGE_SIZE max_hw_sectors = 1024 max_discard_sectors = UINT_MAX I/O 처리: memcpy (단순 페이지 복사) zram (zram_drv.c) submit_bio = zram_submit_bio physical_block_size = PAGE_SIZE logical_block_size = PAGE_SIZE (4KB 정렬 필수) io_opt = PAGE_SIZE I/O 처리: 압축/해제 + zsmalloc (CPU 집약적)
그림 13. zram BIO 처리 흐름과 BRD 대비 큐 설정 비교
/* BRD: submit_bio 방식 (no-queue) */
static const struct block_device_operations brd_fops = {
    .owner      = THIS_MODULE,
    .submit_bio = brd_submit_bio,  /* 직접 BIO 처리, 큐 없음 */
};

/* BRD 디스크 초기화 — brd_alloc() 전체 분석 */
static int brd_alloc(int i)
{
    struct brd_device *brd;
    struct gendisk *disk;

    brd = kzalloc(sizeof(*brd), GFP_KERNEL);
    xa_init(&brd->brd_pages);  /* XArray 초기화 */

    /* gendisk 할당 (NUMA 노드 무관) */
    disk = blk_alloc_disk(NUMA_NO_NODE);

    /* 디바이스 번호 설정 */
    disk->major = RAMDISK_MAJOR;  /* major 1 (/dev/ram*) */
    disk->first_minor = i * max_part;
    disk->minors = max_part;
    disk->fops = &brd_fops;
    disk->private_data = brd;
    snprintf(disk->disk_name, sizeof(disk->disk_name),
             "ram%d", i);

    /* 큐 속성 설정 */
    blk_queue_physical_block_size(disk->queue, PAGE_SIZE);
    blk_queue_max_hw_sectors(disk->queue, 1024);
    blk_queue_flag_set(QUEUE_FLAG_NONROT, disk->queue);
    /* NONROT: 회전 미디어 아님 → 스케줄러 힌트 */

    /* DISCARD 지원 — fstrim/TRIM으로 페이지 해제 */
    blk_queue_max_discard_sectors(disk->queue, UINT_MAX);

    /* 디스크 용량 (512바이트 섹터 단위) */
    set_capacity(disk, rd_size * 2);
    /* rd_size=4096(KB) → 4096*2=8192 섹터 → 4MB */

    brd->brd_disk = disk;
    list_add_tail(&brd->brd_list, &brd_devices);
    add_disk(disk);  /* /dev/ram{i} 등록 */
    return 0;
}

/*
 * zram: 마찬가지로 submit_bio 방식 사용
 * drivers/block/zram/zram_drv.c
 */
static const struct block_device_operations zram_devops = {
    .submit_bio = zram_submit_bio,
    .open       = zram_open,
    .owner      = THIS_MODULE,
};
blk_queue 설정BRD 값zram 값의미
physical_block_sizePAGE_SIZEPAGE_SIZE물리적 최소 I/O 단위
logical_block_size512 (기본)PAGE_SIZE논리적 최소 I/O 단위 (zram은 4KB 정렬 필수)
max_hw_sectors1024기본값한 번에 처리할 최대 섹터 수
io_opt설정 안 함PAGE_SIZE최적 I/O 크기
QUEUE_FLAG_NONROT설정설정회전 미디어 아님 (SSD 스케줄링 힌트)
max_discard_sectorsUINT_MAXUINT_MAXDISCARD/TRIM 지원 범위
no-queue(submit_bio) vs blk-mq: BRD/zram은 submit_bio 콜백으로 BIO를 직접 처리합니다(no-queue). 일반 블록 디바이스(NVMe, SCSI 등)는 blk-mq(multi-queue)를 사용하여 하드웨어 큐에 요청을 디스패치합니다. RAM 디스크는 하드웨어 큐가 없으므로 요청 큐 오버헤드 없이 BIO를 즉시 처리하는 것이 효율적입니다. 커널 6.4에서 BRD를 blk-mq로 전환하는 시도가 있었으나, 성능 이점이 없어 submit_bio 방식이 유지되었습니다.

I/O 스케줄러

RAM 디스크는 탐색 시간(seek time)이 없으므로 I/O 스케줄링이 불필요합니다. BRD/zram은 submit_bio 콜백을 사용하여 스케줄러를 완전히 바이패스합니다.

# 스케줄러 확인 (none이면 바이패스)
cat /sys/block/ram0/queue/scheduler
# none

cat /sys/block/zram0/queue/scheduler
# none

# 비교: 실제 디스크
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber bfq none

# RAM 디스크의 큐 깊이와 I/O 속성 확인
cat /sys/block/ram0/queue/nr_requests       # 128 (기본)
cat /sys/block/ram0/queue/read_ahead_kb     # 128 (기본)
cat /sys/block/ram0/queue/rotational        # 0 (비회전 미디어)
cat /sys/block/ram0/queue/physical_block_size  # 4096 (PAGE_SIZE)
cat /sys/block/ram0/queue/logical_block_size   # 512
RAM 디스크 I/O 최적화: BRD/zram은 submit_bio 방식을 사용하여 blk-mq 소프트웨어 큐를 완전히 바이패스합니다. BIO가 submit_bio_noacct()를 통해 직접 드라이버의 submit_bio 콜백으로 전달됩니다. 이는 하드웨어 큐가 없는 RAM 디스크에서 불필요한 큐 오버헤드를 제거합니다. read_ahead_kb도 의미가 없지만 기본값이 설정됩니다 (무시해도 됨).

RAM 디스크 유형 비교

속성BRDramfstmpfszram
인터페이스블록 디바이스파일시스템파일시스템블록 디바이스
디바이스 노드/dev/ram0없음없음/dev/zram0
크기 제한rd_size없음size=disksize
메모리 할당온디맨드 페이지페이지 캐시shmem 페이지zsmalloc (압축)
압축없음없음없음lzo/lz4/zstd
스왑 가능아니오아니오아니오 (자체가 스왑 대상)
DISCARD지원해당 없음해당 없음지원
FS 마운트ext4, xfs 등자체 FS자체 FSext4, xfs 등
주 용도initrd, 테스트rootfs 내부/tmp, /run, /dev/shm스왑, 캐시
메모리 효율1:11:11:1 (스왑 시 개선)2~3:1 (압축)
소스drivers/block/brd.cfs/ramfs/mm/shmem.cdrivers/block/zram/
# 모든 RAM 디스크 유형의 상태를 한 번에 확인하는 스크립트
echo "=== BRD ==="
lsblk -d | grep ram 2>/dev/null || echo "(none)"

echo -e "\n=== tmpfs ==="
df -h -t tmpfs 2>/dev/null

echo -e "\n=== zram ==="
zramctl 2>/dev/null || echo "(none)"

echo -e "\n=== Memory Usage ==="
grep -E 'MemTotal|MemAvailable|Shmem|Buffers|SwapTotal' /proc/meminfo
/* 각 RAM 디스크 유형의 file_system_type / block_device_operations 비교 */

/* BRD — 블록 디바이스 */
static const struct block_device_operations brd_fops = {
    .submit_bio = brd_submit_bio,       /* BIO 직접 처리 */
};

/* zram — 블록 디바이스 */
static const struct block_device_operations zram_devops = {
    .submit_bio = zram_submit_bio,      /* 압축 + zsmalloc */
    .open = zram_open,
};

/* ramfs — 파일시스템 */
static struct file_system_type ramfs_fs_type = {
    .name = "ramfs",
    .init_fs_context = ramfs_init_fs_context,
    .kill_sb = ramfs_kill_sb,           /* 슈퍼블록 해제 */
};

/* tmpfs — 파일시스템 */
static struct file_system_type shmem_fs_type = {
    .name = "tmpfs",
    .init_fs_context = shmem_init_fs_context,
    .kill_sb = kill_litter_super,
    .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP,  /* 네임스페이스 지원 */
};

CONFIG 옵션 종합

CONFIG 옵션기본값설명
CONFIG_BLK_DEV_RAMy/mBRD 드라이버 활성화
CONFIG_BLK_DEV_RAM_COUNT16기본 RAM 디스크 수
CONFIG_BLK_DEV_RAM_SIZE4096기본 RAM 디스크 크기 (KB)
CONFIG_BLK_DEV_INITRDyinitrd/initramfs 지원
CONFIG_INITRAMFS_SOURCE""빌트인 initramfs 소스 경로
CONFIG_INITRAMFS_COMPRESSION_GZIPy빌트인 initramfs gzip 압축
CONFIG_INITRAMFS_COMPRESSION_LZ4n빌트인 initramfs lz4 압축
CONFIG_INITRAMFS_COMPRESSION_ZSTDn빌트인 initramfs zstd 압축
CONFIG_ZRAMmzram 모듈 활성화
CONFIG_ZRAM_WRITEBACKnzram writeback 지원
CONFIG_ZRAM_MULTI_COMPnzram 다중 압축 알고리즘
CONFIG_ZRAM_DEF_COMP"lzo-rle"zram 기본 압축 알고리즘
CONFIG_ZSMALLOCmzsmalloc 메모리 할당자 (zram 의존)
CONFIG_TMPFSytmpfs 지원
CONFIG_TMPFS_POSIX_ACLytmpfs POSIX ACL 지원
CONFIG_TMPFS_XATTRytmpfs 확장 속성 지원
CONFIG_RD_GZIPyinitrd/initramfs gzip 해제
CONFIG_RD_LZ4yinitrd/initramfs lz4 해제
CONFIG_RD_ZSTDyinitrd/initramfs zstd 해제
# 현재 커널의 CONFIG 확인
zcat /proc/config.gz | grep -E 'BLK_DEV_RAM|ZRAM|INITRAMFS|TMPFS'

# 또는
grep -E 'BLK_DEV_RAM|ZRAM|INITRAMFS|TMPFS' /boot/config-$(uname -r)

# 필요한 CONFIG 한 번에 확인하는 스크립트
for opt in BLK_DEV_RAM ZRAM ZRAM_WRITEBACK ZRAM_MULTI_COMP \
           ZSMALLOC TMPFS BLK_DEV_INITRD; do
    val=$(zcat /proc/config.gz 2>/dev/null | grep "CONFIG_${opt}=" || echo "not set")
    printf "%-30s %s\n" "CONFIG_${opt}" "$val"
done
# CONFIG_BLK_DEV_RAM            CONFIG_BLK_DEV_RAM=m
# CONFIG_ZRAM                   CONFIG_ZRAM=m
# CONFIG_ZRAM_WRITEBACK         CONFIG_ZRAM_WRITEBACK=y
# ...
최소 CONFIG 가이드: 임베디드 환경에서 RAM 디스크 관련 최소 설정: CONFIG_TMPFS=y (필수, rootfs 백엔드), CONFIG_BLK_DEV_INITRD=y (initramfs 부팅), CONFIG_ZRAM=m + CONFIG_ZSMALLOC=m (메모리 효율적 스왑). BRD(CONFIG_BLK_DEV_RAM)는 initrd를 사용하지 않으면 비활성화해도 됩니다.

커널 부트 파라미터

부트 파라미터 커널 파싱 흐름 cmdline에서 __setup(), early_param을 거쳐 각 핸들러로 전달되는 파싱 흐름 커널 부트 파라미터 파싱 흐름 부트로더 (GRUB) cmdline → boot_params start_kernel() setup_command_line() parse_early_param() early_param() 매크로 등록 parse_args() — late __setup() 매크로 등록 핸들러 함수들 • ramdisk_size() • initrd_load() • root_dev_setup() • rdinit_setup() • initramfs_async_setup() • rootfstype_setup() 플랫폼별 initrd 전달 메커니즘 x86 (GRUB/syslinux) boot_params.hdr.ramdisk ARM/ARM64 (DT) chosen/linux,initrd-* UEFI (직접) EFI_LOAD_FILE2_PROTOCOL RISC-V / MIPS (DT) chosen/linux,initrd-start 커널 코드: __setup / early_param 등록 예시 __setup("ramdisk_size=", ramdisk_size); /* init/do_mounts_rd.c — BRD 크기 설정 */ __setup("root=", root_dev_setup); /* init/do_mounts.c — 루트 디바이스 */ early_param("initrd", early_initrd); /* arch/x86/kernel/setup.c — initrd 물리 주소 */
그림 14. 부트 파라미터 파싱 흐름 — cmdline → early_param/setup → 핸들러 함수
파라미터설명예시
ramdisk_size=BRD 디스크 크기 (KB)ramdisk_size=131072 (128MB)
initrd=initrd/initramfs 파일 경로initrd=/boot/initramfs.img
noinitrdinitrd/initramfs 무시noinitrd
root=루트 파일시스템 디바이스root=/dev/ram0 (BRD 루트)
rdinit=initramfs에서 실행할 init 경로rdinit=/bin/sh (디버깅용)
init=실제 루트의 init 경로init=/sbin/init
rootfstype=루트 파일시스템 유형rootfstype=ext4
initramfs_async=initramfs 비동기 언패킹initramfs_async=0 (동기)
플랫폼initrd 전달 방식커널 수신 코드물리 주소 저장
x86 (BIOS/GRUB)boot_params.hdr.ramdisk_imagearch/x86/kernel/setup.cinitrd_start / initrd_end
x86 (UEFI)EFI stub이 LoadFile2로 로드drivers/firmware/efi/libstub/boot_params.hdr.ramdisk_image
ARM (DT)/chosen/linux,initrd-startarch/arm/kernel/atags_parse.cphys_initrd_start / phys_initrd_size
ARM64 (DT)/chosen/linux,initrd-startarch/arm64/kernel/setup.c동일
RISC-V (DT)/chosen/linux,initrd-startarch/riscv/kernel/setup.c동일
# GRUB에서 BRD를 루트로 사용 (테스트/복구)
linux /vmlinuz root=/dev/ram0 ramdisk_size=262144 rw
initrd /initrd.img

# initramfs 디버깅: /init 대신 셸 실행
linux /vmlinuz rdinit=/bin/sh

# initramfs 무시하고 직접 루트 마운트
linux /vmlinuz noinitrd root=/dev/sda2 rootfstype=ext4

# 실전 GRUB menuentry: LUKS + LVM + initramfs
menuentry 'Linux (encrypted root)' {
    set root='hd0,gpt2'
    linux /vmlinuz-6.8.0 \
        root=/dev/mapper/vg0-root \
        rd.luks.uuid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
        rd.lvm.lv=vg0/root \
        rootfstype=ext4 \
        resume=/dev/mapper/vg0-swap \
        quiet splash
    initrd /initramfs-6.8.0.img
}

# Device Tree에서 initrd 전달 (ARM/ARM64)
# chosen {
#     linux,initrd-start = <0x48000000>;
#     linux,initrd-end   = <0x49000000>;
#     bootargs = "root=/dev/mmcblk0p2 rootfstype=ext4";
# };

실전 활용 가이드

RAM 디스크 유형 선택 가이드 요구사항에 따라 BRD, tmpfs, zram 중 적절한 유형을 선택하는 의사결정 트리 RAM 디스크 유형 선택 의사결정 트리 블록 디바이스가 필요한가? 아니오 크기 제한/스왑이 필요한가? 아니오 ramfs rootfs 내부용만 tmpfs /tmp, /run, Docker 메모리 압축이 필요한가? 아니오 BRD 테스트, initrd, 벤치마크 zram 스왑, IoT, 캐시 주요 활용 시나리오 컨테이너/Docker tmpfs: /run, /tmp zram: 컨테이너 스왑 overlayfs + tmpfs 임베디드/IoT zram swap (512MB RAM) tmpfs rootfs overlay initramfs 기반 부팅 CI/CD 빌드 BRD + ext4: 빌드 디렉터리 tmpfs: /tmp 빌드 아티팩트 빌드 시간 30~50% 단축 데이터베이스 tmpfs: Redis AOF 버퍼 BRD: 임시 테이블스페이스 zram: 페이지 캐시 확장
그림 16. RAM 디스크 유형 선택 의사결정 트리 — 요구사항에 따른 최적 선택

성능 벤치마크 방법

# === BRD 벤치마크 ===

# 1. BRD 생성 및 파일시스템 구성
modprobe brd rd_nr=1 rd_size=1048576  # 1GB
mkfs.ext4 /dev/ram0
mount /dev/ram0 /mnt/ramdisk

# 2. fio 벤치마크
fio --name=seqwrite --directory=/mnt/ramdisk \
    --rw=write --bs=4k --size=512m --numjobs=4 \
    --group_reporting --runtime=30

fio --name=randread --directory=/mnt/ramdisk \
    --rw=randread --bs=4k --size=512m --numjobs=4 \
    --group_reporting --runtime=30

# 3. dd 간단 테스트
dd if=/dev/zero of=/mnt/ramdisk/test bs=1M count=512 oflag=direct
dd if=/mnt/ramdisk/test of=/dev/null bs=1M iflag=direct

# === tmpfs 벤치마크 ===
mount -t tmpfs -o size=1g tmpfs /mnt/tmpfs
fio --name=seqwrite --directory=/mnt/tmpfs \
    --rw=write --bs=4k --size=512m --numjobs=4 \
    --group_reporting

# === zram 벤치마크 ===
echo 2G > /sys/block/zram0/disksize
mkfs.ext4 /dev/zram0
mount /dev/zram0 /mnt/zram

fio --name=seqwrite --directory=/mnt/zram \
    --rw=write --bs=4k --size=1g --numjobs=4 \
    --group_reporting

# 압축률 확인
cat /sys/block/zram0/mm_stat | awk '{printf "압축률: %.1f:1\n", $1/$2}'
일반적인 성능 비교:
  • tmpfs: 가장 빠름 (VFS→페이지 캐시 직접 경로, 블록 레이어 바이패스)
  • BRD (ext4): 약간 느림 (VFS→블록 레이어→BRD→페이지 복사)
  • zram (ext4): BRD보다 느림 (압축/해제 CPU 오버헤드), 하지만 메모리 효율 2~3배
  • SSD: RAM 대비 10~100배 느림
벤치마크 결과 해석 가이드:
  • IOPS: tmpfs > BRD > zram >> SSD (tmpfs는 수백만 IOPS 가능)
  • 지연 시간: tmpfs ~1us, BRD ~2us, zram ~10us (압축 오버헤드), SSD ~100us
  • 대역폭: 모두 DRAM 대역폭에 근접 (10~40 GB/s), zram은 CPU가 병목
  • fio --ioengine=sync로 실제 시스템 콜 경로 측정 권장

데이터 영속성 처리

# RAM 디스크 데이터는 전원 꺼짐 시 소실
# 주기적으로 디스크에 동기화하는 방법:

# 방법 1: rsync 주기적 동기화
while true; do
    rsync -a /mnt/ramdisk/ /backup/ramdisk/ --delete
    sleep 300  # 5분마다
done

# 방법 2: systemd timer
# /etc/systemd/system/ramdisk-sync.service
# [Service]
# Type=oneshot
# ExecStart=/usr/bin/rsync -a /mnt/ramdisk/ /backup/ramdisk/ --delete

# 방법 3: 종료 시 자동 백업 (systemd)
# /etc/systemd/system/ramdisk-backup.service
# [Unit]
# Description=Backup ramdisk on shutdown
# DefaultDependencies=no
# Before=shutdown.target reboot.target halt.target
# [Service]
# Type=oneshot
# ExecStart=/usr/bin/rsync -a /mnt/ramdisk/ /backup/ramdisk/
# [Install]
# WantedBy=halt.target reboot.target shutdown.target

# 방법 4: tmpfs + overlayfs (읽기 전용 베이스 + RAM 변경분)
mount -t overlay overlay \
    -o lowerdir=/base,upperdir=/mnt/ramdisk/upper,workdir=/mnt/ramdisk/work \
    /merged

컨테이너/Docker 활용

# === Docker에서 tmpfs 활용 ===

# 컨테이너에 tmpfs 마운트 (메모리 제한 포함)
docker run --tmpfs /tmp:rw,size=256m,noexec,nosuid \
    --tmpfs /run:rw,size=64m \
    -it ubuntu:24.04 bash

# Docker Compose에서 tmpfs 설정
# services:
#   app:
#     tmpfs:
#       - /tmp:size=512m,mode=1777
#       - /run:size=64m

# Kubernetes에서 emptyDir tmpfs
# volumes:
#   - name: cache
#     emptyDir:
#       medium: Memory
#       sizeLimit: 1Gi

# === 컨테이너에서 zram swap ===

# 호스트에서 zram 설정 (모든 컨테이너가 공유)
echo lz4 > /sys/block/zram0/comp_algorithm
echo 4G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# cgroup v2에서 컨테이너별 메모리/스왑 제한
# memory.max = 2G    (메모리 제한)
# memory.swap.max = 4G (zram 스왑 제한)

임베디드/IoT 최적 설정

# === 512MB RAM IoT 디바이스에서 zram 최적 설정 ===

# 물리 RAM의 50%를 zram swap으로 할당
modprobe zram num_devices=1
echo lz4 > /sys/block/zram0/comp_algorithm  # 저지연
echo 256M > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# vm 파라미터 튜닝 (임베디드 최적화)
echo 60 > /proc/sys/vm/swappiness  # 스왑 적극 사용
echo 100 > /proc/sys/vm/vfs_cache_pressure  # 캐시 적극 회수

# === 읽기 전용 rootfs + tmpfs overlay (임베디드) ===
# 부팅 시 실행:
mount -t tmpfs -o size=64m tmpfs /mnt/overlay
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay overlay \
    -o lowerdir=/,upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work \
    /mnt/merged
# → 읽기 전용 NAND/eMMC + RAM 변경분 = 쓰기 수명 보호

CI/CD 빌드 가속

# === BRD를 빌드 디렉터리로 활용 ===

# 4GB RAM 디스크 생성
modprobe brd rd_nr=1 rd_size=4194304  # 4GB
mkfs.ext4 -E lazy_itable_init=0 /dev/ram0
mount -o noatime,discard /dev/ram0 /build

# 빌드 실행 (디스크 I/O 제거 → 빌드 30~50% 가속)
cd /build
git clone --depth 1 https://github.com/project/repo .
make -j$(nproc)

# === tmpfs를 빌드 디렉터리로 (더 간단) ===
mount -t tmpfs -o size=4g tmpfs /build
# tmpfs가 BRD보다 빠름 (블록 레이어 바이패스)
활용 시나리오권장 유형설정 포인트기대 효과
Docker /tmptmpfssize=256m,noexec임시파일 I/O 가속, 보안 강화
IoT 스왑zram (lz4)RAM 50%, swappiness=60유효 메모리 1.5~2배 확대
CI 빌드tmpfs 또는 BRDsize=4g,noatime빌드 시간 30~50% 단축
Redis 캐시tmpfssize=2g,noswap영속성 불필요 데이터 가속
임베디드 rootfstmpfs + overlayfs읽기 전용 base + RAM overlayNAND 수명 보호
커널 테스트BRDrd_size=1048576블록 디바이스 인터페이스 테스트

동적 크기 조정

# === tmpfs 온라인 리사이즈 ===
# tmpfs는 마운트 상태에서 즉시 크기 변경 가능 (데이터 보존)
mount -o remount,size=2g /tmp

# BRD: 리사이즈 불가 (재생성 필요)
umount /mnt/ramdisk
rmmod brd
modprobe brd rd_nr=1 rd_size=2097152  # 2GB
mkfs.ext4 /dev/ram0
mount /dev/ram0 /mnt/ramdisk

# zram: 리셋 후 재설정 필요
swapoff /dev/zram0
echo 1 > /sys/block/zram0/reset
echo 8G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon -p 100 /dev/zram0

# zram: hot_add/hot_remove로 동적 추가/제거
cat /sys/class/zram-control/hot_add    # 새 zram 디바이스 번호 반환
echo 1 > /sys/class/zram-control/hot_remove  # zram1 제거

디버깅과 트러블슈팅

RAM 디스크 디버깅 의사결정 트리 문제 유형별로 적절한 디버깅 도구와 확인 방법을 안내하는 흐름도 RAM 디스크 디버깅 의사결정 트리 어떤 문제인가? 디바이스 미생성 확인 도구: • lsmod | grep brd/zram • cat /proc/devices • zcat /proc/config.gz 성능 저하 확인 도구: • fio / dd 벤치마크 • perf stat / perf top • cat mm_stat (압축률) 메모리 이상 확인 도구: • /proc/meminfo (Shmem) • /proc/slabinfo (zspage) • cgroup memory.stat 부팅 실패 확인 도구: • rdinit=/bin/sh • rd.break / rd.debug • dmesg | grep initr 고급 추적 도구 ftrace brd_submit_bio 추적 bpftrace zram 압축률 모니터링 perf CPU 프로파일링 /proc/vmstat pswpin/pswpout 카운터 문제 유형 → 적절한 도구 선택 → 원인 분석 → 해결
그림 15. RAM 디스크 디버깅 의사결정 트리 — 문제 유형별 도구와 확인 포인트
# === BRD 디버깅 ===

# BRD 디바이스 확인
ls -la /dev/ram*
lsblk | grep ram
cat /proc/devices | grep ramdisk  # major 1

# BRD 메모리 사용량 (근사값)
for dev in /sys/block/ram*/size; do
    name=$(dirname $dev | xargs basename)
    sectors=$(cat $dev)
    echo "$name: $((sectors * 512 / 1024 / 1024))MB capacity"
done

# === zram 디버깅 ===

# zram 상태 종합 확인
zramctl
# NAME       ALGORITHM DISKSIZE   DATA  COMPR  TOTAL STREAMS MOUNTPOINT
# /dev/zram0 lz4            4G  2.1G  712M  734M       8 [SWAP]

# 상세 통계
cat /sys/block/zram0/mm_stat
cat /sys/block/zram0/io_stat
cat /sys/block/zram0/bd_stat

# 디버그 메시지 (커널 로그)
dmesg | grep -i "zram\|brd\|ramdisk\|initramfs\|initrd"

# === initramfs 디버깅 ===

# initramfs 내용 확인
lsinitrd /boot/initramfs-$(uname -r).img  # RHEL/Fedora
lsinitramfs /boot/initrd.img-$(uname -r)   # Debian/Ubuntu

# initramfs 부팅 문제 디버깅
# 커널 파라미터에 추가:
#   rd.break          — initramfs의 switch_root 직전에 셸 진입
#   rd.debug          — dracut 디버그 로그 활성화
#   rdinit=/bin/sh    — /init 대신 셸 실행

# === 메모리 추적 ===
# /proc/meminfo에서 RAM 디스크 관련 항목
grep -E 'Shmem|Buffers|Cached' /proc/meminfo
# Buffers:      BRD가 사용하는 블록 버퍼 캐시
# Shmem:        tmpfs가 사용하는 메모리
# Cached:       페이지 캐시 (ramfs 포함)

# slabinfo에서 zsmalloc 확인
grep zspage /proc/slabinfo
# === ftrace로 brd_submit_bio 추적 ===

# 1. function tracer 설정
cd /sys/kernel/debug/tracing
echo function > current_tracer
echo brd_submit_bio > set_ftrace_filter
echo 1 > tracing_on

# 2. BRD에 I/O 발생시키기
dd if=/dev/zero of=/dev/ram0 bs=4k count=100

# 3. 추적 결과 확인
cat trace
#  dd-12345  [002]  ...  brd_submit_bio <-submit_bio_noacct
#  dd-12345  [002]  ...  brd_submit_bio <-submit_bio_noacct

echo 0 > tracing_on
echo > set_ftrace_filter
# === bpftrace: zram 압축률 실시간 모니터링 ===

# 압축 전후 크기 추적 (zram_write_page 프로브)
bpftrace -e '
kprobe:zram_write_page {
    @write_count = count();
}
kretprobe:zram_write_page /retval == 0/ {
    @success = count();
}
interval:s:5 {
    printf("writes: %d, success: %d\n",
           @write_count, @success);
    clear(@write_count);
    clear(@success);
}
'

# zram I/O 레이턴시 히스토그램
bpftrace -e '
kprobe:zram_submit_bio { @start[tid] = nsecs; }
kretprobe:zram_submit_bio /@start[tid]/ {
    @latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'
# === /proc/vmstat zram 관련 카운터 해석 ===

grep -E 'pswpin|pswpout|pgfault|pgmajfault' /proc/vmstat
# pswpin 12345    ← 스왑 인 페이지 수 (zram 읽기 포함)
# pswpout 67890   ← 스왑 아웃 페이지 수 (zram 쓰기 포함)
# pgfault 1234567 ← 페이지 폴트 총 수
# pgmajfault 456  ← major 폴트 (디스크/스왑 접근)

# zram이 스왑 대상일 때: pswpin/pswpout이 zram 압축/해제 횟수
# pgmajfault가 높으면 → 메모리 부족, 스왑 thrashing 의심

# === OOM 시나리오: ramfs 메모리 고갈 재현 ===

# 주의: 테스트 환경에서만 실행!
mount -t ramfs ramfs /mnt/test
# ramfs는 크기 제한이 없으므로 아래 명령은 OOM을 유발
# dd if=/dev/zero of=/mnt/test/oom bs=1M count=999999
# → OOM killer 발동 → dmesg에 기록

# === cgroup v2에서 shmem(tmpfs) 메모리 추적 ===

# cgroup v2 memory controller
cat /sys/fs/cgroup/user.slice/memory.stat | grep shmem
# shmem 12345678  ← 이 cgroup의 tmpfs 사용량 (바이트)
# shmem_pmdmapped 0

# 특정 컨테이너의 tmpfs 사용량 모니터링
cat /sys/fs/cgroup/system.slice/docker-*.scope/memory.stat | grep shmem
실무에서 자주 발생하는 문제 5가지:
  • 1. BRD가 없음: CONFIG_BLK_DEV_RAM=m이면 modprobe brd 필요. lsmod | grep brd로 확인.
  • 2. initramfs 부팅 실패: /init 파일이 없거나 실행 권한이 없는 경우. rdinit=/bin/sh로 셸 진입 후 ls -la /init 확인.
  • 3. zram swap 성능 저하: CPU 바운드 워크로드에서 압축 오버헤드. perf top으로 lzo1x_compress CPU 점유율 확인 → lz4로 전환.
  • 4. ramfs OOM: 크기 제한 없어 메모리 전체 소진 → OOM killer 발동. tmpfs로 전환하고 size= 옵션 설정.
  • 5. zram writeback 실패: backing device 공간 부족 또는 I/O 에러. bd_stat 확인 후 writeback_limit 조정.

커널 버전별 변화

커널 버전변경 사항영향주요 커밋/참고
2.4ramfs 도입페이지 캐시 기반 메모리 FSLinus Torvalds, VFS 예제 코드
2.4.4tmpfs(shmem) 도입크기 제한 + 스왑 가능한 메모리 FSChristoph Rohland
2.5initramfs 도입cpio 기반, BRD 불필요Al Viro, usr/gen_init_cpio.c
2.6.33brd.c 리팩토링기존 rd.c를 현대적 블록 레이어 API로 전환Nick Piggin, commit 2e2e7da
3.14zram staging 졸업drivers/block/zram/ 공식 포함Minchan Kim, Nitin Gupta
4.7zram: same_page 최적화0으로 채워진 페이지 메모리 할당 없이 저장commit 43209ea (same_filled)
4.14zram: writeback 도입유휴 페이지를 backing device로 이동Minchan Kim, CONFIG_ZRAM_WRITEBACK
5.1zram: idle 페이지 마킹writeback 대상 세밀 제어commit cab7a7e
5.6BRD: XArray 전환Radix Tree에서 XArray로 마이그레이션Matthew Wilcox
5.18tmpfs: noswap 옵션tmpfs 개별 스왑 비활성화 가능commit 2c6efe9
6.1zram: multi-comp 기반 마련ZRAM_PRIMARY_COMP / ZRAM_SECONDARY_COMPSergey Senozhatsky
6.2zram: 다중 압축(recompress)기본(빠른) + 보조(높은 압축) 이중 전략CONFIG_ZRAM_MULTI_COMP
6.4BRD: blk-mq 전환 시도submit_bio 방식 유지 (성능 이점 없음)전환 후 revert
6.8zram: per-entry 압축 알고리즘페이지별로 다른 압축 알고리즘 적용 가능recompress 고도화
Deprecated/변경 API이전 API대체 API변경 버전
BRD 페이지 관리radix_tree_insert/lookupxa_store/xa_load (XArray)5.6
zram 압축 인터페이스zcomp_strm_find/releasezcomp_stream_get/put (per-CPU)4.x
블록 디바이스 할당alloc_disk() + blk_init_queue()blk_alloc_disk()5.15
BIO 페이지 반복bio_for_each_segment()동일 (유지)
initrd 로드rd_load_image()동일 (레거시 유지)
shmem folioshmem_getpage()shmem_get_folio()5.18+
향후 변경 예정:
  • zram: per-page 압축 알고리즘 선택 고도화, 더 세밀한 recompression 정책
  • BRD: 커널에서 제거 논의 진행 중 (zram/tmpfs로 대체 가능), 현재 initrd 호환을 위해 유지
  • tmpfs: large folio 지원 확대 (THP 외에도 다양한 folio order)
  • zsmalloc: NUMA 인식 할당 개선, 컴팩션 성능 최적화

보안 고려사항

RAM 디스크는 메모리 기반이라 물리적 디스크와 다른 보안 특성을 가집니다. 전원 꺼짐 시 데이터가 사라지는 점은 보안에 유리하지만, 메모리 상의 데이터는 cold boot attack이나 메모리 덤프로 노출될 수 있습니다.

보안 주제위험대응관련 CONFIG
tmpfs 민감 데이터/tmp에 저장된 비밀번호/키 파일이 메모리에 평문 존재memfd_secret() 사용 (5.14+), 파일 삭제 후 메모리 즉시 해제CONFIG_SECRETMEM
ramfs OOM 공격크기 제한 없는 ramfs에 대량 쓰기 → 시스템 다운프로덕션에서 ramfs 미사용, tmpfs + size= 제한
initramfs 변조initramfs에 악성 /init 삽입 → 부팅 시 루트 권한 획득IMA/EVM 서명 검증, Secure Boot 연동CONFIG_IMA, CONFIG_EVM
zram 데이터 잔존swapoff 후 압축 데이터가 zsmalloc에 잔존echo 1 > reset으로 전체 초기화, DISCARD on swapoff
Cold boot attack전원 차단 직후 DRAM에서 데이터 추출Full memory encryption (AMD SME/SEV, Intel TME)CONFIG_AMD_MEM_ENCRYPT
/dev/shm 권한공유 메모리 영역을 통한 프로세스 간 데이터 유출적절한 권한 설정 (1777), namespace 격리
# === initramfs 서명 검증 (IMA/EVM) ===

# IMA 정책으로 initramfs 내 파일 무결성 검증
# /etc/ima/ima-policy:
# measure func=KEXEC_INITRAMFS_CHECK
# appraise func=KEXEC_INITRAMFS_CHECK appraise_type=imasig

# initramfs에 IMA 서명 추가
evmctl ima_sign -k /etc/keys/privkey_ima.pem /boot/initramfs.img

# Secure Boot 체인: UEFI → shim → GRUB → vmlinuz (서명) → initramfs (IMA)

# === zram 데이터 안전 삭제 ===

# swapoff만으로는 zsmalloc 메모리가 즉시 해제되지 않을 수 있음
swapoff /dev/zram0

# 완전 초기화 (메모리 + 메타데이터 전부 해제)
echo 1 > /sys/block/zram0/reset

# 민감 환경: zram 사용 후 반드시 reset 수행
# reset → zs_destroy_pool() → 모든 zspage __free_page()

# === tmpfs에서 민감 데이터 처리 ===

# memfd_secret: 커널도 접근 불가한 비밀 메모리 (5.14+)
# 프로그래밍 방식:
#   fd = memfd_secret(0);
#   ftruncate(fd, 4096);
#   ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
#              MAP_SHARED, fd, 0);
# → 이 메모리는 /proc/pid/mem으로도 읽을 수 없음

# tmpfs 파일 안전 삭제
shred -u /tmp/secret_file  # 0으로 덮어쓴 후 삭제
CONFIG 보안 옵션기본값설명
CONFIG_SECRETMEMymemfd_secret() 시스템 콜 활성화 (커널 접근 불가 메모리)
CONFIG_IMAn/yIntegrity Measurement Architecture — 파일 무결성 측정
CONFIG_EVMnExtended Verification Module — 파일 메타데이터 무결성
CONFIG_AMD_MEM_ENCRYPTy (AMD)AMD Secure Memory Encryption — DRAM 암호화
CONFIG_INTEL_TDX_GUESTnIntel Trust Domain Extensions — 메모리 격리
CONFIG_INIT_ON_FREE_DEFAULT_ONn페이지 해제 시 0으로 초기화 (성능 영향)
CONFIG_PAGE_POISONINGn해제된 페이지에 독 패턴 기록 (디버그용)
# === tmpfs 보안 강화 마운트 예시 ===

# /tmp: noexec,nosuid,nodev — 실행 파일/setuid/디바이스 파일 차단
mount -t tmpfs -o size=1g,mode=1777,noexec,nosuid,nodev tmpfs /tmp

# /dev/shm: 공유 메모리 크기 제한
mount -t tmpfs -o size=512m,noexec,nosuid,nodev tmpfs /dev/shm

# fstab 설정
# tmpfs  /tmp      tmpfs  defaults,size=1g,noexec,nosuid,nodev,mode=1777  0  0
# tmpfs  /dev/shm  tmpfs  defaults,size=512m,noexec,nosuid,nodev           0  0
# tmpfs  /run      tmpfs  defaults,size=256m,noexec,nosuid,nodev,mode=755  0  0
/* init/initramfs.c — initramfs 서명 검증 (IMA 연동) */

/*
 * CONFIG_IMA_APPRAISE_SIGNED_INIT 활성화 시
 * initramfs의 모든 파일이 IMA 서명을 가져야 부팅 가능
 *
 * 검증 흐름:
 * 1. unpack_to_rootfs() → 각 파일을 rootfs에 생성
 * 2. 파일 접근 시 → ima_file_check() → 서명 검증
 * 3. 서명 불일치 → -EACCES → 부팅 실패
 *
 * 서명 포함 방법:
 * - evmctl ima_sign --key /path/to/key 파일
 * - security.ima xattr에 서명 저장
 * - cpio 아카이브에 xattr 포함하여 패키징
 */
보안 모범 사례: 프로덕션 환경에서 RAM 디스크를 사용할 때는 ① tmpfs에 noexec,nosuid 옵션 추가, ② /dev/shm의 크기를 제한(size=), ③ zram swap 사용 후 반드시 reset 수행, ④ initramfs는 Secure Boot + IMA 서명으로 무결성 보장을 권장합니다.