tmpfs In-Memory Filesystem
tmpfs is a Linux filesystem whose files live entirely in the kernel’s page cache — there is no block device behind it, and nothing is ever written to disk unless the page-cache pages are swapped out under memory pressure. It is implemented in
mm/shmem.c, which is why the kernel uses the names tmpfs and shmem (“shared memory”) interchangeably: the very same code backs/tmp,/dev/shm,/run, every POSIX shared-memory object (shm_open), every System V shared-memory segment, and everyMAP_SHARED|MAP_ANONYMOUSmapping. Because its pages are ordinary swappable page-cache folios, tmpfs grows and shrinks on demand and can spill to swap — distinguishing it from ramfs, which pins its pages in RAM forever and cannot be size-limited (tmpfs.rst, v6.12). The single most important idea: tmpfs is not a RAM disk; it is the page cache exposed as a mountable filesystem, with swap as its overflow.
Mental Model
The intuitive but wrong mental model of tmpfs is “a chunk of RAM formatted as a disk.” That is brd (the /dev/ram* block-RAM-disk driver) or a loop-mounted file — a fixed lump of memory with a real filesystem layered on top. tmpfs is the opposite: it has no block layer at all (tmpfs.rst, v6.12, lines 29–35). A tmpfs file is just an inode whose data pages hang directly off an address_space in the page cache — the same address_space machinery a disk file uses, except that for a disk file a cache miss triggers a read from the block device, whereas for a tmpfs file a “miss” on a never-written page is satisfied by allocating a zero-filled page on the spot. There is no canonical on-disk copy to fall back to; the page cache is the filesystem.
That single design choice explains every tmpfs property. Files cost only the memory of the bytes actually stored, so the filesystem grows and shrinks dynamically. Because the pages are normal swappable page-cache pages, the memory-reclaim machinery can push them to swap when RAM is tight — so a “RAM filesystem” can, paradoxically, hold more than RAM if you have swap. And because there is no persistent backing store, unmounting (or rebooting) discards everything.
flowchart TB subgraph US["Userspace"] APP["open / write / mmap<br/>on /tmp, /dev/shm"] SHM["shm_open() / shmget()<br/>MAP_SHARED|MAP_ANONYMOUS"] end APP --> VFS["VFS layer"] SHM -->|"internal shm_mnt<br/>(invisible mount)"| SHMEM VFS -->|"per-fs ops"| SHMEM["mm/shmem.c<br/>(tmpfs == shmem)"] SHMEM --> PC["Page cache<br/>(address_space + folios)"] PC -->|"normal page"| RAM["Physical RAM"] PC -->|"under memory pressure<br/>shmem_writepage()"| SC["Swap cache"] SC --> SWAP["Swap device / file"] RAM -.->|"accounted as Shmem<br/>in /proc/meminfo"| METER["free / df / du"]
How tmpfs relates to the page cache and swap. What it shows: both the visible mounts (/tmp, /dev/shm) and the invisible kernel-internal shm_mnt (used by shm_open, System V shared memory, and anonymous shared mmap) funnel into the same mm/shmem.c code, whose pages sit in the page cache and can be evicted to swap. The insight: there is only one shmem subsystem; “tmpfs,” “POSIX shm,” “SysV shm,” and “anonymous shared memory” are four front doors onto it, which is why they all show up under the Shmem counter in /proc/meminfo (tmpfs.rst, v6.12, lines 37–41).
tmpfs Extends ramfs
The cleanest way to understand tmpfs is as ramfs plus three things. The shmem source says so directly: shmem “is heavily based on the ramfs. It extends ramfs by the ability to use swap and honor resource limits” (mm/shmem.c, v6.12, lines 49–51). ramfs is the minimal “page cache as a filesystem” — when you write to a ramfs file, the data is added to the page cache and dirty pages are never written back to any backing store, so the pages simply stay resident forever. ramfs has no size limit (its only limit is “all of physical memory”), cannot swap, and accepts no tunable mount parameters (tmpfs.rst, v6.12, lines 19–27).
tmpfs adds, on top of that ramfs core:
- Swap. tmpfs pages are reclaimable: under memory pressure the kernel can move them to swap rather than keeping them pinned. ramfs pages cannot be reclaimed at all, which is why an unbounded ramfs can hard-lock a machine by exhausting unswappable memory.
- Resource limits. tmpfs honors
size=/nr_blocks=(a byte/block cap) andnr_inodes=(a file-count cap), enforced per mount. ramfs has none. - Filesystem-level features. POSIX ACLs and extended attributes (in the
trusted.*,security.*, anduser.*namespaces), Transparent Huge Pages, NUMA memory policy, disk-quota accounting, and — added in 6.16 — case-insensitive (casefold) lookup (tmpfs.rst, v6.18, lines 244–265).
Tellingly, when CONFIG_TMPFS (and CONFIG_SHMEM) is not set, mm/shmem.c falls back to literally reusing the ramfs implementation: shmem_init_fs_context, the file operations, and shmem_get_inode are #define-aliased straight to their ramfs_* counterparts (mm/shmem.c, v6.12, lines 5136–5199). The shmem internal machinery is always present (the kernel needs it for shared anonymous mappings and SysV shm regardless of CONFIG_TMPFS), but its tmpfs face degrades to ramfs.
How Swapping Works: shmem_writepage
This is the mechanical heart of “tmpfs pages can be swapped.” Every address_space has a table of operations, and shmem’s includes ->writepage = shmem_writepage. For an ordinary disk filesystem, writepage flushes a dirty page back to its block device; for tmpfs there is no block device, so shmem_writepage instead moves the page into the swap cache and lets the swap subsystem write it to a swap device (mm/shmem.c, v6.12, the shmem_writepage function near line ~1480).
The walk-through, following the actual v6.12 code:
- It refuses to run except under reclaim. The function opens with
if (WARN_ON_ONCE(!wbc->for_reclaim)) goto redirty;. tmpfs deliberately does not participate in normal periodic writeback the way disk filesystems do — there is nowhere durable to write to. It only ever pushes a page to swap when the memory reclaim path (kswapdor direct reclaim) calls it because RAM is scarce. (wbcis thewriteback_control;for_reclaimis the flag set when the caller is reclaiming memory.) This is the code-level reason tmpfs files survive in RAM until pressure forces them out. - It bails if swapping is disabled.
if (WARN_ON_ONCE(... sbinfo->noswap)) goto redirty;andif (!total_swap_pages) goto redirty;— if the mount was created withnoswap, or if there is no swap configured at all, the page is simply re-dirtied and kept in RAM.sbinfois the per-superblockshmem_sb_info;noswapis the field set by thenoswapmount option (tmpfs.rst, v6.12, lines 103–109). - It allocates a swap slot and moves the page into the swap cache.
swap = folio_alloc_swap(folio);reserves a slot on a swap device; if none is free, the page is re-dirtied (goto redirty). On success the inode is added toshmem_swaplist(so the page can later be found and brought back),add_to_swap_cache(folio, swap, ...)inserts the folio into the swap cache, andshmem_delete_from_page_cache(folio, swp_to_radix_entry(swap))replaces the page’s slot in the file’saddress_spacewith a swap entry — a special marker that records “this file offset now lives at this swap slot.” - Finally it writes the page out.
return swap_writepage(&folio->page, wbc);hands the folio to the generic swap writeout path. (folio_alloc_swap,add_to_swap_cache, andswap_writepageare the same primitives anonymous-memory swapping uses — confirming that, from the swap subsystem’s view, a swapped tmpfs page is indistinguishable from a swapped anonymous page.)
The reverse path, shmem_swapin_folio, runs on a page fault or read against a file offset whose address_space slot holds a swap entry: it allocates a folio, reads the bytes back from swap, and reinstalls the folio in the page cache. The upshot is that swap is transparent — userspace never sees that a tmpfs page took a detour through the swap device.
Cross-link, not duplication
The swap-cache lifecycle, swap-entry encoding, and how the swap device is written are owned by The Swap Cache and Linux Swap Subsystem. This note shows only the tmpfs entry point into that machinery. The page-cache and
address_spacemechanics are owned by The Page Cache and address_space; huge-folio handling by Folios and the Page Cache.
The Four Front Doors onto shmem
tmpfs’s reach is wider than the visible /tmp mount. mm/shmem.c is reached four ways:
- Explicit tmpfs mounts.
mount -t tmpfs ... /mnt— the obvious case./tmp,/var/tmp,/dev/shm, and/runare all tmpfs mounts on a modern systemd distribution. - POSIX shared memory.
shm_open(3)“makes use of a dedicated tmpfs(5) filesystem that is normally mounted under /dev/shm” (shm_open(3) man page). The standard pattern isshm_open()→ftruncate()(to size the zero-length object) →mmap(); the returned fd is just an open file on the/dev/shmtmpfs. glibc 2.2+ expects/dev/shmto exist for this reason (tmpfs.rst, v6.12, lines 53–57). - System V shared memory.
shmget(2)/shmat(2)segments are backed by the kernel-internal mountshm_mnt, which is invisible — it has no entry in/proc/mountsand exists even whenCONFIG_TMPFSis off (tmpfs.rst, v6.12, lines 45–65). It is not/dev/shm; a common misconception is that SysV shm needs/dev/shmmounted, but the internal mount handles it. - Shared anonymous memory.
mmap(MAP_SHARED|MAP_ANONYMOUS)and the copy a child inherits acrossfork()are also backed by shmem files onshm_mnt. Internally,shmem_kernel_file_setup()andshmem_zero_setup()create an unlinked file onshm_mnt:return __shmem_file_setup(shm_mnt, name, size, flags, S_PRIVATE);(mm/shmem.c, v6.12, lines 5244–5257).
Because all four share the same code and counters, every shmem page — whether a /tmp file or a SysV segment — is tallied under Shmem in /proc/meminfo and Shared in free(1) (tmpfs.rst, v6.12, lines 37–41).
Configuration and Mount Options (v6.12 / v6.18)
The line-by-line reference for the sizing and feature options, all from the v6.12 tmpfs.rst:
mount -t tmpfs -o size=10G,nr_inodes=10k,mode=700 tmpfs /mytmpfssize=10G— upper bound on allocated bytes. Default issize=50%of physical RAM when neithersizenornr_blocksis given (tmpfs.rst, v6.12, lines 78–93). Acceptsk/m/gsuffixes and a%suffix (percentage of RAM). Crucially, this is a limit, not a reservation — an emptysize=10Gtmpfs consumes essentially zero memory.nr_blocks=N— the same cap expressed inPAGE_SIZEblocks.nr_blocks=0(orsize=0) means unlimited — which the docs warn is “generally unwise, since it allows any user with write access to use up all the memory on the machine.”nr_inodes=N— maximum inode (file) count; default is half the number of physical RAM pages.nr_inodes=0means unlimited. Extended attributes also consume this inode budget, so heavy xattr use shows up indf -i.mode=/uid=/gid=— permissions and ownership of the root directory; ineffective on remount (change them withchmod/chowninstead).noswap— disables swap for this mount (since Linux 6.4); the page-cache pages then behave like ramfs and stay pinned (tmpfs.rst, v6.12, lines 103–109). Per the source, this is thesbinfo->noswapflag checked inshmem_writepage.huge=never|always|within_size|advise— Transparent Huge Page policy (requiresCONFIG_TRANSPARENT_HUGEPAGE); default ishuge=never.within_sizeonly uses a huge page when it fits fully within the file’si_size;adviseonly onmadvise(MADV_HUGEPAGE)(tmpfs.rst, v6.12, lines 111–127). The global knob/sys/kernel/mm/transparent_hugepage/shmem_enabledcan override all mounts (e.g.denyin an emergency,forcefor testing).mpol=default|prefer:N|bind:NodeList|interleave|local— NUMA allocation policy, adjustable on remount (requiresCONFIG_NUMA) (tmpfs.rst, v6.12, lines 160–208).inode32/inode64— width of generated inode numbers. On a 64-bit kernelCONFIG_TMPFS_INODE64sets the default;inode32exists to avoidEOVERFLOWin ancient 32-bit apps that can only handle 32-bit inode numbers.quota/usrquota/grpquota(+ hard-limit variants) — project/user/group quota accounting using hidden system quota files; none can be set or changed on remount, and tmpfs quotas do not translate uid/gid across user namespaces (tmpfs.rst, v6.12, lines 129–158).
Most options (size, nr_blocks, nr_inodes, huge, mpol) can be changed live with mount -o remount. A tmpfs can be grown or shrunk on remount, but not below its current usage.
Case-insensitive lookup (6.16+)
A post-6.12 addition: casefold and strict_encoding mount options enable per-directory case-insensitive lookup (UTF-8 only). The mount option does not make the whole filesystem case-insensitive — you still flip the +F attribute on each empty directory; new subdirectories inherit it; the mountpoint itself cannot be made case-insensitive (tmpfs.rst, v6.18, lines 244–265). This documentation is present in v6.15 but absent in v6.12 (verified by checking the tmpfs.rst blob at both tags — casefold first appears at v6.15), so tmpfs casefold is available in 6.18 LTS but not 6.12 LTS. The doc change is credited to André Almeida, 23 Aug 2024.
Failure Modes
The OOM-deadlock trap. The single most dangerous tmpfs footgun is oversizing. The v6.12 doc warns bluntly: “If you oversize your tmpfs instances the machine will deadlock since the OOM handler will not be able to free that memory” (tmpfs.rst, v6.12, lines 78–81). The OOM killer reclaims memory by killing processes, but a process’s death does not delete the files it left in /tmp; those tmpfs pages persist until something explicitly unlinks them or the mount is torn down. A size= larger than RAM+swap, or nr_blocks=0 (unlimited) on a multi-user box, lets a single user fill memory with files that the kernel cannot reclaim, hanging the system.
“Out of space” that is really out of memory. A write() to a tmpfs file can fail with ENOSPC not because a disk is full but because the size=/nr_blocks cap was hit, or because RAM+swap is exhausted. Note the kernel reports the block-accounting failure as -ENOSPC, not -ENOMEM (mm/shmem.c, v6.12, lines 199–201). Conversely, df on a tmpfs reports the limit, not how much RAM is actually free, so a tmpfs can show free space while the machine is already swapping hard.
Persistence surprise. Developers sometimes treat /tmp as durable. On modern systemd systems /tmp is frequently a tmpfs (and /run always is), so anything written there vanishes on reboot — and counts against RAM while it lives, unlike a /tmp on a disk filesystem.
Accounting confusion. Because shmem pages are tallied under Shmem/Shared and those counters also include SysV shm and anonymous shared mappings, you cannot read a tmpfs mount’s footprint from /proc/meminfo alone. The doc advises df(1)/du(1) for per-mount accounting (tmpfs.rst, v6.12, lines 37–41).
Alternatives and When to Choose Them
- ramfs — when you specifically must prevent swapping (e.g. holding key material that must never touch a swap device) and you can guarantee bounded usage. The cost is no size limit and no reclaim: an over-full ramfs has no graceful failure. tmpfs with
noswapis usually the safer way to get the same “never swap” behavior with an enforced size cap. - brd (
/dev/ram*) RAM block disk — when you genuinely need a block device in RAM (to test a block-based filesystem, or to back something that insists on a block device). It is a fixed-size lump of RAM with the block layer on top, cannot swap, and cannot be resized after init (tmpfs.rst, v6.12, lines 29–33). For an ordinary “fast scratch directory,” brd is strictly worse than tmpfs — it wastes RAM on the unused portion and routes I/O through the block layer for no reason. - A loop-mounted file on a disk filesystem — when you want the data to persist but still benefit from page-cache speed; you trade durability for the overhead of a real backing store.
hugetlbfs— a different in-memory filesystem specifically for explicitly-reserved huge pages (MAP_HUGETLB); use it when you need guaranteed huge-page-backed memory rather than tmpfs’s opportunistic THP.
For the common cases — /tmp, build scratch space, /dev/shm, container emptyDir “Memory” volumes, secret material that should not hit disk — tmpfs (optionally with noswap and a sane size=) is the right default.
Production Notes
- systemd and
/tmp. Recent systemd defaults toward a tmpfs/tmp(viatmp.mount);/runis always tmpfs. This is why “my/tmpfile disappeared after reboot” and “my big/tmpextract ran the box out of memory” are recurring production incidents. The fix is an explicitsize=and not treating/tmpas durable. - Kubernetes
emptyDir: medium: Memory. A Memory-backedemptyDiris a tmpfs mount; its bytes count against the pod’s memory limit and can trigger the pod’s cgroup OOM killer, not a disk-full error. Sizing it (sizeLimit) matters for the same OOM-deadlock reason above. (Orchestration detail owned by the Kubernetes notes; the kernel mechanism is this tmpfs.) - Databases and
/dev/shm. PostgreSQL’s dynamic shared memory and many other servers use POSIX shm (/dev/shm); container runtimes default/dev/shmto a small 64 MiB tmpfs, which is a classic cause of “could not resize shared memory segment” errors — the fix is--shm-size, i.e. enlarging that tmpfs. - Huge pages for big shared buffers.
huge=within_sizeon a tmpfs mount that holds large shared mappings can cut TLB pressure measurably, but watch for memory waste when files are small relative to the 2 MiB huge-page granularity — hencewithin_size/adviserather thanalways.
See Also
- The Page Cache and address_space — the
address_space/folio machinery tmpfs files live in - Folios and the Page Cache — multi-page folios, which tmpfs uses for huge-page support
- The Swap Cache — where
shmem_writepagedeposits a page on its way to swap - Linux Swap Subsystem — the swap devices and entries tmpfs pages are written to and read back from
- The Virtual File System Layer — the VFS dispatch that routes file ops into
mm/shmem.c - overlayfs Union Filesystem — another special filesystem; often layered with tmpfs as the writable upper
- FUSE Filesystems in Userspace — the other “no block device” filesystem in this section
- MOC: Linux Filesystems and VFS MOC (§7 Special and Pseudo Filesystems)