Skip to content

Filesystems & Mounting

In Linux (and all UNIX-like systems), it is often said: “Everything is a file”. Whether you are dealing with data files, directories, network sockets, hardware devices, or kernel data structures - you interact with them through the same I/O operations (open, read, write, close).

This unifying abstraction simplifies programming enormously: a program that reads from stdin works equally well reading from a keyboard, a file, a network socket, or a pipe. The filesystem provides the consistent interface.


VFS architecture

The Virtual Filesystem (VFS) is a kernel abstraction layer that sits between applications and the actual filesystem implementations. When an application calls open(), read(), or write(), it talks to the VFS - not directly to ext4, XFS, or any other specific filesystem.

The VFS translates generic I/O calls into filesystem-specific code. This means:

  • An application doesn’t need to know if a file is on ext4, NFS, tmpfs, or a USB drive formatted as VFAT
  • Network filesystems (NFS, CIFS) are handled transparently - they look like local directories
  • Linux can support more filesystem varieties than any other OS because adding new filesystem support only requires implementing the VFS interface

VFS layers


FilesystemMax FileMax VolumeJournalingNotes
ext416 TB1 EBYesDefault on most distros; backward compatible with ext3/ext2
XFS8 EB8 EBYesHigh-performance; great for large files; default on RHEL
Btrfs16 EB16 EBYes (CoW)Copy-on-write; built-in snapshots and RAID
ext32 TB4 TBYesPredecessor to ext4
ext22 TB4 TBNoNo journal; long fsck on crash
FilesystemOriginUse case
NTFSWindowsRequired for Windows dual-boot
FAT/VFAT/exFATWindows/DOSUSB drives, memory cards; universal compatibility
HFS+macOSMac drives
JFSIBMOlder; still used
FilesystemMount PointPurpose
tmpfsAnywhereRAM-backed; cleared on reboot; used for /tmp, /run
proc/procKernel data structures and tunables
sysfs/sysDevice tree, hardware info
devtmpfs/devDevice nodes managed by kernel+udev
debugfs/sys/kernel/debugKernel debugging interfaces
squashfsAnywhereRead-only compressed; Live ISOs, snap packages

Journaling filesystems (ext4, XFS, Btrfs, JFS) recover from crashes or ungraceful shutdowns with little or no corruption:

  • Operations are grouped into transactions that must complete atomically - all or nothing
  • A journal (log) records transactions before committing them to disk
  • On crash, only the last incomplete transaction needs examining and rolling back
  • Without journaling (ext2): fsck must scan the entire filesystem - slow on large drives

Featureext2ext3ext4
Max file size2 TB2 TB16 TB
Max volume4 TB4 TB1 EB
JournalingNoYesYes + checksums
Subdirectory limit32,00032,000Unlimited
Timestampssecondsecondnanosecond
ExtentsNoNoYes
Pre-allocationNoNoYes

The ext4 filesystem is divided into block groups - contiguous sets of blocks. Each block group contains:

  • A superblock (copy of global filesystem metadata)
  • Block and inode bitmaps
  • Inode table
  • Data blocks

The superblock (stored redundantly across multiple block groups) contains:

  • Block size (512B, 1K, 2K, 4K, 8K - set at creation time; default 4 KB)
  • Total block and inode counts
  • Free block and inode counts
  • Mount count and maximum mount count
  • Last check time and check interval
  • OS ID

ext4 block structure

The block allocator tries to keep each file’s blocks within the same block group to minimize seek times. The default 4 KB block size creates 128 MB block groups.

Terminal window
# Inspect filesystem metadata
sudo dumpe2fs /dev/sda1 # full superblock + block group info
sudo tune2fs -l /dev/sda1 # summary (same as dumpe2fs header)
# Adjust filesystem parameters
sudo tune2fs -c 25 /dev/sda1 # check every 25 mounts
sudo tune2fs -i 30d /dev/sda1 # check every 30 days
sudo tune2fs -L "mydata" /dev/sda1 # set volume label
sudo tune2fs -U random /dev/sda1 # generate new UUID
# Create and manage
sudo mkfs.ext4 /dev/sdb1 # format
sudo mkfs.ext4 -L "data" /dev/sdb1 # format with label
sudo e2fsck -f /dev/sdb1 # check (must be unmounted)
sudo resize2fs /dev/sdb1 50G # resize (after lvextend, etc.)

tune2fs output


Each filesystem lives in a partition (or logical volume). Partitions isolate different types of data - if /var fills up, the root filesystem keeps working.

Before using a filesystem, you must mount it - attach it at a directory in the tree called the mount point.

Mount points

Terminal window
# Basic mount
sudo mount /dev/sdb1 /mnt/data # mount by device node
sudo mount UUID="abc123..." /mnt/data # mount by UUID (preferred)
sudo mount LABEL="mydata" /mnt/data # mount by label
# With options
sudo mount -o ro /dev/sdb1 /mnt/data # read-only mount
sudo mount -o remount,rw /mnt/data # remount as read-write
sudo mount -t ext4 /dev/sdb1 /mnt/data # explicit filesystem type
# Unmount
sudo umount /mnt/data # by mount point (note: umount, not unmount!)
sudo umount /dev/sdb1 # by device
# View current mounts
mount # all mounted filesystems
mount | grep sdb # filter
df -Th # with filesystem type and space usage
cat /proc/mounts # kernel's current mount table (most accurate)
findmnt # tree view of mount points
findmnt /mnt/data # info about specific mount point

/etc/fstab defines filesystems that should be mounted automatically at boot.

# <device> <mountpoint> <fstype> <options> <dump> <fsck>
UUID=abc123-... / ext4 defaults 0 1
UUID=def456-... /boot ext4 defaults 0 2
UUID=789abc-... /home ext4 defaults 0 2
UUID=swap-uuid none swap sw 0 0
tmpfs /tmp tmpfs defaults,nosuid 0 0
FieldMeaning
DeviceDevice node, UUID, LABEL, or PARTUUID
Mount pointWhere to attach it (none for swap)
Filesystem typeext4, xfs, vfat, swap, nfs, etc.
OptionsComma-separated; defaults = rw,suid,dev,exec,auto,nouser,async
Dump0 = no dump backup; 1 = include in dumps
fsck order0 = skip; 1 = check first (root); 2 = check after root

Common mount options:

OptionEffect
ro / rwRead-only / read-write
noexecDisallow executing binaries (good for /tmp)
nosuidIgnore setuid/setgid bits
nodevDisallow device files
noatimeDon’t update access time on reads (performance)
nofailDon’t fail boot if device is missing (good for removable/NFS)
userAllow non-root users to mount
syncSynchronous writes (safe but slow)
Terminal window
# Test fstab without rebooting
sudo mount -a # mount everything in fstab not yet mounted
sudo mount -av # verbose - see what happened

Inodes

An inode (index node) is a data structure on disk that describes a file. Every file has exactly one inode. The inode stores everything about the file except its name.

Each inode stores:

  • Permissions (read/write/execute for owner/group/other)
  • Owner (UID) and group (GID)
  • Size in bytes
  • Link count (number of directory entries pointing to this inode)
  • Timestamps (nanosecond precision in ext4):
    • atime - last access time (read)
    • mtime - last modification time (file content changed)
    • ctime - last change time (inode changed: permissions, owner, hard links, rename)
  • Block pointers - locations of the actual data on disk
Terminal window
ls -i filename # show inode number
stat filename # show all inode metadata (size, all timestamps, permissions)
df -i / # show inode usage for a filesystem

A directory entry is just a name -> inode mapping. This is the foundation of links.

Links diagram

A hard link is a second directory entry pointing to the same inode. Both names refer to the exact same data.

Terminal window
ln file1 file2 # create hard link: file2 points to same inode as file1
ls -li file1 file2 # both show the same inode number; link count = 2

Properties:

  • Same inode number, same data, same permissions
  • Deleting one name leaves the file intact (data deleted only when link count reaches 0)
  • Cannot cross filesystem boundaries (inode numbers are filesystem-local)
  • Cannot hard-link directories (would create cycles the kernel can’t handle)
  • No concept of “original vs link” - they are equal

A symlink is a file whose content is a path to another file or directory.

Terminal window
ln -s /path/to/original symlink # create symlink
ls -la symlink # shows: symlink -> /path/to/original
readlink symlink # print what the symlink points to
readlink -f symlink # print the ultimate resolved path

Properties:

  • Has its own inode with its own permissions (though actual access uses target’s permissions)
  • Can cross filesystem boundaries
  • Can point to directories
  • If the target is deleted or moved, the symlink becomes dangling (broken)
  • Deleting the symlink does not affect the target
FeatureHard LinkSymbolic Link
InodeSame as targetOwn inode
Cross-filesystemNoYes
Can link directoriesNoYes
Survives target deletionYesNo (becomes dangling)
Space overheadNone (just a directory entry)Small (path string)
Detectablels -i (same inode number)ls -la (shows arrow)

Some filesystem types have no counterpart on disk - they exist purely in kernel memory and are mounted for access to kernel facilities:

FilesystemMount PointPurpose
rootfsNoneEmpty root during kernel init
tmpfsAnywhereRAM disk with swap backing; re-sizable
proc/procKernel structures and process info
sysfs/sysDevice tree and hardware info
devtmpfs/devDevice nodes
devpts/dev/ptsUnix98 pseudo-terminals
hugetlbfsAnywhereLarge memory pages (2 MB / 4 MB)
debugfs/sys/kernel/debugKernel debugging access
sockfsNoneBSD sockets (no user-visible mount point)
pipefsNonePipes

NFS

NFS allows mounting remote directories as if they were local. It uses a client-server architecture:

  • Server exports directories (defined in /etc/exports)
  • Client mounts those exports, accessing them via the network
  • VFS makes this transparent to applications

Server setup:

Terminal window
# Install NFS server
sudo dnf install nfs-utils # RHEL/Fedora
sudo apt install nfs-kernel-server # Debian/Ubuntu
# Define exports in /etc/exports
sudo vim /etc/exports
# /projects *.example.com(rw,sync,no_root_squash)
# /data 192.168.1.0/24(ro)
# Apply exports without restarting
sudo exportfs -av # apply and show exports
sudo exportfs -ra # re-read /etc/exports
# Start and enable
sudo systemctl enable --now nfs-server # RHEL/Fedora
sudo systemctl enable --now nfs-kernel-server # Debian/Ubuntu

NFS export options:

OptionMeaning
rwRead-write
roRead-only
syncWrite to disk before acknowledging (safe, slower)
asyncAcknowledge before disk write (fast, risk of data loss)
no_root_squashRoot on client maps to root on server (risky)
root_squash (default)Root on client maps to nobody on server

Client setup:

Terminal window
# One-time mount
sudo mount server:/projects /mnt/nfs/projects
# Persistent mount in /etc/fstab
server:/projects /mnt/nfs/projects nfs defaults,nofail 0 0
# Check NFS server's exports
showmount -e server

Terminal window
diff file1 file2 # show differences
diff -c file1 file2 # context diff (3 lines around changes)
diff -u file1 file2 # unified diff (standard patch format)
diff -r dir1/ dir2/ # recursive directory comparison
diff -i file1 file2 # ignore case
diff -w file1 file2 # ignore whitespace
diff -q file1 file2 # only report if different (no detail)
cmp file1 file2 # byte-by-byte comparison (good for binaries)
Terminal window
diff3 MY-FILE COMMON-FILE YOUR-FILE

Useful when you and a colleague both edited the same original file. diff3 shows what each changed relative to the shared baseline.

Patches are diff files distributed to update software:

Terminal window
# Generate a patch
diff -Nur original/ modified/ > changes.patch
# Apply a patch
patch -p1 < changes.patch # preferred: strip leading path component
patch original.txt changes.patch # apply to specific file
patch --dry-run -p1 < changes.patch # test without modifying files
patch -R -p1 < changes.patch # reverse: undo a patch

-p1 strips the first path component from filenames in the patch, which is standard for patches generated against a source tree.

Linux file types are determined by content, not extension. A file named script.txt could be an executable, and a file named data.exe could be a text file.

Terminal window
file /usr/bin/ls # ELF 64-bit LSB executable
file /etc/resolv.conf # ASCII text
file image.png # PNG image
file archive.tar.gz # gzip compressed data
file /dev/sda # block special
file unknown_file # detect from magic bytes

The file command reads the file’s magic bytes (first few bytes) to identify it. This is the authoritative way to identify file types in Linux.