Chapter 11: VFS Integration and Kernel Implementation

Updated

October 31, 2026

Chapter 11: VFS Integration and Kernel Implementation

Filesystems do not exist in isolation. Inside the Linux kernel, ext4 operates as a concrete plugin beneath the Virtual Filesystem (VFS) layer. When an application calls open(), read(), write(), or fsync(), the request traverses generic VFS interfaces before invoking ext4-specific functions.

Understanding the interaction between ext4, the VFS, the Linux page cache, and the kernel’s memory management subsystem is essential for diagnosing high-load performance bottlenecks and concurrency lock contention.

This chapter explores ext4’s kernel implementation: the VFS operation function tables, page cache dirty page writeback loops, the multi-tier locking hierarchy, and concurrent multi-threaded block allocation.

The super_operations and inode_operations Tables

The VFS abstracts filesystem operations using object-oriented C structures containing function pointers. ext4 implements these interfaces in fs/ext4/super.c, fs/ext4/inode.c, fs/ext4/file.c, and fs/ext4/namei.c:

┌────────────────────────────────────────────────────────────────────────┐
│                        VFS FUNCTION TABLE DISPATCH                     │
│                                                                        │
│   VFS Interface Object          ext4 Concrete Implementation           │
│   ───────────────────────────   ────────────────────────────────────── │
│   struct super_operations       ext4_sops (alloc_inode, sync_fs)       │
│   struct inode_operations       ext4_file_inode_operations (setattr)   │
│   struct inode_operations (Dir) ext4_dir_inode_operations (lookup)    │
│   struct file_operations        ext4_file_operations (write_iter)     │
│   struct address_space_oper     ext4_aops (read_folio, writepages)     │
└────────────────────────────────────────────────────────────────────────┘

1. Superblock Operations (struct super_operations ext4_sops)

Governs filesystem-wide lifecycle and inode memory allocation: - alloc_inode: Allocates an in-memory ext4_inode_info structure from the dedicated ext4_inode_cachep SLAB cache. - destroy_inode: Returns the inode memory to the SLAB cache upon deletion. - write_inode: Synchronizes an in-memory inode to its corresponding JBD2 transaction. - sync_fs: Commits running journal transactions during a system-wide sync. - statfs: Computes free blocks, free inodes, and capacity statistics for df.

2. Inode Operations (struct inode_operations ext4_dir_inode_operations)

Handles namespace directory manipulation: - lookup: Searches an HTree directory for a filename and attaches the resulting inode to the VFS dentry cache. - create, mkdir, mknod: Allocates a new inode via the Orlov allocator and inserts the directory record. - unlink, rmdir: Decrements link counts, removes directory entries, and enqueues deleted open files into the orphan list. - rename: Moves directory entries atomically using JBD2 transactions.

3. File Operations (struct file_operations ext4_file_operations)

Handles file descriptor execution: - read_iter: Executes vectorized reads via the page cache or direct I/O (ext4_dio_read_iter). - write_iter: Coordinates buffered writes or asynchronous direct writes (ext4_dio_write_iter). - mmap: Maps file data blocks into process virtual address space. - fsync: Commits dirty page cache buffers and flushes JBD2 journal transactions to disk.

Page Cache Interaction and Writeback

Standard Linux buffered writes do not touch storage devices immediately:

┌────────────────────────────────────────────────────────────────────────┐
│                        EXT4 PAGE CACHE WRITEBACK LOOP                  │
│                                                                        │
│   1. Application write(fd, buf, len)                                   │
│          │                                                             │
│          ▼ VFS copy_from_user()                                        │
│   2. Dirty Pages allocated in RAM (Page Cache Folios)                  │
│      Buffers tagged with BH_Dirty, inode marked I_DIRTY_PAGES          │
│          │                                                             │
│          ▼ Asynchronous background window (e.g. 5–30 seconds)          │
│   3. Kernel kworker flusher thread invokes ext4_writepages()           │
│          │                                                             │
│          ▼ Delayed Allocation Resolution:                              │
│   4. mballoc allocates contiguous physical extents on disk             │
│          │                                                             │
│          ▼ DMA Block Layer Submission:                                 │
│   5. BIO requests submitted to blk-mq queue -> NVMe Controller         │
└────────────────────────────────────────────────────────────────────────┘

ext4 implements ext4_writepages() as its workhorse function. When the kernel memory manager initiates writeback, ext4_writepages() scans the dirty page radix tree, groups contiguous dirty pages into unified I/O requests, invokes the multi-block allocator to resolve delayed allocations (delalloc), and issues physical Block I/O (bio) structs to the block storage driver.

Filesystem Locking: Per-file Locks, Group Locks, mballoc Locks

To guarantee thread safety across hundreds of concurrent CPU cores, ext4 uses a hierarchical locking model:

┌────────────────────────────────────────────────────────────────────────┐
│                        EXT4 LOCKING HIERARCHY                          │
│                                                                        │
│   Level 1: VFS Inode RW Semaphore (i_rwsem)                            │
│   └─ Serializes concurrent writes to the same file. Reader/Writer lock.│
│                                                                        │
│   Level 2: ext4 Inode Data Lock (EXT4_I(inode)->i_data_sem)            │
│   └─ Protects the extent tree during extent splitting and allocation.  │
│                                                                        │
│   Level 3: Block Group Spinlock (ext4_group_lock(sb, group))           │
│   └─ Protects individual block group descriptors and allocation bitmaps│
│                                                                        │
│   Level 4: Multi-Block Allocator Mutex (s_mb_alloc_mutex)              │
│   └─ Coordinates buddy bitmap allocation and preallocation pools.      │
│                                                                        │
│   Level 5: JBD2 Journal Transaction Locks                              │
│   └─ Protects transaction handle lifecycle (j_state_lock).             │
└────────────────────────────────────────────────────────────────────────┘

To prevent deadlocks, ext4 strictly enforces lock acquisition order: outer VFS locks must always be acquired before inner ext4 extent locks, which must precede block group spinlocks.

Concurrency: Multi-threaded Allocation and Journaling

On modern servers with 128+ CPU cores, global filesystem locks can cause catastrophic performance degradation. ext4 employs several optimizations to maximize parallelism:

  1. Fine-Grained Group Locking: Instead of a global allocation lock, mballoc locks only the specific block group currently being searched. Threads writing to different directories allocate blocks in different block groups simultaneously without contention.
  2. Asynchronous Transaction Handles: Threads initiating metadata operations invoke ext4_journal_start(), which increments a transaction reference counter and returns immediately. Thousands of concurrent threads attach their operations to the same running JBD2 transaction concurrently.

Dirty Page Management and Flush Threads

The frequency with which ext4 writes dirty memory back to disk is tuned via global kernel virtual memory sysctls:

# Fraction of total system memory that can hold dirty pages before background flusher wakes
sudo sysctl vm.dirty_background_ratio=10

# Hard ceiling: processes attempting write() are throttled into synchronous flushing
sudo sysctl vm.dirty_ratio=20

# Time dirty pages can linger in memory before forced flush (in hundredths of a second)
sudo sysctl vm.dirty_expire_centisecs=3000   # 30 seconds