Ryotta's Linux 7.0 MM

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

tmpfs / shmem

관련 소스: mm/shmem.c, include/linux/shmem_fs.h
관련 문서: 메모리 관리 개요 · Folio / Page Cache · Swap / zswap · Memory Cgroup

개요 (Overview)

tmpfs는 리눅스 커널의 가상 메모리 기반 파일 시스템으로, 모든 데이터가 물리 메모리에 저장되며 시스템 종료 시 사라집니다. shmem(shared memory)는 tmpfs의 내부 구현체로, SysV IPC 공유 메모리, POSIX 공유 메모리, mmap된 익명 파일 등을 구현합니다. tmpfs는 swap을 사용할 수 있어 외부 메모리로 쫓겨날 수 있으며, 이를 통해 물리 메모리보다 큰 가상 파일을 생성할 수 있습니다.

커널 내부에서 shmem은 파일 캐시(folio/page cache)와 swap 캐시를 연결하는 중간 다리 역할을 합니다. 페이지가 메모리에 있으면 file cache로, swap으로 쫓겨나면 swap entry로 관리됩니다. 이를 통해 anonymous shared memory와 file-backed memory를 동일한 인터페이스로 처리합니다.

운영 관점에서는 tmpfs를 "디스크 파일처럼 보이지만 실제로는 메모리 압박에 따라 swap까지 왕복하는 파일 객체"로 이해하면 편합니다. memfd_create(), /dev/shm, 브라우저/DB의 공유 메모리 세그먼트, 컨테이너 내부의 tmpfs 마운트가 모두 같은 shmem 코드 경로 위에서 동작하므로, page cache, reclaim, memcg, THP 설정이 함께 얽힙니다.

/* 주요 소스 파일 */
mm/shmem.c                    // shmem 핵심 구현
include/linux/shmem_fs.h      // 핵심 구조체 정의
mm/shmem_quota.c              // quota 지원

빠른 점검 명령

# tmpfs 마운트 확인
mount | grep tmpfs

# tmpfs 사용량 확인
df -h /dev/shm
df -h /tmp

# shmem 메모리 사용량 확인
cat /proc/meminfo | grep -i shmem

# 시스템 전체 shmem 페이지 수
cat /proc/vmstat | grep nr_shmem

# hugepage 사용 중인 shmem 페이지
cat /proc/vmstat | grep nr_shmem_thps

# 특정 프로세스의 shmem 사용량
pmap -x <PID> | grep shmem

# tmpfs 파일 시스템 정보
stat -f /dev/shm

# slabinfo에서 shmem 관련 slab 확인
cat /proc/slabinfo | grep shmem_inode_cache

# NUMA 노드별 shmem 할당 정책 확인
cat /proc/<PID>/numa_maps | grep shmem

# shmem THP 정책 확인
cat /sys/kernel/mm/transparent_hugepage/shmem_enabled

# tmpfs 마운트 옵션 확인
findmnt -no TARGET,OPTIONS /dev/shm

# 프로세스별 shared/tmpfs 매핑 크기 확인
grep -E 'Shared_Clean|Shared_Dirty|Anonymous|ShmemPmdMapped' /proc/<PID>/smaps

핵심 자료구조

struct shmem_inode_info

shmem inode의 핵심 확장 구조체. 일반 inode보다 큰 메모리 할당이 필요합니다.

/* include/linux/shmem_fs.h:36-59 */
struct shmem_inode_info {
    spinlock_t          lock;
    unsigned int        seals;          // shmem seal 비트
    unsigned long       flags;
    unsigned long       alloced;        // 파일에 할당된 데이터 페이지 수
    unsigned long       swapped;        // swap으로 넘긴 페이지 수의 소계
    union {
        struct offset_ctx dir_offsets;  // 안정적인 디렉터리 오프셋
        struct {
            struct list_head shrinklist; // 축소 가능한 huge folio inode 목록
            struct list_head swaplist;   // swap 위 inode 체인
        };
    };
    struct timespec64   i_crtime;       // 파일 생성 시간
    struct shared_policy policy;        // NUMA 메모리 할당 정책
    struct simple_xattrs xattrs;        // xattr 목록
    pgoff_t             fallocend;      // 가장 높은 fallocate end index
    unsigned int        fsflags;        // FS_IOC_[SG]ETFLAGS용
    atomic_t            stop_eviction;  // inode 작업 중 hold
#ifdef CONFIG_TMPFS_QUOTA
    struct dquot __rcu  *i_dquot[MAXQUOTAS];
#endif
    struct inode        vfs_inode;
};

핵심 필드 설명:

  • alloced: file cache에 존재하는 페이지 수 (swap에 있는 것 포함)
  • swapped: swap으로 쫓겨난 페이지 수. alloced == nrpages + swapped 관계
  • shrinklist: 메모리 부족 시 huge page를 분할(fsplit)하기 위한 inode 목록
  • swaplist: shmem_unuse()가 swap off 시 inode를 찾기 위한 체인
  • struct shmem_sb_info

    superblock 수준의 shmem 메타데이터 관리 구조체.

    /* include/linux/shmem_fs.h:73-92 */
    struct shmem_sb_info {
        unsigned long max_blocks;           // 블록 수 제한
        struct percpu_counter used_blocks;  // 할당된 블록 수
        unsigned long max_inodes;           // inode 수 제한
        unsigned long free_ispace;          // 남은 inode 공간
        raw_spinlock_t stat_lock;           // sb_info 변경 직렬화
        umode_t mode;                       // 루트 디렉토리 마운트 모드
        unsigned char huge;                 // hugepage 사용 여부
        kuid_t uid;                         // 루트 디렉토리 UID
        kgid_t gid;                         // 루트 디렉토리 GID
        bool full_inums;                    // ino uint/ino_t 선택
        bool noswap;                        // swap 사용 불가 플래그
        ino_t next_ino;                     // 다음 사용할 inode 번호
        ino_t __percpu *ino_batch;          // per-cpu inode 번호 배치
        struct mempolicy *mpol;             // 기본 NUMA 정책
        spinlock_t shrinklist_lock;         // shrinklist 보호
        struct list_head shrinklist;        // 축소 가능한 inode 목록
        unsigned long shrinklist_len;       // shrinklist 길이
        struct shmem_quota_limits qlimits;  // quota 제한
    };

    핵심 플래그:

  • SHMEM_F_NORESERVE (BIT(0)): 전체 객체 사전 할당 비활성화
  • SHMEM_F_LOCKED (BIT(1)): swap 사용 불가 (SysV SHM_LOCK용)
  • SHMEM_F_MAPPING_FROZEN (BIT(2)): grow/shrink/hole punch 불가 (folio pinning용)
  • struct shmem_falloc

    fallocate 작업 중 hole punch 대기 상태를 담는 구조체.

    /* mm/shmem.c:105-111 */
    struct shmem_falloc {
        wait_queue_head_t *waitq;  // hole punch 대기 큐
        pgoff_t start;            // 현재 fallocate 중인 범위 시작
        pgoff_t next;             // 다음 fallocate할 페이지 오프셋
        pgoff_t nr_falloced;      // 새로 fallocate된 페이지 수
        pgoff_t nr_unswapped;     // writeout이 swap을 거부한 횟수
    };

    핵심 함수

    shmem_get_folio_gfp()

    페이지 캐시에서 folio를 찾거나, swap에서 읽어오거나, 새롭게 할당합니다.

    /* mm/shmem.c:2467-2644 */
    static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
            loff_t write_end, struct folio **foliop, enum sgp_type sgp,
            gfp_t gfp, struct vm_fault *vmf, vm_fault_t *fault_type)
    /* mm/shmem.c:2491-2557 */
    folio = filemap_get_entry(inode->i_mapping, index);
    if (folio && vma && userfaultfd_minor(vma)) {
        if (!xa_is_value(folio))
            folio_put(folio);
        *fault_type = handle_userfault(vmf, VM_UFFD_MINOR);
        return 0;
    }
    
    if (xa_is_value(folio)) {
        error = shmem_swapin_folio(inode, index, &folio,
                       sgp, gfp, vma, fault_type);
        if (error == -EEXIST)
            goto repeat;
    
        *foliop = folio;
        return error;
    }
    
    if (folio) {
        folio_lock(folio);
    
        /* folio가 truncate되었거나 swap out되었는지 확인 */
        if (unlikely(folio->mapping != inode->i_mapping)) {
            folio_unlock(folio);
            folio_put(folio);
            goto repeat;
        }
        if (sgp == SGP_WRITE)
            folio_mark_accessed(folio);
        if (folio_test_uptodate(folio))
            goto out;
        /* fallocate된 folio */
        if (sgp != SGP_READ)
            goto clear;
        folio_unlock(folio);
        folio_put(folio);
    }
    
    /*
     * SGP_READ: hole이면 NULL folio와 0을 반환하고 호출자가 zero 처리.
     * SGP_NOALLOC: hole이면 NULL folio와 -ENOENT를 반환하고 호출자가 실패 처리.
     */
    *foliop = NULL;
    if (sgp == SGP_READ)
        return 0;
    if (sgp == SGP_NOALLOC)
        return -ENOENT;
    
    /* 빠른 cache/swap 조회에서 찾지 못했으므로 새 folio를 할당한다. */
    if (vma && userfaultfd_missing(vma)) {
        *fault_type = handle_userfault(vmf, VM_UFFD_MISSING);
        return 0;
    }
    
    /* anonymous shmem과 tmpfs에 허용된 hugepage order를 찾는다. */
    orders = shmem_allowable_huge_orders(inode, vma, index, write_end, false);
    if (orders > 0) {
        gfp_t huge_gfp;
    
        huge_gfp = vma_thp_gfp_mask(vma);
        huge_gfp = limit_gfp_mask(huge_gfp, gfp);
        folio = shmem_alloc_and_add_folio(vmf, huge_gfp,
                inode, index, fault_mm, orders);
        if (!IS_ERR(folio)) {
            if (folio_test_pmd_mappable(folio))
                count_vm_event(THP_FILE_ALLOC);
            count_mthp_stat(folio_order(folio), MTHP_STAT_SHMEM_ALLOC);
            goto alloced;
        }
        if (PTR_ERR(folio) == -EEXIST)
            goto repeat;
    }

    분기 로직:

    1. filemap_get_entry()로 페이지 캐시 탐색

    2. xa_is_value(folio) → swap entry → shmem_swapin_folio() 호출

    3. folio 존재 + locked → uptodate 확인 후 반환

    4. 없음 → SGP_READ/SGP_NOALLOC이면 반환, 그 외 할당

    5. hugepage 허용 시 shmem_alloc_and_add_folio() (large folio)

    6. 일반 할당 시 shmem_alloc_and_add_folio() (order 0)

    shmem_swapin_folio()

    swap entry를 실제 folio로 읽어옵니다.

    /* mm/shmem.c:2293-2456 */
    static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
                 struct folio **foliop, enum sgp_type sgp,
                 gfp_t gfp, struct vm_area_struct *vma,
                 vm_fault_t *fault_type)
    /* mm/shmem.c:2335-2359 */
    folio = swap_cache_get_folio(swap);
    if (!folio) {
        if (data_race(si->flags & SWP_SYNCHRONOUS_IO)) {
            /* swap cache와 readahead를 건너뛴 direct swapin */
            folio = shmem_swap_alloc_folio(inode, vma, index,
                               index_entry, order, gfp);
            if (IS_ERR(folio)) {
                error = PTR_ERR(folio);
                folio = NULL;
                goto failed;
            }
        } else {
            /* cached swapin은 order 0 folio만 지원 */
            folio = shmem_swapin_cluster(swap, gfp, info, index);
            if (!folio) {
                error = -ENOMEM;
                goto failed;
            }
        }
        if (fault_type) {
            *fault_type |= VM_FAULT_MAJOR;
            count_vm_event(PGMAJFAULT);
            count_memcg_event_mm(fault_mm, PGMAJFAULT);
        }
    } else {
        swap_update_readahead(folio, NULL, 0);
    }
    /* mm/shmem.c:2425-2437 */
    error = shmem_add_to_page_cache(folio, mapping, index,
                    swp_to_radix_entry(swap), gfp);
    if (error)
        goto failed;
    
    shmem_recalc_inode(inode, 0, -nr_pages);
    if (sgp == SGP_WRITE)
        folio_mark_accessed(folio);
    
    folio_put_swap(folio, NULL);
    swap_cache_del_folio(folio);
    folio_mark_dirty(folio);

    흐름:

    1. radix_to_swp_entry()로 swap entry 변환

    2. shmem_confirm_swap()으로 swap entry 유효성 확인

    3. swap_cache_get_folio()로 swap cache에서 기존 folio 탐색

    4. 없으면 SWP_SYNCHRONOUS_IO 플래그에 따라:

    - 직렬 IO: shmem_swap_alloc_folio() (대기 없이 직접 할당)

    - 비직렬: shmem_swapin_cluster() (readahead 포함)

    5. swap entry 분할 필요 시 shmem_split_large_entry() 호출

    6. shmem_add_to_page_cache()로 페이지 캐시에 추가

    7. folio_put_swap() + swap_cache_del_folio()로 swap cache에서 제거

    shmem_alloc_and_add_folio()

    새 folio를 할당하고 페이지 캐시에 추가합니다.

    /* mm/shmem.c:1936-2038 */
    static struct folio *shmem_alloc_and_add_folio(struct vm_fault *vmf,
            gfp_t gfp, struct inode *inode, pgoff_t index,
            struct mm_struct *fault_mm, unsigned long orders)
    /* mm/shmem.c:1978-2031 */
    __folio_set_locked(folio);
    __folio_set_swapbacked(folio);
    
    gfp &= GFP_RECLAIM_MASK;
    error = mem_cgroup_charge(folio, fault_mm, gfp);
    if (error) {
        if (xa_find(&mapping->i_pages, &index,
                index + pages - 1, XA_PRESENT)) {
            error = -EEXIST;
        } else if (pages > 1) {
            if (pages == HPAGE_PMD_NR) {
                count_vm_event(THP_FILE_FALLBACK);
                count_vm_event(THP_FILE_FALLBACK_CHARGE);
            }
            count_mthp_stat(folio_order(folio), MTHP_STAT_SHMEM_FALLBACK);
            count_mthp_stat(folio_order(folio), MTHP_STAT_SHMEM_FALLBACK_CHARGE);
        }
        goto unlock;
    }
    
    error = shmem_add_to_page_cache(folio, mapping, index, NULL, gfp);
    if (error)
        goto unlock;
    
    error = shmem_inode_acct_blocks(inode, pages);
    if (error) {
        struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
        long freed;
        /*
         * 파일시스템에서 i_size 밖 large folio를 조금 쪼개서
         * 공간을 되찾아 본다.
         */
        shmem_unused_huge_shrink(sbinfo, NULL, pages);
        /*
         * freed page를 반영하려고 shmem_recalc_inode()와 비슷한 정리를 한다.
         * 다만 현재 folio는 아직 cache에 있으므로 완전히 균형이 맞지는 않는다.
         */
        spin_lock(&info->lock);
        freed = pages + info->alloced - info->swapped -
            READ_ONCE(mapping->nrpages);
        if (freed > 0)
            info->alloced -= freed;
        spin_unlock(&info->lock);
        if (freed > 0)
            shmem_inode_unacct_blocks(inode, freed);
        error = shmem_inode_acct_blocks(inode, pages);
        if (error) {
            filemap_remove_folio(folio);
            goto unlock;
        }
    }
    
    shmem_recalc_inode(inode, pages, 0);
    folio_add_lru(folio);

    흐름:

    1. shmem_suitable_orders()로 사용 가능한 huge page order 확인

    2. shmem_alloc_folio()로 NUMA 정책 적용하여 할당

    3. mem_cgroup_charge()로 memcg 충전

    4. shmem_add_to_page_cache()로 XArray에 추가

    5. shmem_inode_acct_blocks()로 블록 할당량 확인

    6. shmem_recalc_inode()로 inode 통계 갱신

    shmem_writeout()

    folio를 swap으로 내보냅니다.

    /* mm/shmem.c:1590-1737 */
    int shmem_writeout(struct folio *folio, struct swap_iocb **plug,
            struct list_head *folio_list)
    /* mm/shmem.c:1601-1642 */
    if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap)
        goto redirty;
    
    if (!total_swap_pages)
        goto redirty;
    
    if (folio_test_large(folio)) {
        index = shmem_fallocend(inode,
            DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE));
        if ((index > folio->index && index < folio_next_index(folio)) ||
            !IS_ENABLED(CONFIG_THP_SWAP))
            split = true;
    }
    
    if (split) {
        int order;
    
    try_split:
        order = folio_order(folio);
        /* subpage가 여전히 dirty 상태가 되도록 보장 */
        folio_test_set_dirty(folio);
        if (split_folio_to_list(folio, folio_list))
            goto redirty;
    
    #ifdef CONFIG_TRANSPARENT_HUGEPAGE
        if (order >= HPAGE_PMD_ORDER) {
            count_memcg_folio_events(folio, THP_SWPOUT_FALLBACK, 1);
            count_vm_event(THP_SWPOUT_FALLBACK);
        }
    #endif
        count_mthp_stat(order, MTHP_STAT_SWPOUT_FALLBACK);
    
        folio_clear_dirty(folio);
    }
    /* mm/shmem.c:1679-1703 */
    if (!folio_alloc_swap(folio)) {
        bool first_swapped = shmem_recalc_inode(inode, 0, nr_pages);
    
        /*
         * 아직 swaplist에 없으면 지금 추가해서 shmem_unuse()가
         * swap된 inode를 다시 찾을 수 있게 한다.
         */
        if (first_swapped) {
            spin_lock(&shmem_swaplist_lock);
            if (list_empty(&info->swaplist))
                list_add(&info->swaplist, &shmem_swaplist);
            spin_unlock(&shmem_swaplist_lock);
        }
    
        folio_dup_swap(folio, NULL);
        shmem_delete_from_page_cache(folio, swp_to_radix_entry(folio->swap));
    
        BUG_ON(folio_mapped(folio));
        error = swap_writeout(folio, plug);
        if (error != AOP_WRITEPAGE_ACTIVATE)
            return error;
    }

    흐름:

    1. SHMEM_F_LOCKED 또는 noswap이면 즉시 redirty

    2. large folio인 경우 split_folio_to_list()로 분할

    3. folio_alloc_swap()으로 swap 할당

    4. shmem_delete_from_page_cache()로 캐시에서 제거

    5. swap_writeout()으로 실제 swap 디스크에 기록

    6. 실패 시 캐시에 다시 추가

    shmem_falloc_wait()

    hole punch 진행 중 발생한 페이지 폴트를 대기시킵니다.

    /* mm/shmem.c:2708-2747 */
    static vm_fault_t shmem_falloc_wait(struct vm_fault *vmf, struct inode *inode)
    /* mm/shmem.c:2714-2741 */
    spin_lock(&inode->i_lock);
    shmem_falloc = inode->i_private;
    if (shmem_falloc &&
        shmem_falloc->waitq &&
        vmf->pgoff >= shmem_falloc->start &&
        vmf->pgoff < shmem_falloc->next) {
        wait_queue_head_t *shmem_falloc_waitq;
        DEFINE_WAIT_FUNC(shmem_fault_wait, synchronous_wake_function);
    
        ret = VM_FAULT_NOPAGE;
        fpin = maybe_unlock_mmap_for_io(vmf, NULL);
        shmem_falloc_waitq = shmem_falloc->waitq;
        prepare_to_wait(shmem_falloc_waitq, &shmem_fault_wait,
                TASK_UNINTERRUPTIBLE);
        spin_unlock(&inode->i_lock);
        schedule();
    
        /*
         * shmem_falloc_waitq는 hole-punch task의 stack을 가리킨다.
         * 여기 도달할 때쯤 무효가 될 수 있지만, 그 경우 finish_wait()는
         * 역참조하지 않는다. 다만 wake_up_all()과의 경합 때문에 i_lock은 필요하다.
         */
        spin_lock(&inode->i_lock);
        finish_wait(shmem_falloc_waitq, &shmem_fault_wait);
    }
    spin_unlock(&inode->i_lock);

    동작:

    1. inode->i_privateshmem_falloc이면

    2. waitq에서 TASK_UNINTERRUPTIBLE로 대기

    3. hole punch 완료 후 wake_up으로 깨어남

    shmem_fault()

    VMA fault 핸들러로, hole punch 대기와 shmem_get_folio_gfp() 호출을 연결합니다.

    /* mm/shmem.c:2749-2777 */
    static vm_fault_t shmem_fault(struct vm_fault *vmf)
    /* mm/shmem.c:2761-2776 */
    if (unlikely(inode->i_private)) {
        ret = shmem_falloc_wait(vmf, inode);
        if (ret)
            return ret;
    }
    
    err = shmem_get_folio_gfp(inode, vmf->pgoff, 0, &folio, SGP_CACHE,
                  gfp, vmf, &ret);
    if (err)
        return vmf_error(err);
    if (folio) {
        vmf->page = folio_file_page(folio, vmf->pgoff);
        ret |= VM_FAULT_LOCKED;
    }
    return ret;

    역할:

    1. inode->i_private가 살아 있으면 punch/fallocate 경합을 먼저 해소

    2. SGP_CACHE로 캐시 조회, swapin, 새 folio 할당을 통합 처리

    3. 최종적으로 vmf->page에 fault 대상 subpage를 연결하고 VM_FAULT_LOCKED를 반환

    호출 흐름

    shmem_fault (VMA 폴트 진입점)
        ├── shmem_falloc_wait (hole punch 대기)
        └── shmem_get_folio_gfp
            ├── filemap_get_entry → 페이지 캐시 탐색
            ├── shmem_swapin_folio → swap에서 읽기
            │   ├── swap_cache_get_folio → swap cache 히트
            │   ├── shmem_swap_alloc_folio → 새 folio 할당
            │   ├── shmem_split_large_entry → large swap entry 분할
            │   └── shmem_add_to_page_cache → 캐시에 추가
            ├── shmem_alloc_and_add_folio → 새 folio 할당
            │   ├── shmem_alloc_folio → 메모리 할당
            │   ├── mem_cgroup_charge → memcg 충전
            │   └── shmem_add_to_page_cache → 캐시에 추가
            └── shmem_recalc_inode → 통계 갱신
    
    shmem_writeout (swap 내보내기)
        ├── split_folio_to_list → large folio 분할
        ├── folio_alloc_swap → swap 할당
        ├── shmem_delete_from_page_cache → 캐시 제거
        └── swap_writeout → swap 디스크 기록
    
    shmem_undo_range (truncate/punch hole)
        ├── find_lock_entries → 대상 folio 검색
        ├── truncate_inode_folio → folio 제거
        └── shmem_free_swap → swap entry 해제
    
    shmem_unuse (swap off)
        ├── shmem_unuse_inode → inode 내 swap 탐색
        │   ├── shmem_find_swap_entries → swap entry 찾기
        │   └── shmem_unuse_swap_entries → swap → 캐시 복원
        └── shmem_unuse_inode 반복
    
    shmem_fault → shmem_get_folio_gfp → shmem_writeout → swap_writeout

    조건별 비교

    SGP (shmem_get_folio) 타입별 동작

    /* include/linux/shmem_fs.h:166-171 */
    enum sgp_type {
        SGP_READ,   // i_size를 넘지 않고 페이지를 할당하지 않음
        SGP_NOALLOC, // 비슷하지만 hole에서는 실패하거나 fallocated 페이지를 사용
        SGP_CACHE,  // i_size를 넘지 않으며 필요하면 페이지를 할당함
        SGP_WRITE,  // i_size를 넘을 수 있고 !Uptodate 페이지도 할당함
        SGP_FALLOC, // SGP_WRITE와 비슷하지만 기존 페이지를 Uptodate로 만듦
    };
    SGP 타입i_size 초과페이지 할당사용 시점
    SGP_READ불가불가읽기만, hole은 NULL 반환
    SGP_NOALLOC불가불가hole이면 -ENOENT 반환
    SGP_CACHE불가가능일반 읽기/쓰기 (페이지 폴트)
    SGP_WRITE가능가능쓰기 시, new folio에 referenced 설정
    SGP_FALLOC가능가능fallocate, existing folio 초기화

    Huge Page 지원 모드

    모드동작마운트 옵션
    SHMEM_HUGE_NEVER (0)huge page 사용 안함`huge=never`
    SHMEM_HUGE_ALWAYS (1)항상 huge page 시도`huge=always`
    SHMEM_HUGE_WITHIN_SIZE (2)i_size 내에 있을 때만`huge=within_size`
    SHMEM_HUGE_ADVISE (3)madvise(MADV_HUGEPAGE) 시에만`huge=advise`
    SHMEM_HUGE_DENY (-1)전체 비활성화 (긴급용)sysfs에서 설정
    SHMEM_HUGE_FORCE (-2)전체 강제 활성화 (테스트용)sysfs에서 설정

    fallocate 모드별 동작

    모드동작반환값
    기본 (0)할당 + zeroing0
    FALLOC_FL_KEEP_SIZEi_size 변경 없이 할당0
    FALLOC_FL_PUNCH_HOLEhole punch + truncate0
    SHMEM_F_MAPPING_FROZEN모든 변경 불가-EPERM
    F_SEAL_GROW확장 불가-EPERM
    F_SEAL_SHRINK축소 불가-EPERM
    F_SEAL_WRITE쓰기 불가-EPERM

    Swap In 경로 선택

    조건경로설명
    SWP_SYNCHRONOUS_IOshmem_swap_alloc_folio대기 없이 직접 할당
    cached swapswap_cache_get_folio기존 swap cache 히트
    !SWP_SYNCHRONOUS_IOshmem_swapin_clusterreadahead 포함
    uffd armedorder 0 fallbackper-page fault 보장
    zswap 비활성화order 0 fallbackzswap 고려 없음

    관련 문서

  • 메모리 관리 개요 - 전체 메모리 관리 아키텍처
  • Folio / Page Cache - folio 구조와 page cache 관리
  • Swap / zswap - swap 하위 시스템
  • Memory Cgroup - memcg 충전/회수
  • Buddy Allocator - 물리 페이지 할당
  • VMA / mmap - 가상 메모리 영역 관리
  • SVG 다이어그램

    shmem 구조 관계도

    shmem 구조 관계도

    shmem 호출 흐름

    shmem 호출 흐름