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_private๊ฐ€ shmem_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 ํ˜ธ์ถœ ํ๋ฆ„