VFS Superblock Object
The superblock object —
struct super_block, defined ininclude/linux/fs.h(torvalds/linux v6.12) — is the kernel’s in-memory representation of one mounted filesystem instance. Where the on-disk “superblock” is a fixed block of bytes describing the filesystem’s geometry, the VFSstruct super_blockis the live runtime object the kernel builds at mount time to manage that instance: it points at the filesystem’s method table (s_op), the root dentry of the mounted tree (s_root), the backing block device (s_bdev) or pseudo-source, the filesystem’s private state (s_fs_info), the block size and maximum file size, and the lists of inodes belonging to the instance (s_inodes). One block device mounted twice at two paths normally yields onesuper_block(the second mount shares it); two different filesystems mounted yield two. It is created by theget_tree_*family calling the filesystem’sfill_super, and destroyed at unmount viakill_sb→generic_shutdown_super.
This note is about the kernel object — its fields, how it is born at mount and how statfs and teardown work through it. The registration of a filesystem type and the broader mount mechanism (the fs_context/fsopen/fsmount path, mount propagation, vfsmount) are owned by Filesystem Registration and Mounting Internals and The New Mount API; the method tables in general are owned by VFS Operation Tables. Read those for the surrounding machinery; read this for the object that anchors a mounted instance.
Mental Model
A mounted filesystem is a small graph hanging off one struct super_block:
flowchart TB FST["file_system_type<br/>(e.g. ext4)<br/>.mount/.init_fs_context, .kill_sb"] FST -->|"fill_super at mount"| SB["struct super_block<br/>s_op, s_root, s_dev<br/>s_blocksize, s_maxbytes<br/>s_fs_info, s_inodes"] SB -->|s_root| ROOT["root dentry<br/>(top of mounted subtree)"] SB -->|s_op| SOP["super_operations<br/>(alloc_inode, statfs,<br/>sync_fs, put_super, ...)"] SB -->|s_bdev| BD["block_device<br/>(or NULL for pseudo fs)"] SB -->|s_fs_info| PRIV["fs-private state<br/>(e.g. ext4_sb_info)"] SB -->|s_inodes| INODES["all live inodes<br/>of this instance"] ROOT --> INO["root inode"] INODES -.-> INO BD -.-> DISK["on-disk superblock<br/>+ filesystem data"]
One mounted instance, anchored by its superblock. What it shows: a file_system_type (registered once per filesystem driver) produces a struct super_block at mount time via fill_super; the superblock then ties together the root dentry (s_root), the method table (s_op), the backing device (s_bdev), the driver’s private state (s_fs_info), and the list of all this instance’s inodes. The insight to take: the superblock is the per-mount-instance object — the place the kernel hangs everything that is true of “this ext4 on /dev/sda1 mounted here,” as opposed to everything true of “ext4 the driver” (which is the file_system_type) or “one file” (which is the inode).
Anatomy of struct super_block (v6.12)
The structure is large; the fields that carry the concept, walked one at a time (fs.h:1253):
struct super_block {
struct list_head s_list; /* node on the global super_blocks list */
dev_t s_dev; /* identifying device number (search key) */
unsigned char s_blocksize_bits;
unsigned long s_blocksize; /* block size in bytes */
loff_t s_maxbytes; /* max file size this fs supports */
struct file_system_type *s_type; /* the driver that owns this instance */
const struct super_operations *s_op; /* the method table */
...
unsigned long s_flags; /* SB_RDONLY, SB_NOSUID, SB_ACTIVE, ... */
unsigned long s_magic; /* fs magic number (e.g. EXT4_SUPER_MAGIC) */
struct dentry *s_root; /* root dentry of the mounted subtree */
struct rw_semaphore s_umount; /* serializes mount/unmount/remount */
int s_count; /* lifetime refcount */
atomic_t s_active; /* active (mounted) refcount */
...
struct list_head s_mounts; /* mounts using this sb (VFS-only) */
struct block_device *s_bdev; /* backing block device, or NULL */
struct file *s_bdev_file; /* file handle on the bdev (v6.12) */
struct backing_dev_info *s_bdi; /* writeback/readahead congestion info */
struct hlist_node s_instances; /* node on s_type->fs_supers */
...
void *s_fs_info; /* FILESYSTEM PRIVATE info */
u32 s_time_gran; /* timestamp granularity (ns) */
...
char s_id[32]; /* informational name (e.g. "sda1") */
uuid_t s_uuid; /* filesystem UUID */
...
struct user_namespace *s_user_ns; /* user-ns for uid/gid/xattr interpretation */
struct list_lru s_dentry_lru; /* per-sb dentry shrinker list */
struct list_lru s_inode_lru; /* per-sb inode shrinker list */
...
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* ALL inodes of this instance */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* inodes under writeback */
} __randomize_layout;s_dev— adev_tthat identifies the instance and serves as the lookup key for “do we already have a superblock for this device?” For block-backed filesystems it is the device number ofs_bdev; for anonymous/pseudo filesystems it is an anonymous device number allocated byset_anon_superfrom a dedicated minor pool. It is what shows up as thest_devof every file on the filesystem, and what makes inode numbers unique only within a device.s_blocksize/s_blocksize_bits— the filesystem block size in bytes and its log2. Set byfill_super(commonly viasb_set_blocksize()), it governs how the page cache and block layer chunk I/O for this instance.s_maxbytes— the largest file size the filesystem supports.alloc_superinitializes it toMAX_NON_LFS(the non-large-file limit, ~2 GiB) andfill_superraises it to the real value (e.g. ext4’s multi-TiB limit).vfs_get_treewarns if a filesystem ever sets it negative.s_type—struct file_system_type *, a back-pointer to the driver (ext4, xfs, tmpfs, …). The superblock is linked ontos_type->fs_supersvias_instancesso the kernel can enumerate all instances of a given filesystem type.s_op—const struct super_operations *, the method table the VFS calls through for whole-filesystem operations (allocate/free an inode, write a dirty inode,statfs,sync_fs,put_super, freeze/thaw). This is the superblock-level analogue off_opon a file. Detailed below and in VFS Operation Tables.alloc_superinitializes it to adefault_opof all-NULL methods;fill_superoverwrites it.s_flags— mount-wide flags:SB_RDONLY(read-only),SB_NOSUID,SB_NODEV,SB_NOEXEC,SB_SYNCHRONOUS,SB_ACTIVE(the instance is live and usable — set afterfill_supersucceeds),SB_BORN(fully published — set byvfs_get_tree), and more. These are the kernel-internalSB_*form of the userspaceMS_*mount flags.s_magic— the filesystem’s magic number, e.g.EXT4_SUPER_MAGIC(0xEF53) orTMPFS_MAGIC(0x01021994). It is whatstatfs(2)returns inf_type, letting userspace identify the filesystem kind of a path.s_root—struct dentry *, the root dentry of the mounted subtree. This is the single most important field: it is the anchor from which the whole in-memory directory tree of this instance hangs, and the thing a mount grafts onto a mount point. A superblock with a non-NULLs_roothas been fully filled in;sget_fcreturns a partially-constructed superblock withs_root == NULLand the caller (vfs_get_super/fill_super) sets it.s_bdev/s_bdev_file— the backingstruct block_device(and, new in this era, thestruct filehandle on it). For pseudo filesystems (tmpfs, proc, sysfs, sockfs) this isNULL— they have no block device, only an anonymouss_dev.s_bdi—struct backing_dev_info *, the writeback/readahead-tuning object the flusher threads consult for this instance.alloc_superpoints it atnoop_backing_dev_infountil the real one is attached.s_fs_info—void *, the filesystem’s private superblock state. This is where the driver hangs its own parsed on-disk superblock and runtime bookkeeping —ext4_sb_infofor ext4,xfs_mountfor XFS,shmem_sb_infofor tmpfs. The VFS never touches it; it belongs entirely to the filesystem.s_inodes(+s_inode_list_lock) — the list of all inodes currently belonging to this instance, used at unmount to evict them and by thes_inode_lrushrinker to reclaim them under memory pressure.s_inodes_wbis the subset currently under writeback.s_user_ns— the owning user namespace, used to interpret on-disk uid/gid/quota/xattr values — the linchpin of unprivileged (“user-namespace”) mounts of filesystems flaggedFS_USERNS_MOUNT.s_umount— anrw_semaphorethat serializes mount, unmount, and remount against each other and against concurrent operations that must see a stable mount state.s_count/s_active— two refcounts with different meanings.s_activecounts active (mounted) references; when it hits zero the filesystem is told to shut the instance down (kill_sb).s_countis the broader lifetime count keeping the memory alive while temporary lookups hold it. The split lets a brief lookup pin the structure (s_count) without keeping the filesystem mounted (s_active).
How a superblock is born: get_tree_* and fill_super
A superblock comes into existence at mount time, driven by the new mount API (Documentation/filesystems/mount_api.rst). The userspace mount(2) (or fsopen/fsconfig/fsmount) builds a struct fs_context describing what to mount and how, then vfs_get_tree(fc) invokes the filesystem’s get_tree operation. The filesystem almost always implements get_tree by calling one of the VFS-provided helpers, chosen by what kind of source it has (fs/super.c):
get_tree_bdev(fc, fill_super)— for a block-device-backed filesystem (ext4, XFS, Btrfs, …). Itlookup_bdev()s the source path to adev_t, thensget_dev(fc, dev)finds-or-creates a superblock keyed on that device. Ifs->s_rootis already set, an existing instance is being shared (the second mount of the same device) — it only checks the RO/RW state matches. Otherwise it callssetup_bdev_super()(attachings_bdev, settings_dev) and then the filesystem’sfill_super, and on success setsSB_ACTIVE.get_tree_nodev(fc, fill_super)— for a filesystem with no backing device and no sharing (a fresh, independent instance every mount). It callssget_fc(fc, NULL, set_anon_super_fc), which always allocates a new superblock with an anonymouss_dev, then runsfill_super.get_tree_single(fc, fill_super)— for a filesystem that should have exactly one shared instance system-wide (e.g. sysfs-like singletons):test_single_superalways matches, so the first mount creates it and every later mount shares it.get_tree_keyed(fc, fill_super, key)— share-or-create keyed on an arbitrarykeystashed ins_fs_info(used when “the same instance” is defined by something other than a device, e.g. a network filesystem’s server+export tuple).
All four funnel through sget_fc(), the find-or-create core. sget_fc walks fc->fs_type->fs_supers looking for an existing superblock the test callback accepts; if none matches it alloc_super()s a fresh one, runs the set callback, copies in s_fs_info, sets s_type and s_id, publishes it onto the global super_blocks list and the per-type fs_supers hlist, and returns it in a partially constructed state — s_root is still NULL, SB_BORN/SB_ACTIVE not yet set (sget_fc, fs/super.c).
The filesystem-specific fill_super(sb, fc) is then where the real work happens: it reads the on-disk superblock off s_bdev, validates the magic, sets s_blocksize/s_maxbytes/s_magic/s_time_gran, installs the real s_op (and s_d_op, s_xattr, etc.), allocates and reads the root inode, builds s_root via d_make_root(), and stashes its parsed state in s_fs_info. When fill_super returns success, vfs_get_super does sb->s_flags |= SB_ACTIVE and the caller sets fc->root = dget(sb->s_root). Finally, back in vfs_get_tree, super_wake(sb, SB_BORN) publishes the instance as fully alive (with a barrier ordering the setup writes before the SB_BORN flag becomes visible). The order — publish empty → fill → mark ACTIVE → mark BORN — is what lets concurrent lookups safely wait for SB_BORN rather than racing a half-built superblock.
statfs: a method call through s_op
statfs(2)/fstatfs(2) are the clearest example of dispatching through the superblock’s method table. The syscall resolves the path (or fd) to a dentry, then statfs_by_dentry() calls, verbatim (fs/statfs.c):
if (!dentry->d_sb->s_op->statfs)
return -ENOSYS;
...
retval = dentry->d_sb->s_op->statfs(dentry, buf);That is: from the dentry, reach its superblock (d_sb), reach the method table (s_op), and call the filesystem’s statfs method, which fills a struct kstatfs (total/free/available blocks, free inodes, block size, filesystem magic in f_type, fragment size, max filename length). The VFS copies the kstatfs out to the userspace struct statfs. Every other whole-filesystem operation works the same way — sync_fs, freeze_super/thaw_super, remount_fs, the inode-lifecycle hooks alloc_inode/destroy_inode/write_inode/evict_inode — each is a s_op->method(sb, …) call, exactly as file operations are f_op->method(file, …) calls on the VFS File Object. The super_operations members and their contracts are documented in Documentation/filesystems/vfs.rst and enumerated in VFS Operation Tables.
How a superblock dies: kill_sb → generic_shutdown_super
Unmount drops the last active reference. deactivate_locked_super(s) does atomic_dec_and_test(&s->s_active); when that reaches zero it frees the per-sb shrinker, calls the filesystem’s s->s_type->kill_sb(s), destroys the LRU lists, drops the file_system_type reference, and finally put_super() (fs/super.c). Almost every filesystem implements kill_sb by doing its own cleanup and then calling the common helper generic_shutdown_super(sb), which: shrinks the dcache for the instance (shrink_dcache_for_umount), runs a final sync_filesystem, clears SB_ACTIVE, evicts all zero-refcount inodes (evict_inodes), tears down fsnotify and security state, calls the filesystem’s s_op->put_super if present, destroys the fscrypt keyring, and — defensively — poisons any inodes still on s_inodes after all that, logging “VFS: Busy inodes after unmount” (a “you forgot to release an inode” bug in the filesystem). It then marks the superblock SB_DYING, drops the backing-device reference, and the memory is freed via RCU once s_count also reaches zero.
Failure Modes and Diagnostics
EBUSYonmountfrom an RO/RW mismatch.get_tree_bdevrefuses to silently flip a shared instance’s read-only state: if you try to mount a device read-write that is already mounted read-only (or vice-versa) and they would share one superblock, it returns-EBUSY(“Can’t mount, would change RO state”). Usemount -o remount,rwto change the state of the existing instance instead.EBUSYonumount(busy filesystem).s_activecannot reach zero while anything holds the instance: an open file (itsf_path.mnt/dentry pin it), a process with a cwd inside it, or a bind mount.generic_shutdown_super’s “Busy inodes after unmount” poison is the kernel’s last-ditch guard against a filesystem that leaks inodes;lsof +f -- /mountpointandfuser -mfind the userspace culprits.ENOSYSfromstatfs. A filesystem with nos_op->statfsmethod returns-ENOSYS; in practice every real filesystem implements it.- Anonymous-device exhaustion. Pseudo filesystems draw
s_devfrom a limited anonymous-minor pool viaset_anon_super→get_anon_bdev, which allocates from a 20-bit minor ID space (ida_alloc_range(..., (1 << MINORBITS) - 1, ...)). On exhaustion the allocator’s-ENOSPCis explicitly remapped to-EMFILEbefore returning (get_anon_bdev, fs/super.c). Pathological numbers of simultaneous pseudo mounts (e.g. systems creating huge numbers of mount namespaces) can in principle hit this; it is rare in practice.
Uncertain
Verify: the field set, ordering, and the
s_bdev_filecompanion field shown above are from v6.12include/linux/fs.h;struct super_blockis large and gains/loses members across releases (thes_bdev_filehandle and the per-sb shrinkers_shrinkpointer are recent additions). Reason: layout is volatile release-to-release. To resolve: diffinclude/linux/fs.hagainst the specific target tag before quoting field offsets or the presence of any optional#ifdef-gated member. uncertain
Alternatives and Boundaries
There is no alternative to the superblock — it is the per-mounted-instance object in Linux. The useful distinctions are with its neighbours:
- vs
file_system_type. Onefile_system_typeper registered driver (ext4); onesuper_blockper mounted instance of it. The driver is the factory; the superblock is a product. Owned by Filesystem Registration and Mounting Internals. - vs
vfsmount/struct mount. Asuper_blockis the filesystem instance; avfsmountis a placement of (part of) that instance into the namespace tree. One superblock can underlie manyvfsmounts (bind mounts), which is exactly whyf_pathon a file carries both a vfsmount and a dentry. Owned by The Mount Tree and vfsmount. - vs the on-disk superblock. The on-disk superblock is a fixed byte layout (ext4’s is at a known offset, with magic
0xEF53);struct super_blockis the runtime objectfill_superbuilds from it. They are not the same thing, and a filesystem may keep many on-disk superblock copies (backups) for one runtimestruct super_block.
Production Notes
The superblock is the natural granularity for filesystem-wide operations administrators actually invoke. sync_fs is what syncfs(2) and sync(8) drive; freeze_super/thaw_super back fsfreeze(8) and the FIFREEZE ioctl that LVM snapshots rely on to quiesce a filesystem to a consistent state before snapshotting. The per-sb s_inode_lru/s_dentry_lru shrinkers (registered in alloc_super) are how memory reclaim reclaims cached inodes and dentries per filesystem, which matters on machines with thousands of mounts — a single global LRU would be a scalability bottleneck. The migration to the fs_context/get_tree_* mount API (merged for 5.2, 2019) was a multi-year effort to replace the opaque, hard-to-extend mount(2) data page with an incremental, introspectable configuration object; the superblock-creation helpers above are its filesystem-facing half (LWN: Mount API, LWN: filesystem mounting).
See Also
- VFS Operation Tables —
super_operationsand the other method tables, in depth - Filesystem Registration and Mounting Internals —
file_system_type,get_tree_*,fill_superfrom the registration/mount side - The Virtual File System Layer — the layer this object lives in
- The New Mount API — the
fs_context/fsopen/fsmountpath that drives superblock creation - The Mount Tree and vfsmount — how a superblock instance is placed into the namespace
- VFS Inode Object — the per-file objects a superblock owns (
s_inodes) - VFS File Object — the per-open object;
s_opis the superblock-level analogue of itsf_op - MOC: Linux Filesystems and VFS MOC