Filesystems · Filesystem Implementation
The Superblock: Magic Number, Geometry, and Feature Flags
How a filesystem announces its own layout at a fixed disk offset, why its feature bits are split into three classes with different contracts, and what a driver does when it meets a bit it does not understand.
A filesystem driver receives a block device and nothing else: no type tag, no length field, no header that says which format the bytes belong to. Everything the driver needs in order to interpret the volume must be discoverable from a location fixed by convention. That location is the superblock, and the first thing it holds is a number that says whether the driver is even looking at the right kind of filesystem.
A record at a fixed byte offset
The ext2, ext3, and ext4 family place the superblock at byte offset 1024 from the start of the volume and give it a length of 1024 bytes. The first kilobyte is left unclaimed so that a boot block or a partition table can live there without colliding with filesystem metadata. At offset 0x38 inside the record sits a 16-bit magic value, 0xEF53, which is the only cheap test available before parsing anything else.
The choice of 1024 has a second consequence. A filesystem block is at least 1024 bytes, so on a volume with 1 KiB blocks the superblock occupies block 1 and block 0 is left for the boot area. On a volume with 4 KiB blocks the superblock sits inside block 0. The field s_first_data_block records which case applies: it is 1 for 1 KiB blocks and 0 otherwise, and every conversion between block number and byte offset has to add it.
The layout of the leading fields is stable and worth reading as a structure, because the arithmetic below depends on their order:
struct ext2_super_block {
__le32 s_inodes_count; /* 0x00 total inodes */
__le32 s_blocks_count; /* 0x04 total blocks, low 32 bits */
__le32 s_r_blocks_count; /* 0x08 blocks reserved for root */
__le32 s_free_blocks_count; /* 0x0c */
__le32 s_free_inodes_count; /* 0x10 */
__le32 s_first_data_block; /* 0x14 0 or 1, see above */
__le32 s_log_block_size; /* 0x18 block size = 1024 << this */
__le32 s_log_frag_size; /* frag size shift; now the cluster shift */
__le32 s_blocks_per_group; /* 0x20 */
__le32 s_frags_per_group; /* 0x24 clusters per group in ext4 */
__le32 s_inodes_per_group; /* 0x28 */
__le32 s_mtime; /* 0x2c last mount time */
__le32 s_wtime; /* 0x30 last write time */
__le16 s_mnt_count; /* 0x34 */
__le16 s_max_mnt_count; /* 0x36 */
__le16 s_magic; /* 0x38 0xEF53 */
__le16 s_state; /* 0x3a clean or not */
};
The block size is not stored directly. s_log_block_size holds a shift count, and the block size is 1024 << s_log_block_size, giving 1024, 2048, 4096, 8192 and so on. Encoding it as a shift keeps the field small and makes the legal set of sizes a short list rather than a range; a driver that finds a shift producing a block larger than the platform’s page size can reject the volume instead of allocating buffers it cannot fill.
Geometry fields and the arithmetic they enable
Three fields do most of the work of turning a byte offset into a metadata record: s_blocks_count, s_blocks_per_group, and s_inodes_per_group. Together they let a driver compute, without reading anything else, which group a given block belongs to and where that group’s descriptor lives. The group descriptor table is covered in Block Groups and the Cost of Metadata Locality; the superblock only has to say how many groups exist, which is ceil((blocks_count - first_data_block) / blocks_per_group).
s_blocks_per_group is bounded by the bitmap that tracks allocation. One block of bitmap holds 8 × block_size bits, so a group cannot exceed that many blocks: 32768 blocks at a 4 KiB block size, 65536 at 8 KiB. The bound is structural, not a policy choice, and it is why a large volume ends up with many groups rather than a few enormous ones.
The inode count is fixed at format time. s_inodes_count is a 32-bit field, and s_inodes_per_group divides it across the groups, so the ratio of inodes to blocks is decided once, when the filesystem is created, from an estimate of how many files the volume will hold. Running out of inodes on a volume with free data blocks is the visible consequence; it is discussed in Inode Tables and the Arithmetic of Inode Numbers.
Three feature words with three different contracts
After the geometry comes the part of the superblock that governs compatibility: three 32-bit words named s_feature_compat, s_feature_incompat, and s_feature_ro_compat. They are not three halves of one namespace. Each word carries a different promise about what an uninformed driver may do.
A bit in the compat word may be ignored entirely. A driver that does not implement it can still read and write the filesystem correctly, because the feature only adds information that other tools may use. Extended attributes and the on-disk journal itself are recorded this way: a driver with no journal support can mount a cleanly unmounted volume and read it, since the journal is only needed to repair an unclean one.
A bit in the read-only-compat word may be ignored only if the volume is mounted read-only. Sparse superblocks and large-file support sit here. A driver that does not understand large files can still read the volume, but if it wrote to it, it might truncate a size field it cannot represent, so write access must be refused. Implementations differ in how they refuse it: some reject a read-write mount outright and tell the operator to retry with the read-only option, others downgrade the mount themselves and log the reason. Both satisfy the contract, and neither may proceed to write.
A bit in the incompat word cannot be ignored at all. If the driver does not understand it, the on-disk structures may be arranged in a way it will misread, so the mount fails. Directory entries carrying a file-type byte, extent trees, 64-bit block numbers, and flexible block groups are all incompat bits. The recover bit is the one that says the journal must be replayed before the metadata can be trusted.
/* The shape of the check. Schematic, not a real driver's table. */
if (sb->feature_incompat & ~FS_INCOMPAT_SUPPORTED)
return -EINVAL; /* refuse the mount */
if ((sb->feature_ro_compat & ~FS_RO_COMPAT_SUPPORTED) && !read_only)
return -EROFS; /* refuse write access */
/* feature_compat is not consulted. */
The split is a versioning strategy. Adding a compat bit costs nothing; adding an incompat bit guarantees that older drivers will refuse the volume, which is why format designers postpone incompat bits as long as possible and why a filesystem created with a recent set of features may be unreadable by a driver from a few years earlier while an older filesystem remains readable by the new one.
Backup copies and the refusal path
A single superblock at one offset is a single point of failure for the entire volume, so the format keeps copies. With the sparse-super feature, backup copies live in group 0 and in every group whose number is a power of 3, 5, or 7 (1, 3, 5, 7, 9, 25, 27, 49, and so on), which spreads them across the volume without paying for one per group. The primary is still the one read at mount time; the backups exist so that a repair tool can reconstruct the primary when the first kilobyte is destroyed.
That reconstruction is the reason the superblock must be self-describing. If the primary copy is unreadable, a tool scans candidate group boundaries for a record whose magic matches and whose geometry fields are mutually consistent, then writes that record back to offset 1024. Nothing else on the volume is needed for this step, which is also why the geometry fields must be checkable against each other: a plausible magic in random data is easy to find, but a magic plus a block count plus a group size that divide evenly is not.
What the format cannot grow into
The feature words are 32 bits each, and most of them are spent. Growth has therefore moved into other places: block counts beyond 32 bits needed a new incompat bit and a second pair of high-order fields; inode sizes beyond 128 bytes needed a field that older drivers read as zero; timestamps beyond 2038 needed extra bytes appended to the inode rather than a wider existing field. Each of these is an append or a parallel field, chosen because inserting bytes in the middle of a record would shift every following offset and force an incompat bit.
The superblock is thus less a description of the filesystem than a contract about how much of it a given driver is allowed to touch. The bytes are fixed; the meaning of the volume is the intersection of what is recorded on disk and what the mounting driver claims to understand, and the three feature words are where that intersection is negotiated.
References
- ext4 Data Structures and Algorithms — Linux kernel documentation
- The Second Extended File System: Internal Layout — Dave Poirier
- Design and Implementation of the Second Extended Filesystem — R. Card, T. Ts'o, S. Tweedie, Proceedings of the First Dutch International Symposium on Linux, 1994
- ext4 General Information — Linux kernel documentation