Skip to content

Linux Virtual File Systems — VFS


cat works identically whether you’re reading a file on an ext4 partition, a file on a USB drive formatted as FAT32, or a file under /proc that has no disk storage behind it at all — no special flags, no different syntax, nothing to indicate the underlying reality is completely different in each case. This isn’t a coincidence, and it isn’t cat being especially clever either. It’s the result of a single abstraction layer inside the kernel that every filesystem, real or virtual, is built against: the VFS.

The Problem The VFS Solves

Every program that opens, reads, writes, or lists files — cat, ls, a text editor, a database, literally everything — expects to work through the same small set of operations regardless of what kind of storage it’s actually touching. Without something standardizing this, every single program would need its own separate code path for “how do I read a file on ext4” versus “how do I read a file on xfs” versus “how do I read a file over a network filesystem,” and adding support for a new filesystem type would mean updating every single program that touches files, one at a time.

The VFS (Virtual File System) — sometimes called the “virtual filesystem switch” — is the kernel’s answer: a common interface that every filesystem driver implements against, and the only interface userspace programs ever actually talk to. A program calls the same open(), read(), write(), close() system calls no matter what’s underneath, and the VFS is responsible for routing that call to whichever actual filesystem driver — ext4, xfs, procfs, tmpfs, an NFS network share — is responsible for handling it.

    flowchart TB
    A["Programs (cat, ls, your text editor)"] --> B["System calls:<br/>open, read, write, close"]
    B --> C["VFS<br/>(common interface)"]
    C --> D["ext4 driver"]
    C --> E["xfs driver"]
    C --> F["procfs driver"]
    C --> G["sysfs driver"]
    C --> H["tmpfs driver"]
    D --> I["Real block device"]
    E --> I
    F --> J["Live kernel data,<br/>generated on demand"]
    G --> J
    H --> K["RAM"]
  

The Four Core Objects

The VFS defines a small set of standard object types that every filesystem driver must be able to produce, regardless of what’s actually happening underneath.

Superblock — represents one mounted filesystem instance as a whole: its type, its overall state, and top-level metadata about that specific mount. Every mounted filesystem — a real disk partition, a virtual one, a network share — has exactly one superblock while it’s mounted.

Inode — represents a single file’s metadata: permissions, ownership, size, timestamps, and where its actual data lives. Every file has exactly one inode for as long as it exists, no matter how many times it’s opened.

Dentry (directory entry) — represents the mapping between a name and an inode, and is what makes path lookups fast. Resolving /home/you/notes.txt means walking through a chain of dentries — one for home, one for you, one for notes.txt — each pointing to the next, with the kernel caching these aggressively since the same paths tend to get looked up repeatedly.

File object — represents one specific open instance of a file, tracked per-process. This is distinct from the inode: if two different processes have the same file open simultaneously, each gets its own file object (tracking, among other things, its own current read/write position within that file), while both point back to the exact same single inode.

Why This Is What Makes /proc Possible At All

This is the payoff for understanding the object model above: a filesystem driver only needs to produce these four kinds of objects when asked — it’s under no obligation to actually be backed by a disk at all. procfs, which the next chapter covers in depth, implements the VFS interface completely, but instead of reading blocks off a physical disk to answer “what’s in this file,” it generates the content live, on the spot, from the kernel’s own in-memory data structures, the instant something reads it.

This is exactly why a file like /proc/cpuinfo behaves like a normal file to every tool you already know — cat, less, grep — while genuinely containing no stored data anywhere. Reading it triggers kernel code that formats current CPU information into text, on demand, and hands that text back through the exact same read() path a real file would use. The VFS doesn’t know or care that this file’s “data” didn’t come from a disk; from the VFS’s perspective, procfs has satisfied the same interface ext4 would have.

Seeing This Directly

The kernel maintains a list of every filesystem type it currently supports — itself readable, fittingly, through a virtual file:

cat /proc/filesystems
nodev	sysfs
nodev	tmpfs
nodev	proc
nodev	cgroup
	ext4
	vfat
	xfs

The nodev prefix marks filesystem types that don’t require an actual block device to back them — precisely the virtual filesystems this whole section is about, distinguished directly from ext4 and xfs, which do require a real underlying device.

To see which filesystems are actually mounted right now, including the virtual ones quietly running alongside your real disks the whole time:

findmnt
TARGET                SOURCE   FSTYPE   OPTIONS
/                     /dev/sda2 ext4    rw,relatime
├─/proc               proc     proc     rw,nosuid,nodev,noexec
├─/sys                sysfs    sysfs    rw,nosuid,nodev,noexec
├─/run                tmpfs    tmpfs    rw,nosuid,nodev

Every one of these entries besides the real ext4 root filesystem is a virtual filesystem, mounted and functioning exactly like any other mount point covered elsewhere in this course, despite having no physical disk behind it.

What’s Next

With the VFS’s role understood — a single, unified interface that lets fundamentally different kinds of “filesystems” behave identically to every program that touches them — the next chapter gets hands-on with the most commonly used virtual filesystem of all: /proc, and everything it exposes about running processes and the kernel itself.

Last updated on