Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mounting

Introduction

Mounting is the process of attaching a filesystem to a specific point in the Linux directory tree. The mount(2) system call is the fundamental operation that makes a filesystem’s contents accessible at a path. Modern Linux mounting is far more complex than the simple “attach device to directory” model: it supports mount namespaces, bind mounts, propagation types, overlay mounts, and a rich security model.

The mount system call has evolved significantly. The legacy mount(2) is a single syscall with flags and data arguments. Linux 5.2+ introduced a new mount API (fsopen, fsconfig, fsmount, move_mount) that splits the operation into discrete steps, enabling better error handling and more complex configurations.

The mount(2) System Call

Legacy API

#include <sys/mount.h>

int mount(const char *source, const char *target,
          const char *filesystemtype, unsigned long mountflags,
          const void *data);

Parameters:

  • source — Device path, remote path, or NULL (for virtual filesystems)
  • target — Directory where the filesystem will be mounted
  • filesystemtype — Filesystem type string (e.g., “ext4”, “tmpfs”, “nfs”)
  • mountflags — Bitmask of mount flags
  • data — Filesystem-specific options string

Mount Flags

#define MS_RDONLY        1      /* Mount read-only */
#define MS_NOSUID        2      /* Ignore suid/sgid bits */
#define MS_NODEV         4      /* Disallow access to device files */
#define MS_NOEXEC        8      /* Disallow program execution */
#define MS_SYNCHRONOUS   16     /* Writes are synchronous */
#define MS_REMOUNT       32     /* Remount existing mount */
#define MS_MANDLOCK      64     /* Enable mandatory locking */
#define MS_DIRSYNC       128    /* Directory modifications synchronous */
#define MS_NOATIME       1024   /* Don't update access times */
#define MS_NODIRATIME    2048   /* Don't update directory access times */
#define MS_BIND          4096   /* Bind mount */
#define MS_MOVE          8192   /* Move mount to new location */
#define MS_REC           16384  /* Recursive (for bind/rbind) */
#define MS_SILENT        32768  /* Suppress kernel messages */
#define MS_POSIXACL      (1<<16) /* POSIX ACLs */
#define MS_UNBINDABLE    (1<<17) /* Make unbindable */
#define MS_PRIVATE       (1<<18) /* Make private */
#define MS_SLAVE         (1<<19) /* Make slave */
#define MS_SHARED        (1<<20) /* Make shared */
#define MS_RELATIME      (1<<21) /* Update atime if older than mtime */
#define MS_KERNMOUNT     (1<<22) /* Kernel-internal mount */
#define MS_I_VERSION     (1<<23) /* Update inode i_version */
#define MS_STRICTATIME   (1<<24) /* Always update atime */
#define MS_LAZYTIME      (1<<25) /* Lazy atime updates */

Example Mount Calls

/* Mount ext4 */
mount("/dev/sda1", "/mnt/data", "ext4", MS_NOATIME, "errors=remount-ro");

/* Mount tmpfs */
mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV, "size=2G,mode=1777");

/* Bind mount */
mount("/home/user/docs", "/mnt/docs", NULL, MS_BIND, NULL);

/* Recursive bind mount */
mount("/home/user", "/mnt/user", NULL, MS_BIND | MS_REC, NULL);

/* Remount read-only */
mount(NULL, "/mnt/data", NULL, MS_REMOUNT | MS_RDONLY, NULL);

/* NFS mount */
mount("server:/share", "/mnt/nfs", "nfs", 0, "vers=4.2,hard,rsize=1048576");

The New Mount API (Linux 5.2+)

The new API splits mounting into discrete steps:

#include <sys/syscall.h>
#include <linux/mount.h>

/* Step 1: Create a filesystem context */
int fs_fd = syscall(__NR_fsopen, "ext4", FSOPEN_CLOEXEC);

/* Step 2: Configure the filesystem */
syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING, "source", "/dev/sda1", 0);
syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING, "errors", "remount-ro", 0);
syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG, "noatime", NULL, 0);
syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);

/* Step 3: Create a mount object */
int mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, MS_NOATIME);

/* Step 4: Attach to the directory tree */
syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, "/mnt/data",
        MOVE_MOUNT_F_EMPTY_PATH);

New API Advantages

graph LR
    subgraph "Legacy mount(2)"
        L1["mount(source, target, type, flags, data)"]
        L2["All-or-nothing: success or error"]
    end
    subgraph "New Mount API"
        N1["fsopen(type) → fd"]
        N2["fsconfig(fd, key, value)"]
        N3["fsconfig(fd, key, value)"]
        N4["fsconfig(fd, CMD_CREATE)"]
        N5["fsmount(fd) → mnt_fd"]
        N6["move_mount(mnt_fd, target)"]
        N1 --> N2 --> N3 --> N4 --> N5 --> N6
    end

Benefits:

  • File descriptor based: Mount objects are FDs, can be passed between processes
  • Better error reporting: Each step can fail independently
  • Atomic configuration: Options are accumulated before creation
  • Superseded source: Can change source without re-mounting
  • Open-tree: open_tree() to manipulate existing mounts

Bind Mounts

Bind mounts make a directory or file visible at another location:

# Basic bind mount
mount --bind /source/dir /target/dir

# Bind mount a single file
mount --bind /etc/hostname /mnt/hostname

# Recursive bind mount (includes sub-mounts)
mount --rbind /source/dir /target/dir

# Read-only bind mount
mount --bind /source/dir /target/dir
mount -o remount,ro,bind /target/dir

# Equivalent to:
mount --bind --read-only /source/dir /target/dir

Bind Mount Use Cases

graph TB
    subgraph "Container / Chroot"
        C1["/container"]
        C2["/container/bin"]
        C3["/container/lib"]
        C4["/container/etc"]
    end
    subgraph "Host System"
        H1["/usr/bin"]
        H2["/usr/lib"]
        H3["/etc"]
    end
    H1 -->|bind mount| C2
    H2 -->|bind mount| C3
    H3 -->|bind mount| C4
# Make /usr available in a chroot
mount --bind /usr /chroot/usr

# Overlay config files in a container
mount --bind /host/config/app.conf /container/etc/app.conf

# Share a directory between containers
mount --bind /shared/data /container1/data
mount --bind /shared/data /container2/data

Mount Namespaces

Mount namespaces provide isolated views of the filesystem hierarchy. They are the foundation of containers.

Creating a Mount Namespace

# unshare: create a new mount namespace
$ sudo unshare --mount /bin/bash

# In the new namespace, mount/unmount without affecting the host
$ mount -t tmpfs tmpfs /mnt
$ ls /mnt
# Only visible in this namespace

# In another terminal (original namespace):
$ ls /mnt
# Empty — the tmpfs mount is not visible here

Mount Namespace Diagram

graph TB
    subgraph "Initial Namespace (PID 1)"
        M1["/ (rootfs)"]
        M2["/dev"]
        M3["/proc"]
        M4["/sys"]
        M5["/mnt/data"]
    end
    subgraph "New Namespace (container)"
        N1["/ (rootfs -- shared initially)"]
        N2["/dev -- devtmpfs"]
        N3["/proc -- procfs"]
        N4["/sys -- sysfs"]
        N5["/mnt/data -- private"]
        N6["/app -- overlayfs"]
    end
    M1 -.->|shared| N1
    N5 -.->|propagation: private| N5

Mount Propagation

Mount propagation controls how mount and unmount events are shared between namespaces:

graph TD
    subgraph "Propagation Types"
        SHARED["Shared<br>Events propagate bidirectionally"]
        PRIVATE["Private<br>No propagation"]
        SLAVE["Slave<br>One-way: parent → child"]
        UNBINDABLE["Unbindable<br>Cannot be bind-mounted"]
    end
PropagationBehavior
sharedMount/unmount events propagate to peer mounts and vice versa
privateNo events propagate in either direction
slaveReceives events from master, but doesn’t propagate back
unbindableCannot be the source of a bind mount
# View propagation types
$ findmnt -o TARGET,PROPAGATION
TARGET                        PROPAGATION
/                             shared
├─/proc                       shared
├─/dev                        shared
├─/sys                        shared
├─/run                        shared
└─/mnt/data                   private

# Change propagation type
mount --make-private /mnt/data
mount --make-shared /mnt/data
mount --make-slave /mnt/data
mount --make-unbindable /mnt/data

# Recursive propagation change
mount --make-rshared /
mount --make-rprivate /mnt

Propagation in Container Runtimes

# Docker/Podman typically:
# 1. Create a new mount namespace
# 2. Set root to private propagation
# 3. Pivot root to new rootfs
# 4. Mount /proc, /sys, /dev as shared (for device hotplug)

# Example: container mount setup
unshare --mount --propagation private -- /bin/bash
mount --make-rprivate /
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
pivot_root /new_root /new_root/old_root
umount -l /old_root

Remounting

# Remount read-only
mount -o remount,ro /mnt/data

# Remount with different options
mount -o remount,noatime,nodiratime /mnt/data

# Remount a bind mount
mount -o remount,bind,ro /mnt/docs

# Cannot change certain things via remount:
# - filesystem type
# - source device (use bind mount instead)
# - Some flags require full unmount+mount

umount

# Unmount a filesystem
umount /mnt/data

# Lazy unmount (detach immediately, cleanup later)
umount -l /mnt/data

# Force unmount (even if busy — for NFS)
umount -f /mnt/nfs

# Unmount by device
umount /dev/sda1

# Unmount all filesystems of a type
umount -t nfs

# Recursive unmount (all mounts under a path)
umount -R /mnt/namespace

What Happens During umount

sequenceDiagram
    participant U as User
    participant VFS as VFS
    participant FS as Filesystem
    participant SB as Superblock

    U->>VFS: umount("/mnt/data")
    VFS->>VFS: Check for busy inodes/dentries
    alt Busy
        VFS-->>U: EBUSY (open files or cwd)
    else Not busy
        VFS->>FS: sync_fs(sb, 1)
        VFS->>SB: Decrement s_active
        SB->>FS: put_super(sb)
        FS->>FS: Release resources
        VFS->>VFS: Remove mount from tree
        VFS-->>U: Success
    end

/proc and /sys Mount Information

# View all mounts
$ cat /proc/mounts
# or
$ cat /proc/self/mountinfo

# mountinfo format (11 fields + optional):
# 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext4 /dev/root rw,errors=continue

# Field meanings:
# 1: mount ID
# 2: parent mount ID
# 3: major:minor device numbers
# 4: root (path of mount within the filesystem)
# 5: mount point
# 6: mount options
# 7: optional fields (propagation, etc.)
# 8: separator (-)
# 9: filesystem type
# 10: source device
# 11: superblock options

# Find mount for a specific path
$ findmnt /mnt/data
TARGET    SOURCE    FSTYPE OPTIONS
/mnt/data /dev/sda1 ext4   rw,noatime,errors=remount-ro

# Find mount by device
$ findmnt -S /dev/sda1

Implementation Details

Key Source Files

  • fs/namespace.c — Mount system calls and mount tree management (~4500 lines)
  • fs/seq_file.c/proc/mounts output
  • include/linux/mount.h — Mount structure definitions
  • include/uapi/linux/mount.h — User-visible mount flags

Mount Structure

/* Simplified from include/linux/mount.h */
struct vfsmount {
    struct dentry *mnt_root;        /* Root dentry of this mount */
    struct super_block *mnt_sb;     /* Superblock for this mount */
    int mnt_flags;                  /* Mount flags */
};

struct mount {
    struct hlist_node mnt_hash;     /* Hash table entry */
    struct mount *mnt_parent;       /* Parent mount */
    struct dentry *mnt_mountpoint;  /* Dentry where mounted */
    struct vfsmount mnt;            /* VFS mount structure */
    struct list_head mnt_mounts;    /* Child mounts */
    struct list_head mnt_child;     /* Link in parent's mnt_mounts */
    struct list_head mnt_instance;  /* Link in sb->s_mounts */
    const char *mnt_devname;        /* Device name */
    struct list_head mnt_list;      /* Global list of mounts */
    struct mnt_namespace *mnt_ns;   /* Owning namespace */
    /* ... */
};

References

Further Reading


Virtual Filesystem Mounts

Linux has several in-memory (virtual) filesystems that don’t correspond to any physical device:

tmpfs

tmpfs stores files entirely in RAM (and swap). Commonly used for /tmp, /run, and /dev/shm:

# Mount tmpfs
mount -t tmpfs -o size=2G,mode=1777 tmpfs /tmp

# tmpfs options
# size=    — Maximum size (default: 50% of RAM)
# nr_inodes= — Maximum number of inodes
# mode=    — Directory permissions
# uid=,gid= — Owner
# huge=    — Enable huge pages (always/within_size/never)

# Check current tmpfs usage
df -h /tmp /run /dev/shm

# View tmpfs mount details
mount | grep tmpfs
cat /proc/mounts | grep tmpfs

proc

procfs exposes kernel and process information:

mount -t proc proc /proc

# Key procfs entries
/proc/cpuinfo          # CPU information
/proc/meminfo          # Memory statistics
/proc/<pid>/           # Per-process information
/proc/<pid>/maps       # Memory mappings
/proc/<pid>/fd/        # Open file descriptors
/proc/<pid>/status     # Process status
/proc/sys/             # Kernel tunables (sysctl)

sysfs

sysfs exposes kernel device model information:

mount -t sysfs sysfs /sys

# Key sysfs entries
/sys/block/            # Block devices
/sys/class/            # Device classes
/sys/devices/          # Device tree
/sys/module/           # Loaded kernel modules
/sys/firmware/          # Firmware interfaces

devtmpfs

devtmpfs provides automatic device node management:

mount -t devtmpfs devtmpfs /dev

# Kernel automatically creates/removes /dev nodes
# when devices are added/removed

OverlayFS Mounts

OverlayFS combines multiple directory trees into one. It’s the foundation of container image layering:

# OverlayFS mount
mount -t overlay overlay \
    -o lowerdir=/lower,upperdir=/upper,workdir=/work \
    /merged

# Components:
# lowerdir  — Read-only base layer (can be multiple, comma-separated)
# upperdir  — Read-write layer (changes go here)
# workdir   — Working directory (must be on same filesystem as upperdir)
# merged    — Combined view presented to users

OverlayFS in Containers

graph TD
    subgraph "Container Image Layers (lowerdir, read-only)"
        L1["Base image (e.g., ubuntu:22.04)"]
        L2["Layer 2 (apt-get install)"]
        L3["Layer 3 (app code)"]
    end
    subgraph "Container Layer (upperdir, read-write)"
        U1["Runtime changes"]
    end
    subgraph "Merged View"
        M1["Container sees one filesystem"]
    end
    L1 --> L2 --> L3 --> M1
    U1 --> M1
# Docker overlay2 driver example
# Lower layers: image layers (read-only)
# Upper layer: container writable layer
# Work dir: overlayfs internal state

mount -t overlay overlay \
    -o lowerdir=/var/lib/docker/overlay2/l/ABC:/var/lib/docker/overlay2/l/DEF,\
upperdir=/var/lib/docker/overlay2/XYZ/diff,\
workdir=/var/lib/docker/overlay2/XYZ/work \
    /var/lib/docker/overlay2/XYZ/merged

Multiple Lower Layers

# Stack multiple read-only layers
mount -t overlay overlay \
    -o lowerdir=/layer3:/layer2:/layer1,upperdir=/upper,workdir=/work \
    /merged

# Files in /layer3 take precedence over /layer2, which takes
# precedence over /layer1. The upperdir is writable and takes
# highest precedence.

/etc/fstab

The fstab file defines filesystems to mount at boot:

# /etc/fstab format:
# <device>  <mount>  <type>  <options>  <dump>  <fsck>

# Examples:
# Device with UUID (preferred)
UUID=12345678-1234-1234-1234-123456789abc  /  ext4  errors=remount-ro  0  1

# tmpfs
tmpfs  /tmp  tmpfs  defaults,noatime,nosuid,nodev,size=2G  0  0

# NFS
server:/share  /mnt/nfs  nfs4  defaults,soft,timeo=10  0  0

# Bind mount (fstab)
/data/shared  /srv/shared  none  bind,ro  0  0

# Swap
UUID=...  none  swap  sw  0  0

# Common mount options:
# defaults   — rw, suid, dev, exec, auto, nouser, async
# noatime    — Don't update access times (performance)
# nosuid     — Ignore suid/sgid bits (security)
# nodev      — Don't interpret device files (security)
# noexec     — Don't allow execution (security)
# discard    — Enable TRIM for SSDs
# x-systemd.automount — Automount on first access
# _netdev    — Wait for network before mounting

Verifying fstab

# Test fstab without rebooting
mount -a

# Check for fstab errors
findmnt --verify

# Remount all fstab entries
systemctl daemon-reload
systemctl restart local-fs.target

systemd Automounting

systemd can automount filesystems on first access:

# /etc/systemd/system/data.mount
[Unit]
Description=Data Partition

[Mount]
What=/dev/disk/by-uuid/12345678-1234-1234-1234-123456789abc
Where=/data
Type=ext4
Options=defaults,noatime

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/data.automount
[Unit]
Description=Automount Data Partition

[Automount]
Where=/data
TimeoutIdleSec=300

[Install]
WantedBy=multi-user.target
# Enable automount
systemctl enable data.automount
systemctl start data.automount

# The filesystem mounts on first access to /data
# and unmounts after 300 seconds of inactivity

Mount Troubleshooting

Common Errors

# EBUSY: device is busy
$ umount /mnt/data
umount: /mnt/data: target is busy.

# Find what's using the mount
fuser -vm /mnt/data
# USER        PID ACCESS COMMAND
# root        1234 ..c.. bash
# root        5678 ...ce vim

lsof +D /mnt/data
# COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
# bash     1234 root  cwd    DIR  8,1     4096    2 /mnt/data

# Solutions:
# 1. Kill processes using the mount
fuser -km /mnt/data

# 2. Lazy unmount (detach now, cleanup later)
umount -l /mnt/data

# 3. Force unmount (for NFS)
umount -f /mnt/data

# ENOENT: mount point doesn't exist
mkdir -p /mnt/data

# EACCES: permission denied
# Check /etc/fstab options, or use sudo

# Wrong filesystem type
# Check: blkid /dev/sda1
# Verify: file -sL /dev/sda1

Mount Debugging

# View all mounts with details
findmnt
findmnt -t ext4,xfs

# View mount options
findmnt -o TARGET,SOURCE,OPTIONS /data

# View /proc/self/mountinfo (detailed)
cat /proc/self/mountinfo

# Trace mount operations
strace mount /dev/sda1 /mnt/data 2>&1 | grep mount

# Debug fstab issues
mount -fav  # Verbose, don't actually mount

Container Mount Setup

A complete container filesystem setup sequence:

# 1. Create new mount namespace
unshare --mount

# 2. Set all mounts to private propagation
mount --make-rprivate /

# 3. Mount new rootfs (overlayfs)
mkdir -p /tmp/overlay/{upper,work,merged}
mount -t overlay overlay \
    -o lowerdir=/var/lib/images/base,upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work \
    /tmp/overlay/merged

# 4. Mount virtual filesystems
mount -t proc proc /tmp/overlay/merged/proc
mount -t sysfs sysfs /tmp/overlay/merged/sys
mount -t devtmpfs devtmpfs /tmp/overlay/merged/dev
mount -t tmpfs tmpfs /tmp/overlay/merged/run

# 5. Pivot root
cd /tmp/overlay/merged
mkdir -p .old_root
pivot_root . .old_root

# 6. Unmount old root
umount -l /.old_root
rmdir /.old_root

# 7. Now running in isolated container filesystem
exec /bin/sh
  • superblock — Each mount has a superblock
  • tmpfs — Commonly mounted virtual filesystem
  • overlayfs — Uses mount namespaces for container layering
  • fuse — FUSE mounts via fusermount
  • Mount namespaces — Process-level mount isolation
  • Device Mapper — Virtual block devices