Operating systems and concurrency
File systems and journaling
How a file system maps names to inodes, why hard links share one inode, and how a journal keeps metadata consistent after a crash.
A file system turns a disk into named files and directories. Each file has an inode, a record holding its size, permissions, timestamps and pointers to its data blocks. The name is separate. A directory entry is just a mapping from a name to an inode number.
That separation explains a lot. Renaming a file doesn't move its data. Deleting a name doesn't free the data until no names point to the inode. Two names can share one inode.
Walking through it: hard links
A hard link is a second name for the same inode. The file's link count tracks how many names exist. Here two names point at one inode:
import os, tempfile
d = tempfile.mkdtemp()
p = os.path.join(d, "a.txt"); q = os.path.join(d, "b.txt")
with open(p, "w") as f: f.write("hello\n")
os.link(p, q)
sa, sb = os.stat(p), os.stat(q)
print(sa.st_ino == sb.st_ino, sa.st_nlink)Output:
True 2Both names report the same inode, and the link count is 2. Deleting one name leaves the data in place, because the other name still refers to the inode. A symbolic link, by contrast, stores a path, so it can dangle if the target is moved or deleted. Hard links can't cross file systems or point at directories, which is why they're rarely used for application data.
The crash problem
Creating a file changes several structures: the directory entry, the inode, a bitmap of free blocks, and the data. A crash between two of those changes leaves the file system inconsistent. For example, a block is marked used but no file points to it, or a directory points to an inode that was never written.
Old file systems fixed this with a slow fsck pass after every crash, which scans the whole disk. On large volumes, that takes too long.
Journaling
A journal solves it, the same way a database's write-ahead log does. Before changing the real metadata, the file system writes the intended change to the journal. After a crash, it replays the committed entries in the journal and discards the rest. Recovery takes seconds, not hours.
Modes differ in what they journal:
- Metadata-only journaling (ext4's default
data=ordered) journals names and inodes and writes data first. The file system stays consistent, but file contents can be stale after a crash. - Full data journaling logs the data too, which is safer and slower.
Journaling keeps the file system's structure consistent. It does not by itself guarantee that your application's data reached the disk. That is the job of fsync, covered in page cache and fsync.
Note
Copy-on-write file systems, such as ZFS and Btrfs, take a different route. They never overwrite live blocks, so a crash leaves either the old tree or the new one, and they checksum data as well.
Common mistakes
- Assuming that a journaled file system protects your file contents. It protects structure by default, not your bytes.
- Using hard links across volumes or for directories, which the system won't allow.
- Treating a rename as a safe update without checking what happens to the old file's data.
Check yourself
- Why does a hard link keep a file's data after the original name is deleted? Answer: both names point to the same inode, and the data is freed only when the link count reaches zero.
- Why does a journal make recovery fast? Answer: it replays only the recent committed changes, instead of scanning the whole disk for inconsistencies.
Key takeaways
- A name is a pointer to an inode. Hard links share an inode, and deleting a name only drops one link.
- Journaling logs metadata changes before applying them, so recovery is quick and structure stays consistent.
- Journaling doesn't guarantee your data is on disk. Use
fsyncfor that.
Further learning
Next, see page cache and fsync to learn when data is truly durable.
Related
Files and storage
How file systems map names to inodes, how journaling and the page cache affect durability, and how the storage devices underneath behave, from SATA disks and SSDs to cloud volumes.
Page cache and fsync
Why a successful write only reaches RAM, how fsync and fdatasync force data to disk, when O_DIRECT helps, and why a failed fsync cannot be retried.
Write-ahead log
The write-ahead log records every change before the page is written. How commits become durable, how recovery replays the log, and what group commit and checkpoints do.