shmem/tmpfs — 공유 메모리 파일시스템 심화
shmem(공유 메모리 파일시스템)은 리눅스 커널에서 RAM을 백업 저장소로 사용하는 가상 파일시스템입니다.
사용자 공간에서는 tmpfs로 마운트하여 접근하며, 커널 내부에서는 POSIX 공유 메모리,
System V 공유 메모리, DRM GEM 객체, memfd_create() 등 다양한 서브시스템의 기반으로 활용됩니다.
이 문서는 mm/shmem.c의 내부 구현부터 스왑 연동, THP(Transparent Huge Pages),
메모리 압력 대응, 보안 설정, 성능 튜닝까지 전 영역을 깊이 있게 다룹니다.
핵심 요약
- shmem — 커널 내부 이름.
mm/shmem.c에 구현된 RAM 기반 파일시스템 핵심 코드 - tmpfs — 사용자 공간에서 마운트하는 이름.
mount -t tmpfs로 사용 - 스왑 가능 — 일반 RAM 페이지처럼 메모리 부족 시 스왑 아웃 가능 (ramfs와의 핵심 차이)
- 크기 제한 — 마운트 시
size=옵션으로 최대 크기를 제한하여 메모리 고갈 방지 - 다중 사용처 —
/tmp,/dev/shm,/run, memfd, DRM GEM 등 커널 전반에 활용
단계별 이해
- VFS 계층
shmem은 VFS의file_system_type으로 등록됩니다.super_operations,inode_operations,file_operations를 구현하여 일반 파일시스템처럼 동작합니다. - 페이지 캐시 활용
파일 데이터는 페이지 캐시(page cache)에 저장됩니다. 디스크 블록 장치가 없으므로 페이지 캐시 자체가 유일한 저장소입니다. - 스왑 백엔드
메모리 압력이 발생하면 shmem 페이지는 스왑 공간으로 내보내집니다. 다시 접근하면 스왑에서 읽어와 페이지 캐시에 복원합니다. - 사용자 공간 인터페이스
mount -t tmpfs,shm_open(),shmget()/shmat(),memfd_create()등 다양한 경로로 접근합니다.
shmem/tmpfs 개요
shmem/tmpfs란?
shmem(shared memory filesystem)은 리눅스 커널의 mm/shmem.c에 구현된
RAM 기반 가상 파일시스템입니다. 물리 디스크 없이 시스템 메모리(RAM)를 백업 저장소로 사용하며,
필요 시 스왑 공간을 활용할 수 있습니다.
사용자 공간에서는 tmpfs라는 파일시스템 타입으로 마운트하여 접근합니다.
커널 내부에서는 shmem이라는 이름으로 다양한 서브시스템이 이를 활용합니다.
ramfs와 tmpfs의 차이
| 특성 | ramfs | tmpfs (shmem) |
|---|---|---|
| 크기 제한 | 없음 (메모리 전부 사용 가능) | size= 옵션으로 제한 가능 |
| 스왑 가능 | 불가 | 가능 (메모리 압력 시 스왑 아웃) |
| 메모리 회수 | 불가 (페이지 해제 불가) | 가능 (LRU 기반 회수) |
| 구현 파일 | fs/ramfs/ | mm/shmem.c |
| 주요 용도 | initramfs | /tmp, /dev/shm, /run |
shmem의 역할 범위
shmem은 단순한 /tmp 마운트 포인트를 넘어, 커널 전반에서 핵심적인 역할을 수행합니다.
/* shmem이 사용되는 주요 경로 */
사용자 공간:
mount -t tmpfs → /tmp, /run, /dev/shm
shm_open() / shm_unlink() → POSIX 공유 메모리
shmget() / shmat() → System V 공유 메모리
memfd_create() → 익명 파일 기반 공유 메모리
커널 내부:
DRM GEM (i915, amdgpu) → GPU 버퍼 객체 백업 저장소
IPC 서브시스템 → SysV SHM 세그먼트
zero-copy 전송 → splice/vmsplice 파이프라인
shmem 아키텍처
shmem은 VFS(Virtual File System) 인터페이스를 완전히 구현하는 파일시스템입니다. 일반적인 디스크 파일시스템과 달리 블록 장치 대신 페이지 캐시와 스왑 공간을 백엔드로 사용합니다.
VFS 인터페이스
shmem은 struct file_system_type으로 등록되어 VFS 계층과 통합됩니다.
shmem_fs_type은 tmpfs라는 이름으로 등록되며,
shmem_init()에서 커널 부팅 시 초기화됩니다.
static struct file_system_type shmem_fs_type = {
.owner = THIS_MODULE,
.name = "tmpfs",
.init_fs_context = shmem_init_fs_context,
.parameters = shmem_fs_parameters,
.kill_sb = kill_litter_super,
.fs_flags = FS_USERNS_MOUNT,
};
int __init shmem_init(void)
{
int error;
shmem_init_inodecache();
error = register_filesystem(&shmem_fs_type);
if (error) {
pr_err("Could not register tmpfs\\n");
goto out2;
}
shm_mnt = kern_mount(&shmem_fs_type);
if (IS_ERR(shm_mnt)) {
error = PTR_ERR(shm_mnt);
goto out1;
}
return 0;
out1:
unregister_filesystem(&shmem_fs_type);
out2:
shmem_destroy_inodecache();
shm_mnt = ERR_PTR(error);
return error;
}
코드 설명
-
1-7행
shmem_fs_type구조체 정의.name = "tmpfs"로 사용자 공간에서mount -t tmpfs로 마운트 가능합니다.FS_USERNS_MOUNT플래그로 비특권 사용자 네임스페이스에서도 마운트 허용합니다. -
9-10행
shmem_init()는__init매크로가 붙은 부팅 시 일회성 함수입니다. -
14행
register_filesystem()으로 VFS에 tmpfs를 등록합니다. -
20행
kern_mount()로 커널 내부 사용을 위한 shmem 인스턴스를 마운트합니다. 이shm_mnt는shmem_file_setup()등에서 사용됩니다.
tmpfs 마운트와 옵션
기본 마운트
# 기본 마운트: 물리 RAM의 50%를 최대 크기로 사용
mount -t tmpfs tmpfs /mnt/tmp
# 크기 제한 설정 (2GB)
mount -t tmpfs -o size=2G tmpfs /mnt/tmp
# /etc/fstab 항목
tmpfs /tmp tmpfs defaults,size=1G,mode=1777 0 0
주요 마운트 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
size= | 물리 RAM 50% | 최대 사용 가능 바이트 수. 접미사 k/m/g/% 지원 |
nr_inodes= | 물리 RAM 페이지 수 / 2 | 최대 inode(파일/디렉터리) 수 |
mode= | 1777 | 루트 디렉터리 퍼미션 |
uid= | 0 | 루트 디렉터리 소유자 UID |
gid= | 0 | 루트 디렉터리 소유자 GID |
huge= | never | THP 정책: never, always, within_size, advise |
noexec | off | 실행 파일 실행 금지 |
nosuid | off | setuid/setgid 비트 무시 |
nodev | off | 디바이스 파일 사용 금지 |
inode64 | off | 64비트 inode 번호 사용 |
마운트 옵션 파싱 코드
static const struct fs_parameter_spec shmem_fs_parameters[] = {
fsparam_u32("mode", Opt_mode),
fsparam_string("size", Opt_size),
fsparam_string("nr_inodes", Opt_nr_inodes),
fsparam_u32("uid", Opt_uid),
fsparam_u32("gid", Opt_gid),
fsparam_enum("huge", Opt_huge, shmem_param_enums),
fsparam_flag("noswap", Opt_noswap),
{}
};
런타임 재마운트
# 크기를 4GB로 변경 (마운트 해제 없이)
mount -o remount,size=4G /tmp
# 현재 tmpfs 사용량 확인
df -h /tmp
# Filesystem Size Used Avail Use% Mounted on
# tmpfs 4.0G 128M 3.9G 4% /tmp
# 전체 tmpfs 마운트 목록
mount | grep tmpfs
shmem 내부 구현
shmem_inode_info 구조체
shmem의 inode별 메타데이터는 struct shmem_inode_info에 저장됩니다.
이 구조체는 struct inode에 임베딩되며, SHMEM_I() 매크로로 접근합니다.
struct shmem_inode_info {
spinlock_t lock;
unsigned int seals; /* F_SEAL_* 플래그 */
unsigned long flags;
unsigned long alloced; /* 할당된 데이터 페이지 수 */
unsigned long swapped; /* 스왑 아웃된 페이지 수 */
pgoff_t fallocend; /* fallocate 예약 끝 */
struct list_head shrinklist; /* 슈퍼블록 shrink 목록 */
struct list_head swaplist; /* 스왑 가능 inode 목록 */
struct shared_policy policy; /* NUMA 정책 */
struct simple_xattrs xattrs; /* 확장 속성 목록 */
atomic_t stop_eviction; /* 삭제 방지 카운터 */
struct inode vfs_inode; /* VFS inode (임베딩) */
};
코드 설명
-
3행
seals: memfd에서 사용하는 씰(seal) 플래그.F_SEAL_SHRINK,F_SEAL_GROW,F_SEAL_WRITE,F_SEAL_SEAL등으로 파일 수정을 제한합니다. -
5-6행
alloced: RAM에 있는 페이지 수.swapped: 스왑에 내보낸 페이지 수. 두 값의 합이 파일의 총 데이터 페이지 수입니다. - 8-9행 메모리 회수 경로에서 사용하는 연결 리스트. shrinklist는 슈퍼블록의 회수 대상 목록, swaplist는 스왑 가능 inode 목록입니다.
-
13행
VFS inode가 구조체 끝에 임베딩됩니다.
container_of()로shmem_inode_info에서inode로, 또는 그 반대로 변환합니다.
shmem_get_folio() 흐름
shmem_get_folio()은 shmem 파일의 페이지에 접근하는 핵심 함수입니다.
페이지 캐시를 먼저 검색하고, 없으면 스왑에서 복원하거나 새로 할당합니다.
static int shmem_get_folio_gfp(struct inode *inode,
pgoff_t index, struct folio **foliop,
enum sgp_type sgp, gfp_t gfp,
struct vm_fault *vmf)
{
struct address_space *mapping = inode->i_mapping;
struct shmem_inode_info *info = SHMEM_I(inode);
struct folio *folio;
int error;
/* 1단계: 페이지 캐시 검색 */
folio = filemap_get_folio(mapping, index);
if (!IS_ERR(folio))
goto out;
/* 2단계: 스왑 엔트리 확인 */
folio = shmem_swapin_folio(inode, index, ...);
if (!IS_ERR_OR_NULL(folio))
goto out;
/* 3단계: 새 folio 할당 */
folio = shmem_alloc_and_add_folio(vmf, gfp,
inode, index, ...);
out:
*foliop = folio;
return error;
}
페이지 할당과 스왑 연동
페이지 할당 경로
shmem의 페이지 할당은 shmem_alloc_folio()를 통해 이루어집니다.
NUMA 정책, THP 여부, GFP 플래그에 따라 적절한 할당 경로를 선택합니다.
shmem_writepage() - 스왑 아웃
static int shmem_writepage(struct page *page,
struct writeback_control *wbc)
{
struct folio *folio = page_folio(page);
struct shmem_inode_info *info;
struct address_space *mapping;
swp_entry_t swap;
/* 스왑 공간에 슬롯 할당 */
swap = folio_alloc_swap(folio);
if (!swap.val)
goto redirty;
/* XArray에서 folio를 swap_entry로 교체 */
xa_lock_irq(&mapping->i_pages);
if (shmem_replace_folio(&folio, swap, mapping))
goto free_swap;
xa_unlock_irq(&mapping->i_pages);
info->swapped++;
info->alloced--;
/* 스왑 장치에 기록 */
swap_writepage(&folio->page, wbc);
return 0;
}
코드 설명
-
10행
folio_alloc_swap()로 스왑 공간에서 빈 슬롯을 할당받습니다. 실패하면 페이지를 dirty 상태로 유지합니다. -
15-17행
XArray를 잠근 상태에서 folio 엔트리를
swap_entry_t로 원자적으로 교체합니다. 이후shmem_get_folio()에서 이 swap 엔트리를 찾아 복원합니다. -
20-21행
swapped카운터 증가,alloced카운터 감소. 이 두 카운터의 합은 파일의 총 논리 페이지 수를 유지합니다.
Transparent Huge Pages in tmpfs
리눅스 커널은 tmpfs에서 Transparent Huge Pages(THP)를 지원합니다. 2MB 크기의 huge page를 사용하면 TLB 미스를 줄이고 대용량 파일 접근 성능을 크게 향상시킬 수 있습니다.
THP 활성화 방법
# 마운트 시 THP 활성화
mount -t tmpfs -o size=4G,huge=always tmpfs /mnt/huge_tmp
# sysfs를 통한 전역 설정
echo always > /sys/kernel/mm/transparent_hugepage/shmem_enabled
# madvise 기반 (advise 모드일 때)
# 프로그램에서 madvise(addr, len, MADV_HUGEPAGE) 호출 필요
# 현재 설정 확인
cat /sys/kernel/mm/transparent_hugepage/shmem_enabled
# always within_size advise [never] deny force
THP 통계 확인
# shmem THP 관련 통계
grep -i shmem /proc/vmstat
# nr_shmem 1234
# nr_shmem_hugepages 56
# nr_shmem_pmdmapped 48
# AnonHugePages vs ShmemHugePages
grep -E "Shmem|Huge" /proc/meminfo
# Shmem: 512000 kB
# ShmemHugePages: 114688 kB
# ShmemPmdMapped: 98304 kB
always로 설정하면 내부 단편화로 인해
실제 메모리 사용량이 증가할 수 있습니다. 예를 들어 5KB 파일에 2MB 페이지가 할당되면
2043KB가 낭비됩니다. 프로덕션 환경에서는 within_size 또는 advise를 권장합니다.
POSIX 공유 메모리 (shm_open, /dev/shm)
POSIX 공유 메모리는 shm_open()/shm_unlink() API를 통해
프로세스 간 공유 메모리 세그먼트를 생성합니다. 내부적으로 /dev/shm에 마운트된
tmpfs 위에 파일을 생성하는 방식으로 동작합니다.
POSIX 공유 메모리 예제
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#define SHM_NAME "/my_shared_buf"
#define SHM_SIZE 4096
/* 생산자: 공유 메모리 생성 및 데이터 쓰기 */
int producer(void)
{
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd < 0) { perror("shm_open"); return 1; }
ftruncate(fd, SHM_SIZE);
char *ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
strcpy(ptr, "Hello from producer!");
printf("생산자: 데이터 기록 완료\\n");
munmap(ptr, SHM_SIZE);
return 0;
}
/* 소비자: 공유 메모리 읽기 */
int consumer(void)
{
int fd = shm_open(SHM_NAME, O_RDONLY, 0);
if (fd < 0) { perror("shm_open"); return 1; }
char *ptr = mmap(NULL, SHM_SIZE,
PROT_READ, MAP_SHARED, fd, 0);
close(fd);
printf("소비자: %s\\n", ptr);
munmap(ptr, SHM_SIZE);
shm_unlink(SHM_NAME); /* 정리 */
return 0;
}
System V 공유 메모리 (shmget, shmat)
System V IPC의 공유 메모리는 shmget()/shmat()/shmdt()/shmctl()
API를 사용합니다. 커널 내부에서는 shmem 파일시스템 위에 익명 파일을 생성하여 구현됩니다.
System V vs POSIX 공유 메모리
| 특성 | System V (shmget) | POSIX (shm_open) |
|---|---|---|
| 식별자 | 정수 키 (key_t) | 문자열 이름 (/name) |
| API | shmget/shmat/shmdt/shmctl | shm_open/mmap/munmap/shm_unlink |
| 파일시스템 | 커널 내부 shmem (보이지 않음) | /dev/shm/ (보임) |
| 크기 조정 | 생성 시 고정 | ftruncate()로 변경 가능 |
| 수명 | 명시적 IPC_RMID 또는 재부팅 | shm_unlink() 또는 재부팅 |
| 큰 페이지 | SHM_HUGETLB 플래그 | tmpfs huge= 옵션 의존 |
| 권장 여부 | 레거시 (새 코드에서 비권장) | 권장 (POSIX 표준) |
System V 공유 메모리 사용 예제
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#define SHM_KEY 0x1234
#define SHM_SIZE 4096
int main(void)
{
/* 공유 메모리 세그먼트 생성/획득 */
int shmid = shmget(SHM_KEY, SHM_SIZE,
IPC_CREAT | 0666);
if (shmid < 0) {
perror("shmget");
return 1;
}
/* 현재 프로세스 주소 공간에 연결 */
char *ptr = shmat(shmid, NULL, 0);
if (ptr == (char *)-1) {
perror("shmat");
return 1;
}
strcpy(ptr, "SysV SHM 데이터");
printf("데이터: %s\\n", ptr);
/* 분리 및 삭제 */
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
System V SHM 커널 내부 경로
/* ipc/shm.c: shmget() 시스템 콜 처리 */
static int newseg(struct ipc_namespace *ns,
struct ipc_params *params)
{
struct shmid_kernel *shp;
struct file *file;
size_t size = params->u.size;
/* shmem 파일 생성 (핵심!) */
file = shmem_kernel_file_setup("SYSV", size, 0);
if (IS_ERR(file))
return PTR_ERR(file);
shp->shm_file = file; /* shmem 파일과 연결 */
...
}
shmem_file_setup과 커널 내부 사용
shmem_file_setup()은 커널 내부에서 shmem 기반 익명 파일을 생성하는 핵심 함수입니다.
DRM GEM 객체, memfd, System V SHM 등 다양한 서브시스템이 이 함수를 통해
shmem 백업 저장소를 활용합니다.
memfd_create() 시스템 콜
memfd_create()는 이름 없는 shmem 파일을 생성하는 현대적인 API입니다.
파일 디스크립터만 반환하며, 파일시스템에 보이지 않아 이름 충돌이 없습니다.
씰(seal) 메커니즘으로 파일 내용의 불변성을 보장할 수 있어,
IPC와 버퍼 공유에 이상적입니다.
#include <sys/mman.h>
#include <linux/memfd.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
/* 익명 shmem 파일 생성 */
int fd = memfd_create("my_buffer", MFD_ALLOW_SEALING);
if (fd < 0) { perror("memfd_create"); return 1; }
/* 크기 설정 */
ftruncate(fd, 4096);
/* 데이터 기록 */
char *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
sprintf(ptr, "memfd 공유 데이터");
/* 씰 적용: 크기 변경/쓰기 금지 */
fcntl(fd, F_ADD_SEALS,
F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
/* 이제 fd를 다른 프로세스에 전달 (sendmsg SCM_RIGHTS) */
printf("memfd fd=%d, 씰 적용 완료\\n", fd);
/* 수신 측은 씰을 확인하여 데이터 무결성 보장 가능 */
int seals = fcntl(fd, F_GET_SEALS);
printf("씰 플래그: 0x%x\\n", seals);
munmap(ptr, 4096);
close(fd);
return 0;
}
DRM GEM shmem 사용
/* drivers/gpu/drm/drm_gem_shmem_helper.c */
struct drm_gem_shmem_object *drm_gem_shmem_create(
struct drm_device *dev, size_t size)
{
struct drm_gem_shmem_object *shmem;
shmem = kzalloc(sizeof(*shmem), GFP_KERNEL);
/* shmem 기반 GEM 객체 초기화 */
drm_gem_object_init(dev, &shmem->base, size);
/* 내부적으로 shmem_file_setup() 호출 */
mutex_init(&shmem->pages_lock);
mutex_init(&shmem->vmap_lock);
return shmem;
}
tmpfs와 메모리 압력
tmpfs의 가장 중요한 특성 중 하나는 메모리 압력(memory pressure) 시 일반 페이지 캐시처럼 회수(reclaim)될 수 있다는 점입니다. ramfs와 달리 shmem 페이지는 LRU 목록에 포함되어 kswapd의 관리를 받습니다.
size= 옵션으로 tmpfs 크기를 제한하고, 적절한 스왑 공간을 확보하세요.
메모리 사용량 모니터링
# shmem 전체 사용량 확인
grep Shmem /proc/meminfo
# Shmem: 512000 kB
# 개별 tmpfs 마운트 사용량
df -h --type=tmpfs
# Filesystem Size Used Avail Use% Mounted on
# tmpfs 1.0G 128M 896M 13% /tmp
# tmpfs 3.9G 0 3.9G 0% /dev/shm
# tmpfs 800M 1.2M 799M 1% /run
# cgroup v2 메모리 통계 (컨테이너 환경)
cat /sys/fs/cgroup/memory.stat | grep shmem
# shmem 131072
커널 설정 (CONFIG_SHMEM, CONFIG_TMPFS)
관련 커널 설정 옵션
| 설정 | 기본값 | 설명 |
|---|---|---|
CONFIG_SHMEM | y | shmem 핵심 구현 활성화. 비활성화 시 ramfs로 대체 |
CONFIG_TMPFS | y | tmpfs 사용자 공간 마운트 지원 |
CONFIG_TMPFS_POSIX_ACL | y | tmpfs에서 POSIX ACL 지원 |
CONFIG_TMPFS_XATTR | y | tmpfs에서 확장 속성(xattr) 지원 |
CONFIG_TMPFS_INODE64 | y | 64비트 inode 번호 기본 활성화 |
CONFIG_TRANSPARENT_HUGEPAGE | y | THP 지원 (tmpfs 포함) |
CONFIG_SWAP | y | 스왑 서브시스템 활성화 (shmem 스왑 아웃 필수) |
CONFIG_SHMEM 비활성화 시 동작
/* include/linux/shmem_fs.h */
#ifdef CONFIG_SHMEM
/* 전체 shmem 구현 사용 (mm/shmem.c) */
extern int shmem_init(void);
extern struct file *shmem_file_setup(const char *name,
loff_t size, unsigned long flags);
#else
/* ramfs 기반 최소 구현으로 대체 */
/* 스왑 불가, 크기 제한 불가, THP 불가 */
static inline struct file *shmem_file_setup(
const char *name, loff_t size,
unsigned long flags)
{
return ramfs_file_setup(name, size, flags);
}
#endif
CONFIG_SHMEM=n으로 설정하면 임베디드 환경에서
커널 이미지 크기를 줄일 수 있습니다. 다만 스왑, 크기 제한, THP 등 핵심 기능이
모두 비활성화되므로 일반적인 리눅스 배포판에서는 사용하지 않습니다.
보안 (noexec, nosuid, 크기 제한)
tmpfs는 사용자가 쓰기 가능한 파일시스템이므로, 보안 설정이 매우 중요합니다. 공격자가 tmpfs에 악성 바이너리를 올려 실행하거나, setuid 프로그램을 배치하는 것을 방지해야 합니다.
보안 강화 마운트 예시
# /tmp: noexec + nosuid + nodev + 크기 제한
mount -t tmpfs -o size=1G,mode=1777,noexec,nosuid,nodev tmpfs /tmp
# /dev/shm: 공유 메모리 전용, 실행 금지
mount -t tmpfs -o size=2G,mode=1777,noexec,nosuid,nodev tmpfs /dev/shm
# /run: 시스템 런타임 데이터 (적절한 크기)
mount -t tmpfs -o size=800M,mode=0755,nosuid,nodev tmpfs /run
# /etc/fstab 보안 강화 항목
tmpfs /tmp tmpfs defaults,size=1G,noexec,nosuid,nodev,mode=1777 0 0
tmpfs /dev/shm tmpfs defaults,size=2G,noexec,nosuid,nodev,mode=1777 0 0
tmpfs /run tmpfs defaults,size=800M,nosuid,nodev,mode=0755 0 0
memfd_create 씰 보안
| 씰 플래그 | 효과 |
|---|---|
F_SEAL_SEAL | 더 이상 새로운 씰을 추가할 수 없음 |
F_SEAL_SHRINK | 파일 크기 축소 불가 (ftruncate 차단) |
F_SEAL_GROW | 파일 크기 확장 불가 |
F_SEAL_WRITE | 쓰기 불가 (write, mmap PROT_WRITE 차단) |
F_SEAL_FUTURE_WRITE | 새로운 쓰기 매핑만 차단 (기존 매핑은 유지) |
F_SEAL_EXEC | 실행 가능 매핑 차단 (6.3+) |
성능 벤치마크와 튜닝
tmpfs vs ext4 vs xfs 성능 비교
| 워크로드 | tmpfs | ext4 (SSD) | xfs (SSD) |
|---|---|---|---|
| 순차 쓰기 (1GB) | ~8 GB/s | ~500 MB/s | ~480 MB/s |
| 순차 읽기 (1GB) | ~12 GB/s | ~550 MB/s | ~530 MB/s |
| 랜덤 4K 읽기 IOPS | ~2,000K | ~300K | ~280K |
| 파일 생성 (10K 파일) | ~50ms | ~800ms | ~600ms |
| fsync 지연 | N/A (불필요) | ~0.5ms | ~0.3ms |
주요 튜닝 포인트
# 1. THP 활성화 (대용량 파일 접근 시 TLB 미스 감소)
mount -t tmpfs -o huge=within_size,size=8G tmpfs /mnt/fast_tmp
# 2. NUMA 친화적 할당 (NUMA 시스템)
# tmpfs 파일에 NUMA 정책 적용
numactl --membind=0 dd if=/dev/zero of=/mnt/fast_tmp/data bs=1M count=1024
# 3. 스왑 우선순위 최적화 (zram 사용)
# zram을 높은 우선순위로 설정하여 tmpfs 스왑 아웃 시 성능 저하 최소화
zramctl /dev/zram0 --size 4G --algorithm zstd
mkswap /dev/zram0
swapon -p 100 /dev/zram0
# 4. vm.swappiness 조정 (tmpfs 스왑 아웃 빈도 제어)
# 낮은 값: tmpfs 페이지를 오래 RAM에 유지
sysctl vm.swappiness=10
# 5. 적절한 크기 설정 (과도한 할당 방지)
# 실제 필요한 만큼만 할당 (기본 50%는 과도할 수 있음)
mount -o remount,size=2G /tmp
실전 사용 사례
/tmp, /run, /dev/shm
현대 리눅스 배포판은 기본적으로 여러 tmpfs 마운트를 사용합니다.
| 마운트 포인트 | 용도 | 일반적인 크기 |
|---|---|---|
/tmp | 임시 파일 (빌드 출력, 세션 데이터) | RAM 50% 또는 1-4GB |
/dev/shm | POSIX 공유 메모리 (shm_open) | RAM 50% |
/run | 런타임 데이터 (PID 파일, 소켓) | RAM 20% 또는 800MB |
/run/lock | 잠금 파일 | 5MB |
/sys/fs/cgroup | cgroup v2 파일시스템 | 자동 |
컨테이너 환경에서의 tmpfs
# Docker: 컨테이너 내 tmpfs 마운트
docker run --tmpfs /tmp:size=512M,noexec,nosuid alpine sh
# Kubernetes: emptyDir medium=Memory
# Pod spec에서 tmpfs 볼륨 사용
# volumes:
# - name: shared-data
# emptyDir:
# medium: Memory
# sizeLimit: 256Mi
# containerd: OCI 런타임 설정에서 tmpfs
# 각 컨테이너의 /dev/shm은 별도 tmpfs로 격리
# 컨테이너 tmpfs의 memory cgroup 제한 확인
cat /sys/fs/cgroup/system.slice/docker-*.scope/memory.stat | grep shmem
GPU 버퍼 (DRM GEM)
GPU 드라이버(i915, amdgpu, nouveau 등)는 DRM GEM(Graphics Execution Manager) 객체의 백업 저장소로 shmem을 광범위하게 사용합니다. GPU가 접근하지 않는 버퍼는 RAM에서 스왑 아웃되어 메모리를 절약할 수 있습니다.
/* GPU 버퍼 할당 흐름 (i915 드라이버 예시) */
/* 1단계: shmem 기반 GEM 객체 생성 */
struct drm_i915_gem_object *obj;
obj = i915_gem_object_create_shmem(i915, size);
/* 내부: shmem_file_setup() → 페이지 캐시 위에 파일 생성 */
/* 2단계: GPU에 핀 (스왑 아웃 방지) */
i915_gem_object_pin_pages(obj);
/* shmem_get_folio()로 모든 페이지를 RAM에 고정 */
/* 3단계: GPU 렌더링 완료 후 언핀 */
i915_gem_object_unpin_pages(obj);
/* 메모리 압력 시 kswapd가 스왑 아웃 가능 */
/* 4단계: 재사용 시 스왑에서 복원 */
i915_gem_object_pin_pages(obj);
/* shmem_swapin_folio()로 스왑에서 다시 읽기 */
빌드 시스템 최적화
# 리눅스 커널 빌드 시 tmpfs 활용
mount -t tmpfs -o size=8G tmpfs /tmp/kernel-build
cd /tmp/kernel-build
tar xf linux-6.x.tar.xz
cd linux-6.x
make defconfig
make -j$(nproc)
# 디스크 I/O 병목 없이 빌드 속도 극대화
# ccache와 tmpfs 조합
export CCACHE_TEMPDIR=/tmp/ccache_tmp
mkdir -p $CCACHE_TEMPDIR
shmem operations 전체 구조
shmem은 VFS의 모든 주요 operations 구조체를 구현합니다. 각 operations는 shmem의 RAM 기반 특성에 맞게 최적화되어 있습니다.
static const struct address_space_operations shmem_aops = {
.writepage = shmem_writepage,
.dirty_folio = noop_dirty_folio,
#ifdef CONFIG_TMPFS
.write_begin = shmem_write_begin,
.write_end = shmem_write_end,
#endif
.migrate_folio = migrate_folio,
.error_remove_folio = shmem_error_remove_folio,
};
static const struct vm_operations_struct shmem_vm_ops = {
.fault = shmem_fault,
.map_pages = filemap_map_pages,
#ifdef CONFIG_NUMA
.set_policy = shmem_set_policy,
.get_policy = shmem_get_policy,
#endif
};
fallocate와 hole punch
tmpfs는 fallocate() 시스템 콜을 지원하여 공간 사전 할당과
파일 중간의 구멍(hole) 생성을 지원합니다.
#include <fcntl.h>
#include <linux/falloc.h>
#include <unistd.h>
int main(void)
{
int fd = open("/tmp/test_fallocate",
O_CREAT | O_RDWR, 0644);
/* 공간 사전 할당 (1MB) */
fallocate(fd, 0, 0, 1048576);
/* 파일 중간에 hole punch (128KB 영역 해제) */
fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
4096, 131072);
/* 파일 끝부분 제거 (collapse) */
fallocate(fd, FALLOC_FL_COLLAPSE_RANGE,
524288, 524288);
close(fd);
return 0;
}
코드 설명
- 11행 기본 fallocate: 1MB 분량의 페이지를 사전 할당합니다. 디스크 파일시스템과 달리 shmem에서는 실제 RAM 페이지가 할당됩니다.
-
14-15행
FALLOC_FL_PUNCH_HOLE: 파일 크기는 유지하면서 지정 범위의 페이지를 해제합니다. RAM을 즉시 반환하므로 메모리 절약에 유용합니다. -
18-19행
FALLOC_FL_COLLAPSE_RANGE: 지정 범위를 제거하고 뒤쪽 데이터를 앞으로 당깁니다. 파일 크기가 줄어듭니다.
참고자료
- Linux Kernel Documentation: tmpfs
- mm/shmem.c 소스 코드 (Bootlin)
- include/linux/shmem_fs.h
- tmpfs(5) man page
- memfd_create(2) man page
- shm_open(3) man page
- shmget(2) man page
- LWN: Transparent huge pages for tmpfs
- LWN: Sealing memfd
- Transparent Hugepage Support
- VFS (가상 파일시스템) — shmem이 구현하는 VFS 인터페이스 상세
- Page Cache — shmem이 데이터를 저장하는 페이지 캐시 내부
- Swapping 서브시스템 — shmem 스왑 아웃 경로 상세
- 메모리 관리 개요 — 물리 메모리 할당과 회수 전체 구조