리눅스 커널의 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 백엔드
ramfs
VFS 파일시스템
fs/ramfs/
rootfs 백엔드, 초기 파일시스템
tmpfs
VFS 파일시스템
mm/shmem.c
/tmp, /run, /dev/shm, initramfs
zram
블록 디바이스 (/dev/zram*)
drivers/block/zram/
압축 스왑, 압축 RAM 디스크
역사적 배경
RAM 디스크의 역사는 리눅스 커널 초기부터 시작됩니다:
시기
사건
의의
1991
Linux 0.01 — 램디스크 미포함
초기 커널은 플로피에서 직접 부팅
1995
Linux 1.3.x — rd.c RAM 디스크 드라이버
initrd 지원 시작, 모듈 로딩 전 루트 마운트 가능
2000
Linux 2.4 — ramfs 도입
페이지 캐시 기반 메모리 FS, VFS 예제 코드
2001
Linux 2.4.4 — tmpfs(shmem) 도입
크기 제한 + 스왑 지원
2002
Linux 2.5.x — initramfs 도입
cpio 기반, BRD 블록 디바이스 불필요
2009
Linux 2.6.33 — brd.c 리팩토링
기존 rd.c를 현대화, XArray 도입 준비
2014
Linux 3.14 — zram mainline
staging에서 승격, drivers/block/zram/
2019
Linux 5.x — zram writeback
콜드 페이지를 backing device로 이동
2023
Linux 6.x — BRD 멀티큐 전환
blk-mq 기반으로 현대화
RAM 디스크 아키텍처 총괄
리눅스 커널에서 RAM 기반 스토리지는 두 가지 경로로 제공됩니다:
블록 디바이스 계층(BRD, zram)과 VFS 파일시스템 계층(ramfs, tmpfs).
각각의 커널 스택 위치와 데이터 흐름을 살펴봅시다.
그림 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_nr
16
생성할 RAM 디스크 수
modprobe brd rd_nr=4 또는 커널 파라미터 ramdisk_size=
rd_size
4096 (KB)
각 디스크의 최대 크기
modprobe brd rd_size=65536 (64MB)
max_part
1
파티션 최대 수
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)를 사용하여 섹터 번호를 페이지에 매핑합니다.
쓰기 시 처음 접근하는 섹터에 대해서만 페이지를 할당하므로, 메모리 사용이 효율적입니다.
그림 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 위에 파일시스템을 마운트하고 파일을 삭제한 뒤
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_folio
simple_read_folio()
folio를 0으로 초기화 후 uptodate 마킹 (디스크 I/O 없음)
write_begin
simple_write_begin()
grab_cache_page_write_begin()으로 페이지 캐시에 folio 할당/조회
write_end
simple_write_end()
쓰기 완료, inode 크기 갱신, folio를 uptodate 마킹
dirty_folio
noop_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 핵심 차이
속성
ramfs
tmpfs
크기 제한
없음 (메모리 전체 소진 가능)
있음 (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 개요
tmpfs는 ramfs를 확장한 메모리 기반 파일시스템으로,
내부적으로 shmem(Shared Memory) 인프라를 사용합니다.
ramfs와 달리 크기 제한, 스왑 지원, xattr, POSIX ACL 등 완전한 파일시스템 기능을 제공합니다.
그림 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/shm
POSIX 공유 메모리 (shm_open)
RAM의 50%
/sys/fs/cgroup
cgroup 파일시스템 (cgroup v1)
제한 없음
/dev
devtmpfs (디바이스 노드)
제한 없음
마운트 옵션
기본값
설명
예시
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=
never
THP 정책: never/always/within_size/advise
huge=always
noswap
(스왑 허용)
이 tmpfs 인스턴스의 스왑 비활성화 (5.18+)
noswap
inode32 / inode64
inode64
inode 번호 범위 (32비트 호환)
inode32
mpol=
default
NUMA 메모리 정책
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 부팅 흐름
그림 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 이미지 포맷
그림 8. initrd ext2 이미지 바이너리 레이아웃과 initrd/initramfs 감지 로직
포맷
매직 바이트
파일시스템
커널 지원
비압축
파일시스템 매직
ext2, minix, romfs
항상
gzip 압축
1f 8b
ext2 (일반적)
CONFIG_RD_GZIP
bzip2 압축
42 5a
ext2
CONFIG_RD_BZIP2
xz 압축
fd 37 7a 58 5a
ext2
CONFIG_RD_XZ
lz4 압축
02 21 4c 18
ext2
CONFIG_RD_LZ4
zstd 압축
28 b5 2f fd
ext2
CONFIG_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 블록 디바이스가 불필요하고, 메모리 사용도 효율적입니다.
커널 언패킹 코드
그림 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개 파일
#!/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.c
init/initramfs.c
CONFIG
CONFIG_BLK_DEV_RAM
CONFIG_BLK_DEV_INITRD
현재 상태
레거시 (비권장)
표준 (모든 배포판)
마이그레이션 가이드: 레거시 initrd에서 initramfs로 전환하려면:
① /linuxrc를 /init으로 변경, ② pivot_root를 switch_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 아키텍처
그림 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-rle
CONFIG_CRYPTO_LZO
중간 (~2:1)
빠름
커널 기본값, RLE 최적화
lz4
CONFIG_CRYPTO_LZ4
낮음 (~1.8:1)
매우 빠름
압축/해제 속도 최우선
zstd
CONFIG_CRYPTO_ZSTD
높음 (~2.8:1)
보통
Facebook 개발, 높은 압축률
lzo
CONFIG_CRYPTO_LZO
중간 (~2:1)
빠름
레거시, lzo-rle 권장
842
CONFIG_CRYPTO_842
중간
보통
IBM POWER 하드웨어 가속
deflate
CONFIG_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에서 도입되었습니다.
그림 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로 결합하여 단편화를 최소화합니다.
그림 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 index
pages_per_zspage
objects_per_zspage
외부 단편화율
32~48B
0~1
1
85~128
<2%
64~256B
2~14
1
16~64
<5%
272~1024B
15~62
1~2
4~15
<10%
1040~2048B
63~126
2~3
3~7
<15%
2064~3072B
127~190
3~4
4~5
<20%
3088~4096B
191~254
1 (PAGE_SIZE)
1
0% (전체 페이지)
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)을 할당하여 락 경합 없이 병렬 처리합니다.
그림 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;
}
# === 기본 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 디스크의 네 가지 유형은 각각 다른 메모리 관리 전략을 사용합니다.
이 차이가 성능, 메모리 효율, 기능의 근본적인 차이를 만듭니다.
그림 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 + 64MB
4GB + 72MB
~1.5GB + 12MB
할당 단위
PAGE_SIZE (4KB/2MB THP)
PAGE_SIZE (4KB)
16B 단위 가변
해제 메커니즘
truncate/unlink
DISCARD/xa_erase
zs_free/reset
NUMA 인식
예 (mpol= 옵션)
NUMA_NO_NODE
NUMA_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
커널 내부/rootfs → ramfs (자동, 직접 사용하지 않음)
페이지 캐시 기반 (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 처리 흐름
그림 6. BRD BIO 처리 흐름 — submit_bio → brd_submit_bio → 페이지 복사 (스케줄러 바이패스)
요청 큐 설정
그림 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_size
PAGE_SIZE
PAGE_SIZE
물리적 최소 I/O 단위
logical_block_size
512 (기본)
PAGE_SIZE
논리적 최소 I/O 단위 (zram은 4KB 정렬 필수)
max_hw_sectors
1024
기본값
한 번에 처리할 최대 섹터 수
io_opt
설정 안 함
PAGE_SIZE
최적 I/O 크기
QUEUE_FLAG_NONROT
설정
설정
회전 미디어 아님 (SSD 스케줄링 힌트)
max_discard_sectors
UINT_MAX
UINT_MAX
DISCARD/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 콜백을 사용하여 스케줄러를 완전히 바이패스합니다.
RAM 디스크 I/O 최적화: BRD/zram은 submit_bio 방식을 사용하여 blk-mq 소프트웨어 큐를
완전히 바이패스합니다. BIO가 submit_bio_noacct()를 통해 직접 드라이버의 submit_bio 콜백으로
전달됩니다. 이는 하드웨어 큐가 없는 RAM 디스크에서 불필요한 큐 오버헤드를 제거합니다.
read_ahead_kb도 의미가 없지만 기본값이 설정됩니다 (무시해도 됨).
RAM 디스크 유형 비교
속성
BRD
ramfs
tmpfs
zram
인터페이스
블록 디바이스
파일시스템
파일시스템
블록 디바이스
디바이스 노드
/dev/ram0
없음
없음
/dev/zram0
크기 제한
rd_size
없음
size=
disksize
메모리 할당
온디맨드 페이지
페이지 캐시
shmem 페이지
zsmalloc (압축)
압축
없음
없음
없음
lzo/lz4/zstd
스왑 가능
아니오
아니오
예
아니오 (자체가 스왑 대상)
DISCARD
지원
해당 없음
해당 없음
지원
FS 마운트
ext4, xfs 등
자체 FS
자체 FS
ext4, xfs 등
주 용도
initrd, 테스트
rootfs 내부
/tmp, /run, /dev/shm
스왑, 캐시
메모리 효율
1:1
1:1
1:1 (스왑 시 개선)
2~3:1 (압축)
소스
drivers/block/brd.c
fs/ramfs/
mm/shmem.c
drivers/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
# 현재 커널의 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를 사용하지 않으면 비활성화해도 됩니다.
커널 부트 파라미터
그림 14. 부트 파라미터 파싱 흐름 — cmdline → early_param/setup → 핸들러 함수
파라미터
설명
예시
ramdisk_size=
BRD 디스크 크기 (KB)
ramdisk_size=131072 (128MB)
initrd=
initrd/initramfs 파일 경로
initrd=/boot/initramfs.img
noinitrd
initrd/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_image
arch/x86/kernel/setup.c
initrd_start / initrd_end
x86 (UEFI)
EFI stub이 LoadFile2로 로드
drivers/firmware/efi/libstub/
boot_params.hdr.ramdisk_image
ARM (DT)
/chosen/linux,initrd-start
arch/arm/kernel/atags_parse.c
phys_initrd_start / phys_initrd_size
ARM64 (DT)
/chosen/linux,initrd-start
arch/arm64/kernel/setup.c
동일
RISC-V (DT)
/chosen/linux,initrd-start
arch/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";
# };
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.4
ramfs 도입
페이지 캐시 기반 메모리 FS
Linus Torvalds, VFS 예제 코드
2.4.4
tmpfs(shmem) 도입
크기 제한 + 스왑 가능한 메모리 FS
Christoph Rohland
2.5
initramfs 도입
cpio 기반, BRD 불필요
Al Viro, usr/gen_init_cpio.c
2.6.33
brd.c 리팩토링
기존 rd.c를 현대적 블록 레이어 API로 전환
Nick Piggin, commit 2e2e7da
3.14
zram staging 졸업
drivers/block/zram/ 공식 포함
Minchan Kim, Nitin Gupta
4.7
zram: same_page 최적화
0으로 채워진 페이지 메모리 할당 없이 저장
commit 43209ea (same_filled)
4.14
zram: writeback 도입
유휴 페이지를 backing device로 이동
Minchan Kim, CONFIG_ZRAM_WRITEBACK
5.1
zram: idle 페이지 마킹
writeback 대상 세밀 제어
commit cab7a7e
5.6
BRD: XArray 전환
Radix Tree에서 XArray로 마이그레이션
Matthew Wilcox
5.18
tmpfs: noswap 옵션
tmpfs 개별 스왑 비활성화 가능
commit 2c6efe9
6.1
zram: multi-comp 기반 마련
ZRAM_PRIMARY_COMP / ZRAM_SECONDARY_COMP
Sergey Senozhatsky
6.2
zram: 다중 압축(recompress)
기본(빠른) + 보조(높은 압축) 이중 전략
CONFIG_ZRAM_MULTI_COMP
6.4
BRD: blk-mq 전환 시도
submit_bio 방식 유지 (성능 이점 없음)
전환 후 revert
6.8
zram: per-entry 압축 알고리즘
페이지별로 다른 압축 알고리즘 적용 가능
recompress 고도화
Deprecated/변경 API
이전 API
대체 API
변경 버전
BRD 페이지 관리
radix_tree_insert/lookup
xa_store/xa_load (XArray)
5.6
zram 압축 인터페이스
zcomp_strm_find/release
zcomp_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 folio
shmem_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_SECRETMEM
y
memfd_secret() 시스템 콜 활성화 (커널 접근 불가 메모리)
CONFIG_IMA
n/y
Integrity Measurement Architecture — 파일 무결성 측정
CONFIG_EVM
n
Extended Verification Module — 파일 메타데이터 무결성
CONFIG_AMD_MEM_ENCRYPT
y (AMD)
AMD Secure Memory Encryption — DRAM 암호화
CONFIG_INTEL_TDX_GUEST
n
Intel Trust Domain Extensions — 메모리 격리
CONFIG_INIT_ON_FREE_DEFAULT_ON
n
페이지 해제 시 0으로 초기화 (성능 영향)
CONFIG_PAGE_POISONING
n
해제된 페이지에 독 패턴 기록 (디버그용)
# === 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 서명으로 무결성 보장을 권장합니다.