The split that makes everything else possible
Guide 1 showed you the VFS view from above: a path comes in, the kernel walks it through a tree of in-memory dentries and arrives at an in-memory inode object, and from there read() and write() dispatch to a filesystem's own functions. That was the runtime picture, living in RAM. This guide drops underneath it to the question VFS deliberately hides: when nothing is cached and the machine has just booted, where on the actual disk is any of this, and in what shape? The single most important idea you will take away is a split — and once you see it, half the design of every Unix filesystem falls into place.
The split is this: a file's name and a file's data are stored in two completely different places, and the link between them is a number. Almost everyone arrives assuming the name "report.txt" is somehow attached to the file's bytes, like a label glued to a box. It is not. The bytes of report.txt live in one structure, the inode, which holds no name at all. The name lives somewhere else entirely, in the directory, paired with the inode's number. Pull on that one fact and you can already predict why a file can have two names, why renaming never copies data, and why deleting a name does not always delete a file.
The inode: everything about a file except its name
An inode is a small fixed-size record — 128 or 256 bytes is typical for ext4 — and it is the true identity of a file. Open it up and you find every property the file has except its name: the file type (regular file, directory, symlink), the permission bits and owner and group, the size in bytes, three timestamps, and a link count we will return to. Then comes the part that does the real work: a map from the file's logical bytes to the physical blocks on disk that actually hold them. The inode is the spine of the file; the name is just a tag someone hung on it.
Where do these records live? In a fixed array, the inode table, written to a known region of the disk when the filesystem is created. Each inode is identified by its position in that array — its inode number, the integer you see as "Inode" in " ls -i" or " stat report.txt". This is exactly the number guide 1's path walk ends on. Because the table has a fixed size, the count of inodes is fixed at format time too: a disk can run out of inodes while gigabytes of space remain free, if you fill it with millions of tiny files. That is not a bug, it is a direct consequence of laying out a fixed inode table up front.
$ stat report.txt
File: report.txt
Size: 8192 Blocks: 16 IO Block: 4096 regular file
Device: 259,2 Inode: 1973 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ joey) Gid: ( 1000/ joey)
inode 1973 holds: type=regular mode=0644 uid=1000 size=8192
links=1 block-map -> [ data blocks ... ]
^ no filename anywhere in hereDirectories: tables of name-to-number, nothing more
If names are not in inodes, where are they? In directories — and the punchline is that a directory is itself just a file. It has its own inode (with type "directory" instead of "regular"), and its data blocks do not hold document text; they hold a table of directory entries. Each entry is almost nothing: a filename and the inode number it points to. That is the whole of a directory: a list of (name, inode-number) pairs. "Open the directory /home/joey" means "read the data blocks of the inode that /home/joey resolves to, and parse them as a list of these pairs."
Now path resolution from guide 1 stops being magic and becomes mechanical. To resolve /home/joey/report.txt, the kernel starts at the root inode (a known fixed number, traditionally 2), reads its directory entries, finds the one named "home", takes that entry's inode number, reads that inode's directory entries, finds "joey", and repeats — one inode read and one table scan per path component — until it finds "report.txt" and learns its inode number. Every slash in a path is one of these lookups. The directory is the only structure that knows names; the inode never does.
This name/inode split is the secret behind a hard link. Because a name is just an entry pointing at an inode number, nothing stops two directory entries — even in different directories — from pointing at the same inode number. Both names then refer to one identical file; there is no original and no copy. That is what the link count in the inode counts: how many directory entries point at it. "$ rm report.txt" does not erase data — it removes one directory entry and decrements that count. Only when the count hits 0 (and no process still holds the file open) does the filesystem actually free the inode and its blocks. Deleting a name is not deleting a file.
From inode to bytes: extents and the indirect trick
Now the inode's real job: mapping logical file offsets to physical disk blocks. The naive scheme is a plain list — block 0 of the file is at disk block X, block 1 at disk block Y, and so on. It works, but it is wasteful, because real files are usually stored contiguously: thousands of consecutive file blocks sit on thousands of consecutive disk blocks. Listing each one individually is like writing "seat 1, seat 2, seat 3, ... seat 1000" when you could just say "seats 1 through 1000." That compression is exactly the idea of an extent.
An extent is a single descriptor for a run of contiguous blocks: three numbers saying "logical block L of the file maps to physical block P on disk, for a length of N blocks." One 12-byte extent can cover a 4 MiB region that the old per-block list would have needed a thousand entries for. A modern filesystem like ext4 stores a handful of extents right inside the inode itself, so for the common case — a file that fits in a few contiguous runs — the entire block map is read in the same disk access that read the inode. This is why ext4 reads large, well-laid-out files so efficiently: the map is tiny and it is already in hand.
But what if a file is huge or badly fragmented and needs more extents than fit in the inode's small slot? Here the older but still-vital trick of the indirect block appears, sometimes wrapped in a tree. Instead of storing the map inline, the inode points to a separate disk block that holds more map entries; if even that overflows, a doubly-indirect block points to a block of pointers to map blocks, and so on. ext4 generalizes this into an extent tree: the inode holds the root, and interior nodes on disk fan out to leaves full of extents. The cost is honest — a badly fragmented file needs extra disk reads just to assemble its map before any data is read — which is the real meaning of fragmentation hurting performance.
Laying it all out on the disk
Step back and assemble the regions a fresh ext-family filesystem writes at format time. First, near the very front, the superblock — a small record describing the whole filesystem: block size, total block count, how many inodes exist, where the other regions begin. It is the master index, and it is so important that filesystems keep backup copies of it scattered across the disk. Lose the superblock with no backup and the disk becomes an unreadable sea of blocks, even though every byte of your data is still physically there.
Next come the bookkeeping bitmaps. A block bitmap is one bit per data block: 1 means used, 0 means free. To allocate space for a growing file, the filesystem scans this bitmap for 0s; to free a block, it clears the bit. An inode bitmap does the same for the inode table. Then the inode table itself, then the great bulk of the disk: the data blocks that hold file contents and directory entries alike. ext4 actually slices the disk into many block groups, each a miniature copy of this layout, so a file's inode, its data, and its directory tend to land near each other — pure mechanical sympathy, keeping related reads physically close.
Pull the whole picture together and it is satisfyingly small. A file is its inode, an inode holds metadata plus an extent map (overflowing into indirect or tree blocks when needed), and names live apart in directories as entries pointing at inode numbers — a split that gives you hard links and cheap renames for free. The superblock, bitmaps, inode table, and data blocks are simply where all of this is written down. Next, guide 3 confronts the danger this callout just exposed: how a filesystem survives a crash between those writes without corrupting itself.