Filesystems · Filesystem Implementation

Mounting: From Device Node to a Live Superblock

The sequence a filesystem driver follows when it is handed a block device, the checks that must pass before the volume becomes writable, and what a failed mount must unwind.

A mount is the point at which bytes on a device become a filesystem the kernel can call into. Everything before it is speculation: a device node exists, the kernel has a driver for some format, and nothing has verified that the device actually contains that format. Everything after it is a live object with an operation table, a root inode, and a set of cached dentries. The sequence between those two states is where most of a filesystem’s defensive checks live.

Reading the superblock and deciding whether to trust it

The driver’s first action is to read the fixed offset where the superblock should be and check the magic value. A mismatch is the expected case, not an error: a device may hold any format, and a driver that mounts ext4 must fail cleanly when the device holds XFS. Probing several filesystems in turn means each one gets a chance to claim the device, and a false positive would corrupt a volume belonging to another format, so the magic check is followed by consistency checks before anything is written.

The checks that matter are arithmetic. The block size must be a legal power of two and must not exceed what the kernel can address. The block count must be consistent with the group size and the group count. The inode count must divide across the groups in a way that the addressing arithmetic can use. A superblock whose fields disagree is either corrupt or is not a superblock, and either way the mount must fail rather than proceed on a guess.

A backup superblock exists for exactly this reason, as described in The Superblock: Magic Number, Geometry, and Feature Flags, but the driver does not automatically fall back to it. Falling back silently would mean mounting a volume whose primary metadata is damaged, and the correct response to a damaged primary is to refuse the mount and let a repair tool decide.

Feature negotiation before anything else

Once the geometry is consistent, the three feature words decide what the driver may do. An unknown incompat bit ends the mount immediately: the driver cannot know how the structures are arranged. An unknown read-only-compat bit does not end the mount, but write access must be refused, because the driver can read structures it cannot safely modify; whether the mount is downgraded silently or rejected outright is a matter of policy. An unknown compat bit is ignored.

The downgrade path has a second stage. Even when the driver understands every feature, the mount may be requested read-only for other reasons: the device is read-only, the superblock’s error state says the filesystem has already detected an inconsistency, or the previous mount did not complete cleanly. A filesystem that was not unmounted cleanly has its state field marked, and a read-write mount of such a volume requires recovery first. Refusing to mount read-write until recovery has run is what prevents a driver from writing into a half-updated structure.

Recovery as part of mount

If the volume carries a journal and the superblock says it needs replay, the mount path runs recovery before the filesystem becomes visible. The journal is scanned for committed transactions not yet checkpointed, and each is replayed into its home location. Replay is safe to repeat because each transaction’s blocks are written to their final locations, so applying a transaction that was already applied writes the same bytes again rather than a second copy of them.

Recovery is the part of mount that writes into the volume’s own metadata before the filesystem is live, and it runs before the filesystem’s caches are populated, which is why it can be done without the full mount machinery. It is also why a mount can fail after recovery has already modified the volume: if the replay succeeds and the subsequent initialisation fails, the device has been changed by a mount that did not complete.

The replay also depends on the ordering the journal established at commit time. A transaction is valid only if its commit record reached the device, and the commit record is written after the transaction’s blocks with a flush between. A device that reorders writes without honouring the flush could leave a commit record whose data blocks are absent, which is exactly the state recovery must not trust. Filesystems defend against this by checksumming the journal blocks, so that a transaction whose payload does not match its checksum is treated as not committed rather than replayed.

/* Schematic mount sequence. Not kernel source. */
int fill_super(struct super_block *sb)
{
        struct fs_info *fsi = read_superblock(sb->device);
        if (!fsi || fsi->magic != FS_MAGIC)
                return -EINVAL;                /* not our format, or not a superblock */
        if (fsi->incompat & ~FS_INCOMPAT_SUPPORTED)
                return -EINVAL;                /* cannot safely parse the volume */
        if ((fsi->ro_compat & ~FS_RO_COMPAT_SUPPORTED) && !is_read_only(sb))
                return -EROFS;                 /* refuse write access, at minimum */
        if (needs_recovery(fsi) && !is_read_only(sb) && replay(fsi) < 0)
                return -EIO;                   /* journal is damaged */
        sb->root = make_root_dentry(read_inode(sb, FS_ROOT_INO));
        return sb->root ? 0 : -ENOMEM;
}

Building the in-memory structures

A successful mount produces four things: a superblock with its private data, a root inode, a root dentry, and an operation table. The root inode is read from the fixed inode number the format reserves for it, the lookup described in Inode Tables and the Arithmetic of Inode Numbers, and it is the anchor for every path resolution on the filesystem. Its mode must be that of a directory, its link count must be at least two, and its extent or block map must be readable; any of those failing is a mount failure.

The root dentry is created with no parent, which is how the layer knows it is a mount point. A dentry’s parent pointer is what path resolution follows upward, and a root with a parent would create a cycle. The layer also records the mount in the mount namespace’s tree, so that the same filesystem mounted at two paths appears twice in the namespace while both mounts reach the same superblock.

Sharing is a policy decision with real consequences. Two mounts sharing a superblock share the page cache, the inode cache, and the dirty state, so a write through one path is visible through the other immediately. Two mounts with separate superblocks do not, and a write through one may not be visible through the other until the cache is flushed. A network mount made with a different option set gets a superblock of its own and therefore a cache of its own, so a write through one path becomes visible through the other only when the client’s cached attributes expire; mounts of a local device share, and a write through either path is immediately visible through both.

Unwinding a failed mount

Every step after the first allocation has to be reversible, and the unwinding order is the reverse of the acquisition order. A mount that read the superblock, replayed the journal, allocated private data, and then failed to read the root inode must free the private data, release the device, and leave the journal in whatever state the replay left it. It cannot undo the replay, because the replay has already written to the device.

That asymmetry is why recovery is placed as late as possible. A driver that replays first and validates later can leave a device modified by a mount that never succeeded; a driver that validates everything it can before replaying minimises that window. The validation that requires the journal to be replayed, such as reading an inode whose metadata is in the journal, cannot be done early, and the split between the two kinds of check is the design problem the mount path is really solving.

The other failure mode is a mount that succeeds but leaves the volume in a state that the next mount will treat as unclean. The superblock’s mount count and last-mount-time fields are updated at mount and the state field is cleared at unmount. A crash between the two leaves the state marked, and the next mount runs recovery again, which is correct but expensive. Some filesystems suppress the mount count entirely, on the grounds that the count is a heuristic for when to run a full check and that a journal makes the check unnecessary.

What mount cannot fix

A mount is not a repair. It validates the superblock, negotiates features, replays the journal, and reads the root. It does not verify that every inode’s block map is consistent with the bitmap, that every directory entry names an allocated inode, or that the free counts match reality. Those checks are the province of the repair tool described in fsck and the Ordering of Repairs, and the division is deliberate: a mount must be fast, and a full check is proportional to the volume’s size.

The consequence is that a mount can succeed on a volume that is corrupt in ways the journal does not cover. A bitmap that claims a block is free when an inode’s extent references it will not stop the mount, and the first write into that block will overwrite file data. Detecting that requires the full scan, which is why a filesystem that has been repaired or that has detected an error at runtime marks itself for a check on the next mount rather than trusting the previous state.

References

  1. mount(2) — Linux manual page — man7.org
  2. ext4 Data Structures and Algorithms — Super Block — Linux kernel documentation
  3. Overview of the Linux Virtual File System — Linux kernel documentation
  4. The Design of the UNIX Operating System — Maurice J. Bach, Prentice Hall, 1986