shmem/tmpfs — 공유 메모리 파일시스템 심화

shmem(공유 메모리 파일시스템)은 리눅스 커널에서 RAM을 백업 저장소로 사용하는 가상 파일시스템입니다. 사용자 공간에서는 tmpfs로 마운트하여 접근하며, 커널 내부에서는 POSIX 공유 메모리, System V 공유 메모리, DRM GEM 객체, memfd_create() 등 다양한 서브시스템의 기반으로 활용됩니다. 이 문서는 mm/shmem.c의 내부 구현부터 스왑 연동, THP(Transparent Huge Pages), 메모리 압력 대응, 보안 설정, 성능 튜닝까지 전 영역을 깊이 있게 다룹니다.

전제 조건: VFS (가상 파일시스템)메모리 관리 개요 문서를 먼저 읽으세요. 페이지 할당, VMA(Virtual Memory Area), 스왑(swap)의 기본 개념을 이해하고 있어야 합니다.
일상 비유: tmpfs는 화이트보드와 비슷합니다. 디스크에 기록하는 일반 파일시스템이 노트에 펜으로 쓰는 것이라면, tmpfs는 화이트보드에 마커로 쓰는 것입니다. 매우 빠르지만 전원을 끄면(재부팅하면) 내용이 사라집니다. 다만 화이트보드가 꽉 차면 사진을 찍어 보관할 수 있듯이(스왑), tmpfs도 메모리가 부족하면 데이터를 스왑 공간으로 내보낼 수 있습니다.

핵심 요약

  • shmem — 커널 내부 이름. mm/shmem.c에 구현된 RAM 기반 파일시스템 핵심 코드
  • tmpfs — 사용자 공간에서 마운트하는 이름. mount -t tmpfs로 사용
  • 스왑 가능 — 일반 RAM 페이지처럼 메모리 부족 시 스왑 아웃 가능 (ramfs와의 핵심 차이)
  • 크기 제한 — 마운트 시 size= 옵션으로 최대 크기를 제한하여 메모리 고갈 방지
  • 다중 사용처/tmp, /dev/shm, /run, memfd, DRM GEM 등 커널 전반에 활용

단계별 이해

  1. VFS 계층
    shmem은 VFS의 file_system_type으로 등록됩니다. super_operations, inode_operations, file_operations를 구현하여 일반 파일시스템처럼 동작합니다.
  2. 페이지 캐시 활용
    파일 데이터는 페이지 캐시(page cache)에 저장됩니다. 디스크 블록 장치가 없으므로 페이지 캐시 자체가 유일한 저장소입니다.
  3. 스왑 백엔드
    메모리 압력이 발생하면 shmem 페이지는 스왑 공간으로 내보내집니다. 다시 접근하면 스왑에서 읽어와 페이지 캐시에 복원합니다.
  4. 사용자 공간 인터페이스
    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의 차이

특성ramfstmpfs (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) 인터페이스를 완전히 구현하는 파일시스템입니다. 일반적인 디스크 파일시스템과 달리 블록 장치 대신 페이지 캐시와 스왑 공간을 백엔드로 사용합니다.

사용자 공간 (User Space) mount -t tmpfs shm_open() shmget()/shmat() memfd_create() DRM GEM (GPU) VFS (Virtual File System) file_operations / inode_operations / super_operations shmem (mm/shmem.c) shmem_inode_info / shmem_get_folio / shmem_writepage / shmem_file_setup 페이지 캐시 (Page Cache) folio / address_space / XArray 스왑 백엔드 (Swap) swap_entry_t / swap cache 읽기/쓰기 메모리 압력 물리 RAM (Physical Memory) 스왑 장치 (Disk / zram) 스왑 인/아웃 THP (2MB huge pages)

VFS 인터페이스

shmem은 struct file_system_type으로 등록되어 VFS 계층과 통합됩니다. shmem_fs_typetmpfs라는 이름으로 등록되며, 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_mntshmem_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=neverTHP 정책: never, always, within_size, advise
noexecoff실행 파일 실행 금지
nosuidoffsetuid/setgid 비트 무시
nodevoff디바이스 파일 사용 금지
inode64off64비트 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 파일의 페이지에 접근하는 핵심 함수입니다. 페이지 캐시를 먼저 검색하고, 없으면 스왑에서 복원하거나 새로 할당합니다.

shmem_get_folio() filemap_get_folio() 페이지 캐시 검색 캐시 히트? folio 반환 XArray에 swap_entry 존재? 아니오 스왑? shmem_swapin_folio() shmem_alloc_folio() 아니오 shmem_add_to_page_cache() XArray 삽입 folio 반환
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_alloc_folio() NUMA 정책 확인 (mpol) THP 할당 시도 (2MB) 4KB 기본 페이지 할당 페이지 캐시 (address_space XArray) folio / swap_entry_t 혼합 저장 kswapd / 직접 회수 shmem_writepage() 스왑 캐시 / 스왑 장치 XArray에 swap_entry_t 기록 (folio 자리 대체) 메모리 압력 shmem_swapin_folio(): 스왑에서 복원 → 페이지 캐시 삽입

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 미스를 줄이고 대용량 파일 접근 성능을 크게 향상시킬 수 있습니다.

tmpfs THP 할당 결정 흐름 huge= 마운트 옵션 확인 never (기본) always within_size advise force 4KB 페이지만 사용 항상 2MB 시도 i_size 범위 내만 2MB MADV_HUGEPAGE 시 2MB compound folio 할당 시도 → 실패 시 4KB 폴백 (order-0) compaction / reclaim 후 재시도 가능 /sys/kernel/mm/transparent_hugepage/shmem_enabled: always within_size advise never deny force

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
주의: THP를 always로 설정하면 내부 단편화로 인해 실제 메모리 사용량이 증가할 수 있습니다. 예를 들어 5KB 파일에 2MB 페이지가 할당되면 2043KB가 낭비됩니다. 프로덕션 환경에서는 within_size 또는 advise를 권장합니다.

POSIX 공유 메모리 (shm_open, /dev/shm)

POSIX 공유 메모리는 shm_open()/shm_unlink() API를 통해 프로세스 간 공유 메모리 세그먼트를 생성합니다. 내부적으로 /dev/shm에 마운트된 tmpfs 위에 파일을 생성하는 방식으로 동작합니다.

프로세스 A (생산자) fd = shm_open("/buf", O_CREAT|O_RDWR, 0666); ftruncate(fd, 4096); ptr = mmap(fd, ...) 프로세스 B (소비자) fd = shm_open("/buf", O_RDONLY, 0); ptr = mmap(fd, ...) 데이터 읽기 /dev/shm (tmpfs) /dev/shm/buf (shmem inode) shm_open + mmap shm_open + mmap 공유 물리 페이지 (RAM) 동일한 folio를 두 프로세스가 매핑 프로세스 A 가상 주소 공간 프로세스 B 가상 주소 공간

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)
APIshmget/shmat/shmdt/shmctlshm_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 백업 저장소를 활용합니다.

shmem_file_setup() mm/shmem.c | kern_mount(shm_mnt) 위에 파일 생성 DRM GEM 객체 i915, amdgpu, nouveau memfd_create() 익명 공유 메모리 + seals System V SHM ipc/shm.c newseg() splice / pipe zero-copy 전송 shmem inode + folio 페이지 캐시 저장 스왑 백엔드 swap_entry_t 관리 mmap(): vm_area_struct → shmem_vm_ops → shmem_fault()

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의 관리를 받습니다.

shmem 메모리 회수 흐름 메모리 압력 발생 (low watermark) kswapd (비동기) 직접 회수 (동기) shrink_folio_list() LRU 스캔 shmem folio? shmem_writepage() 일반 writeback 경로 아니오 스왑 공간에 기록 + 해제 스왑 없으면 OOM killer
위험: 스왑 공간이 없거나 부족한 상태에서 tmpfs가 대량의 메모리를 사용하면 OOM(Out Of Memory) killer가 호출될 수 있습니다. 프로덕션 환경에서는 반드시 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_SHMEMyshmem 핵심 구현 활성화. 비활성화 시 ramfs로 대체
CONFIG_TMPFSytmpfs 사용자 공간 마운트 지원
CONFIG_TMPFS_POSIX_ACLytmpfs에서 POSIX ACL 지원
CONFIG_TMPFS_XATTRytmpfs에서 확장 속성(xattr) 지원
CONFIG_TMPFS_INODE64y64비트 inode 번호 기본 활성화
CONFIG_TRANSPARENT_HUGEPAGEyTHP 지원 (tmpfs 포함)
CONFIG_SWAPy스왑 서브시스템 활성화 (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 프로그램을 배치하는 것을 방지해야 합니다.

tmpfs 보안 계층 마운트 옵션 보안 noexec nosuid nodev size= (크기 제한) 파일/디렉터리 퍼미션 mode=1777 (sticky bit) / uid= / gid= / POSIX ACL 커널 보안 모듈 (LSM) SELinux / AppArmor / seccomp (tmpfs 접근 제어) 네임스페이스 / cgroup mount namespace 격리 / memory cgroup 메모리 제한 심층 방어

보안 강화 마운트 예시

# /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 성능 비교

워크로드tmpfsext4 (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
핵심 포인트: tmpfs는 디스크 I/O가 없으므로 순수 메모리 대역폭에 가까운 성능을 제공합니다. 빌드 시스템의 임시 파일, 데이터베이스 임시 테이블스페이스, 컴파일 캐시 등에 활용하면 큰 성능 이점을 얻을 수 있습니다.

주요 튜닝 포인트

# 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/shmPOSIX 공유 메모리 (shm_open)RAM 50%
/run런타임 데이터 (PID 파일, 소켓)RAM 20% 또는 800MB
/run/lock잠금 파일5MB
/sys/fs/cgroupcgroup 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 기반 특성에 맞게 최적화되어 있습니다.

shmem (VFS 구현) super_operations alloc_inode destroy_inode statfs / evict_inode inode_operations setattr / getattr tmpfile (O_TMPFILE) fileattr_set/get file_operations read_iter / write_iter (generic) splice_read / splice_write mmap / fallocate / llseek address_space_operations writepage → shmem_writepage write_begin / write_end dirty_folio / migrate_folio vm_operations_struct fault → shmem_fault huge_fault → shmem_huge_fault shmem_dir_inode_operations create / link / unlink / mkdir / rmdir / rename / symlink / mknod tmpfile (O_TMPFILE 지원) / get_offset_ctx
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: 지정 범위를 제거하고 뒤쪽 데이터를 앞으로 당깁니다. 파일 크기가 줄어듭니다.

참고자료

다음 학습: