03. File-System Implementation

Tanenbaum Chapter 4.3: Physical Layouts, Storage Allocation Models, Directory Records, Virtual File Systems, and Journaling.

4.3.1 File-System Layout

File systems are stored on non-volatile disks, solid-state drives, or partitions. Physical storage devices divide raw media into fixed-size physical sectors (typically 512 bytes or 4096 bytes). Operating system file systems group these physical sectors into larger logical blocks (clusters), typically ranging from 1 KB to 64 KB, to balance metadata overhead against internal fragmentation.

4.3.2 Implementing Files: Allocation Strategies & Fragmentation

The central design challenge of a file system is mapping a linear stream of logical file bytes into physical storage blocks. Over time, file churn produces external and internal fragmentation.

FAT32 Volume Optimizer (500 MB Drive)
Theme:
Disk Size:
Speed:
Status
Cluster 16,936 29%
Elapsed Time: 00:00:00
Full Optimization
Legend
■ - Used
▒ - Unused
r - Reading
W - Writing
B - Bad
X - Unmovable
Drive C: 1 block = 27 clusters
Legend:
Free Space
Optimized
Unoptimized
System
500 MB Volume Initialized. 3,000 Blocks on Screen. Optimization: 0% | Fragmentation: High
Sound FX: Defrag in MS-DOS 6.22 (ASMR) by Christopher Swenson

4.3.5 Log-Structured File Systems (LFS)

Traditional Unix and FAT filesystems distribute file data, inodes, directory entries, and indirect blocks across random locations on disk. As processor and memory speeds outpaced mechanical disk seek times in the early 1990s, random disk head seeks emerged as the primary performance bottleneck. To solve this, Mendel Rosenblum and John K. Ousterhout pioneered Log-Structured File Systems (LFS) at UC Berkeley, fundamentally redesigning storage architectures by transforming the disk into a continuous sequential log.

Pioneers Profile: Mendel Rosenblum & John K. Ousterhout

John K. Ousterhout
John K. Ousterhout Stanford University • Wikipedia Entry Stanford
Professor of computer science at Stanford University. Received his B.S. from Yale and Ph.D. from Carnegie Mellon. Alongside foundational work on LFS, he is renowned for creating the Tcl/Tk scripting language and leading the Sprite distributed operating system project at UC Berkeley.
Mendel Rosenblum
Mendel Rosenblum Stanford University • Wikipedia Entry Stanford
Professor of computer science at Stanford University and co-founder of VMware. Received his B.A., M.S., and Ph.D. from UC Berkeley. His pioneering research spans operating systems, virtual machine monitors, distributed storage, and large-scale systems architecture.
  • Institution: University of California, Berkeley
  • Key Publication: “The Design and Implementation of a Log-Structured File System” (ACM TOCS, 1992)
  • Core Innovation: Replaced random in-place metadata and data updates with continuous sequential log writes, accompanied by inode maps and background segment cleaning.

1. The Log-Structured Paradigm & The Write Bottleneck

In traditional file systems (such as FFS or FAT), modifying a file requires multiple random disk I/O operations. Rosenblum and Ousterhout observed that caching absorbs reads, making writes the primary bottleneck. LFS buffers updates in memory and writes them out sequentially to segments.

Figure 4.3.5A: Traditional In-Place Updates vs. LFS Sequential Append Log Traditional File System (Random In-Place Writes) Inode Data Dir Multiple random head seeks per write Log-Structured File System (Sequential Log) Inode Data Inode Dir Single contiguous sequential stream (No seeks)
Figure 4.3.5A: Comparison of random in-place updates versus LFS continuous append logging.
Walkthrough Part 1: Sequential Appends & Dead Space Accumulation Step 1 of 3: Ready
Welcome to the LFS Walkthrough! LFS converts all file system writes into a fast, contiguous sequential stream. Click '1. Append New File' to stream files into the log tail.
Ready to begin LFS demonstration. Live Blocks: 0 | Dead Blocks: 0 | Free Blocks: 32

2. The Inode Map (Imap) Architecture & Indirection

In traditional file systems, inode numbers map to fixed disk offsets. In LFS, inodes relocate on every append, requiring the Inode Map (Imap) indirection layer.

Figure 4.3.5B: Dynamic Inode Map (Imap) Resolution to the Sequential Log Tail File Inode # Inode 42 Index Inode Map (Imap Chunk) Inode 0 → Sector 1024 Inode 42 → Block 8420 Inode 43 → Sector 3120 (Decouples static ID from offset) Locates Active Log Tail (Append Frontier) Block 8420 Inode 42 Block 8421 File Data Bytes Block 8422 Updated Imap → Writes stream contiguously; Frontier advances forward →
Figure 4.3.5B: The Inode Map decoupling persistent file numbers from dynamic log tail write offsets.

3. Checkpoint Regions & Rapid Crash Recovery

LFS breaks recursive imap indirection using fixed Checkpoint Regions (CRs). To prevent write tearing during power cuts, LFS alternates between dual regions (CR A and CR B), picking the most recent valid checksum upon reboot.

4. Background Garbage Collection & Segment Cleaning

Segment cleaning reclaims fragmented dead space using the Cost-Benefit policy ((1-u) × Age) / (1+u), balancing hot and cold data compaction.

Walkthrough Part 2: The 4-Phase Compaction Cycle Phase 0: Ready
Interactive 4-Phase Compaction: Observe how LFS reclaims fragmented space without taking the file system offline. Click "1. Phase 1: Selection" to begin!
Walkthrough ready. Segments 0 & 1 are fragmented candidate segments. Selected: None | Live Migrated: 0 | Reclaimed Segments: 0

4.3.6 Journaling File Systems & Write-Ahead Logging (WAL)

Traditional filesystems update structures directly in-place across scattered disk blocks. When a sudden power outage, kernel panic, or hardware disconnect occurs mid-write, the filesystem is caught midway through mutating interlinked records, producing severe metadata desynchronization.

Database Pioneers: Jim Gray & C. Mohan (Transaction Processing & WAL Theory)

Jim Gray
Jim Gray (James N. Gray) IBM, Tandem & Microsoft Research • Wikipedia Entry Turing Award Laureate (1998)
Pioneered transaction processing, database atomicity, locking, and crash-recovery protocols. His foundational work on System R and distributed transactions laid the blueprint for Write-Ahead Logging and reliable data-intensive computing.
Master innovator in database systems best known for co-authoring the ARIES recovery and concurrency control system. His rigorous protocols solved the theoretical challenges of Write-Ahead Logging and crash recovery used across modern databases and filesystems.
Historical Source Reference: Tony Hey, Stewart Tansley, Kristin Tolle (Eds.): The Fourth Paradigm: Data-Intensive Scientific Discovery. Microsoft Research, 2009.

To prevent lengthy multi-hour volume repair scans upon reboot (such as fsck), modern filesystems (including Linux ext3, ext4, XFS, and Windows NTFS) implement Write-Ahead Logging (WAL), universally referred to as Journaling.

1. The Crash Consistency Problem

A single high-level file modification rarely maps to a single physical disk write. Appending data to an existing file requires executing three distinct physical writes:

  1. Data Block Write: Writing user payload bytes into a newly allocated data block (D).
  2. Inode Metadata Update: Modifying the file's inode (I) to update file length, modification timestamps, and insert an address pointer referencing block D.
  3. Data Allocation Bitmap Update: Toggling the corresponding bit in the block allocation bitmap (B) from 0 (free) to 1 (allocated) to prevent other files from claiming block D.

Physical storage controllers guarantee atomic writes strictly at the granularity of a single physical sector (typically 512 bytes or 4 KB). Atomic writes across three completely separate disk blocks located thousands of sectors apart are physically impossible. If the system loses power between any of these operations, the filesystem enters an inconsistent failure state:

2. The Write-Ahead Logging (WAL) Protocol

Journaling solves this vulnerability by importing the Write-Ahead Logging (WAL) protocol from relational database engines. The governing invariant of Write-Ahead Logging is absolute:

The Fundamental WAL Rule: Never overwrite or update the permanent in-place filesystem structures on disk until a complete description of the impending changes has been sequentially logged, flushed to non-volatile media, and anchored by an explicit, atomic commit record.

Rather than writing directly to the permanent inode tables and allocation bitmaps, the operating system routes modifications through a dedicated, contiguous circular ring buffer called the Journal (or log).

The Anatomy of a Journal Transaction

Related updates are bundled together into an atomic container called a Transaction. A transaction proceeds through four chronological stages:

  1. Transaction Header (Tx Begin): A special descriptor block containing a unique monotonically increasing Transaction ID (TID), starting timestamp, and a manifest of the dirty metadata blocks scheduled for modification.
  2. Descriptor & Payload Blocks: The operating system writes exact in-memory copies of the updated inode (I), the updated allocation bitmap (B), and any modified directory blocks into the sequential journal stream.
  3. Write Barrier & The Commit Block: The controller issues a strict hardware write barrier (e.g., FLUSH CACHE or FUA – Force Unit Access) to ensure all payload blocks are physically seated on persistent media. Only then does it append the Commit Block. The commit block contains the matching Transaction ID and a cryptographic/CRC32 checksum of the entire transaction payload. The instant this commit block reaches disk, the transaction is durable.
  4. Checkpointing (In-Place Flushing): With the transaction safely recorded in the journal, the operating system writes the modified blocks out to their permanent locations across the disk (the real inode table, the real bitmap).
  5. Transaction Release (Journal Free): Once in-place checkpointing finishes, the circular journal marks that transaction's ring buffer sectors as reclaimed, advancing the journal head pointer.
Figure 4.3.6A: The Write-Ahead Logging (WAL) Architecture and Atomic Commit Boundary Traditional Non-Atomic Writes (Vulnerable) 1. Data ($D$) 2. Inode ($I$) ⚡ Power Cut! 3. Bitmap ($B$) Result: $I$ points to $D$, but $B$ marks $D$ as free! Causes cross-allocation, corrupt files, or space leaks. Write-Ahead Logging (WAL) Protocol (Crash-Proof) Tx Begin Payload ($I, B$) Commit Blk In-Place Write ↑ Atomic Commit Boundary (CRC Checksum) ↑ Crash before commit: Drop partial transaction cleanly. Crash after commit: Redo logging replays checkpoint.
Figure 4.3.6A: The WAL protocol isolates multi-block updates behind an atomic, checksummed commit boundary.
Walkthrough Part 3: Journaling Transaction & Crash Recovery Simulator State: Normal Operation
Interactive Crash Recovery: Step through a journaling transaction. You can commit normally or inject a sudden power cut midway through to see how Redo Logging or Torn Transaction Discard protects filesystem integrity.
Filesystem running in Ordered Journaling Mode. Ring buffer empty. Journal Ring: 0/8 Used | Checkpoint Status: Synchronized

4.3.7 Flash Storage & Wear-Leveling Systems

Flash memory cannot overwrite in place due to the erase-before-write constraint. The Flash Translation Layer (FTL) handles out-of-place writes, garbage collection, and dynamic/static wear leveling.

Walkthrough Part 4: FTL Page Remapping, Wear-Leveling & TRIM Simulator P/E Telemetry Active
Interactive FTL & Flash Memory Walkthrough: Test out-of-place writes, trigger an OS TRIM notification to invalidate dead records, and observe how Static Wear-Leveling moves cold data to preserve flash cells.
SSD Initialized. 4 Erase Blocks ready. Host Writes: 0 | Flash Writes: 0 | WAF: 1.00x

4.3.8 Virtual File Systems (VFS)

A contemporary Unix or Linux installation concurrently manages dozens of radically different storage targets: local journaling filesystems (ext4, XFS), legacy FAT volumes on USB drives (vfat), remote network shares (NFS, SMB), optical disc structures (ISO 9660), and volatile kernel introspection pseudo-filesystems (procfs, sysfs).

If user applications were required to invoke unique, driver-specific system calls for every storage target (e.g., ext4_read(), nfs_read(), fat_read()), application portability would collapse. To deliver a seamless, uniform programming model, modern operating systems implement the Virtual File System (VFS) abstraction layer. Originally pioneered by Sun Microsystems in 1985 to integrate NFS into SunOS, VFS provides an object-oriented polymorphic interface in C that decouples standard POSIX system calls from concrete storage implementations.

1. The Polymorphic VFS Architecture

The core design principle of VFS is polymorphism: user processes execute generic POSIX system calls (open(), read(), write(), close(), stat()), which route directly into the VFS layer. The VFS layer contains generic algorithms for path navigation, security validation, and buffer caching.

When an action requires interacting with concrete storage, VFS dispatches the call through function pointer tables registered by the specific filesystem driver mounting that path.

Figure 4.3.8A: VFS Polymorphic Dispatch Hierarchy Across Disparate Storage Media User Space Applications (POSIX System Calls: open, read, write, close, stat) System Call Interface Boundary (Trap to Kernel Mode) Virtual File System (VFS) Abstraction Layer Manages Dentry Cache (Dcache), Inode Table, Mount Hierarchy & Function Pointer Tables ext4 Driver Local NVMe / SSD Block VFAT Driver USB Removable Drive NFS Client Remote RPC Socket procfs Driver Volatile Kernel Memory
Figure 4.3.8A: VFS presents a single POSIX interface while routing operations through driver-specific function pointers.

2. The Four Primary VFS Data Objects

The Linux VFS architecture defines four core data structures that model every filesystem concept in memory:

1. The Superblock Object (struct super_block)

Represents an entire mounted filesystem instance. It holds filesystem-wide parameters, such as block size, device identifiers, mount flags, and a pointer to the root dentry. Crucially, it stores the s_op pointer pointing to Superblock Operations:

2. The Inode Object (struct inode)

Represents a specific file, directory, socket, or device node uniquely within a filesystem. Unlike disk inodes, a VFS inode exists entirely in memory and contains file size, owner UID/GID, permission bits, access/modification timestamps, and locks. It holds two sets of operation tables:

3. The Dentry Object (struct dentry)

In Unix, inodes contain metadata and block pointers, but no file names. File names exist solely as entries within directory payloads. To enable high-speed string path resolution, the VFS introduces Directory Entries (dentries).

A dentry represents a single path component (e.g., in /usr/bin/python3, there are dentries for /, usr, bin, and python3). Each dentry links a string name to its corresponding struct inode. Dentries are cached in the high-speed kernel Dentry Cache (Dcache).

4. The File Object (struct file)

Represents a dynamic, open file descriptor instantiated when a user space thread invokes open(). While an inode represents the static file on disk, a struct file represents an active interaction context. It stores:

Figure 4.3.8B: Interconnection Network of VFS Core Kernel Objects User Process task_struct fd table [fd 3] struct file f_pos = 4096 f_flags = O_RDONLY f_count = 1 → f_op: read, write struct dentry d_name = "syslog" d_parent = "log" (Cached in Dcache) → d_inode Pointer struct inode i_ino = 1048576 i_size = 2.4 MB → i_op: lookup, link • Linked to Superblock
Figure 4.3.8B: Relationship from process file descriptor down to physical inode and superblock.

3. Path Resolution & The Dentry Cache (Dcache)

Looking up a path such as /usr/bin/gcc requires stepping down directory structures. The VFS accelerates this through the in-memory Dentry Cache (Dcache):

  1. Hash Table Lookup: The kernel hashes (parent_dentry, "child_name") and queries a global hash table.
  2. Dcache Hit: If cached, VFS retrieves the associated struct inode locklessly via Read-Copy-Update (RCU).
  3. Dcache Miss: If not cached, VFS invokes the driver's inode->i_op->lookup() method, reading directory blocks from storage and inserting the new dentry into the cache.
  4. Mount Point Traversal: If a traversed dentry has the DCACHE_MOUNTED flag, VFS seamlessly redirects lookup to the root dentry of the mounted filesystem's superblock.
Walkthrough Part 5: VFS Dispatch, Path Walk & Mount Resolution Dcache Initialized
Interactive VFS Engine: Walk a file path, resolve mount boundaries (e.g. crossing into a USB FAT32 filesystem), and execute polymorphic function pointers to see how VFS dynamically routes calls.
VFS Layer active. Ready for path traversal. Active Driver: None | Dcache State: Cold