Storage

write() returns in microseconds. The data is not on the disk. Understanding the machinery between those two facts — the VFS, the page cache, the block layer, the device driver, and the durability guarantees layered over all of them — is what this section is for.

Storage is the most approachable of the four large subsystems for a new contributor: the interfaces are well defined, bugs are usually reproducible, and there are a lot of drivers and filesystems, so there is a lot of small real work.


Orient Yourself First

cd ~/kernel/linux

./scripts/get_maintainer.pl --scm --status -f block/
./scripts/get_maintainer.pl --scm --status -f fs/
git log --oneline --since="6 months ago" -- block/ | head -20
git log --oneline --since="6 months ago" -- fs/ | wc -l

ls block/ | head -20
ls fs/ | head -40
ls drivers/nvme/host/
ls Documentation/filesystems/ Documentation/block/

Predict first: how many filesystems are in fs/? How many of them do you think are actively maintained? The gap is instructive, and MAINTAINERS S: lines will tell you.


Why It Matters

BecauseConsequence
Every persistent byte passes through itA bug here loses data, which is the worst failure class there is
The page cache decides when data reaches the deviceWhich is why fsync() exists and why crash consistency is hard
It is the boundary between "fast and volatile" and "slow and durable"Every design here is a latency/durability trade
It spans four layers with different ownersVFS, filesystem, block layer, driver — and a bug can be in any of them
io_uring changed the syscall modelThe first genuinely new I/O interface in decades

Where the Code Is

   ┌────────────────────────────────────────────────────────────┐
   │  SYSCALLS      read/write/openat/fsync/io_uring_enter      │
   ├────────────────────────────────────────────────────────────┤
   │  VFS           fs/*.c   the common layer: file, inode,     │
   │                         dentry, superblock, and the ops    │
   │                         tables every filesystem fills in   │
   ├────────────────────────────────────────────────────────────┤
   │  PAGE CACHE    mm/filemap.c  ── folios, dirty tracking,    │
   │                              readahead, writeback          │
   ├────────────────────────────────────────────────────────────┤
   │  FILESYSTEM    fs/ext4/  fs/xfs/  fs/btrfs/  fs/iomap/     │
   │                          extent mapping, journalling,      │
   │                          allocation policy                 │
   ├────────────────────────────────────────────────────────────┤
   │  BLOCK LAYER   block/    bio -> request -> blk-mq queues,  │
   │                          I/O schedulers, merging           │
   ├────────────────────────────────────────────────────────────┤
   │  DRIVER        drivers/nvme/  drivers/scsi/  drivers/md/   │
   ├────────────────────────────────────────────────────────────┤
   │  HARDWARE                                                  │
   └────────────────────────────────────────────────────────────┘
AreaKey files
VFS corefs/open.c, fs/read_write.c, fs/namei.c (path lookup), fs/dcache.c, fs/inode.c
The generic I/O pathsmm/filemap.c, fs/iomap/
Blockblock/blk-core.c, block/blk-mq.c, block/bio.c, block/mq-deadline.c
io_uringio_uring/ (a top-level directory since it outgrew fs/)
Device mapper / RAIDdrivers/md/
NVMedrivers/nvme/host/pci.c, core.c
$EDITOR Documentation/filesystems/vfs.rst          # the canonical VFS document
$EDITOR Documentation/filesystems/path-lookup.rst  # genuinely excellent
$EDITOR Documentation/block/blk-mq.rst

The Structures

   struct file                  AN OPEN FILE DESCRIPTION
     ├── f_op ──▶ file_operations   read_iter, write_iter, mmap, fsync...
     ├── f_inode ──▶ struct inode
     ├── f_pos, f_flags, f_mode
     └── private_data               the driver's or fs's per-open state

   struct inode                 A FILE, ON DISK
     ├── i_op  ──▶ inode_operations   lookup, create, unlink, rename...
     ├── i_fop ──▶ file_operations    what to install into a new struct file
     ├── i_mapping ──▶ address_space  ← THE PAGE CACHE for this file
     ├── i_sb ──▶ struct super_block
     └── i_size, i_mode, i_uid, i_ino

   struct dentry                A NAME, IN A DIRECTORY
     ├── d_name, d_parent, d_inode
     └── d_op ──▶ dentry_operations
     The DCACHE. A "negative" dentry records that a name does NOT exist,
     which is how repeated failed lookups stay cheap.

   struct super_block           A MOUNTED FILESYSTEM
     └── s_op ──▶ super_operations   alloc_inode, write_inode, sync_fs...

   struct bio                   AN I/O REQUEST, IN FLIGHT
     ├── bi_iter                 sector, size, position
     ├── bi_io_vec[]             (page, offset, len) segments
     ├── bi_bdev                 the target device
     ├── bi_opf                  READ/WRITE | flags (FUA, PREFLUSH, SYNC)
     └── bi_end_io               the completion callback
rg -n "struct file_operations \{" -A 40 include/linux/fs.h
rg -n "struct inode_operations \{" -A 30 include/linux/fs.h
rg -n "struct address_space_operations \{" -A 40 include/linux/fs.h
rg -n "struct bio \{" -A 40 include/linux/blk_types.h
rg -n "struct request \{" -A 40 include/linux/blk-mq.h

Tip: Four _operations tables — file, inode, address_space, super — are the VFS. A filesystem is a thing that fills them in. If you can say what each of the four is for, you can read any filesystem in the tree.


The Concepts

ChapterAnswers
The VFSWhat is a file, and how does a path become one?
The Block LayerHow does a page become a device command?
Durability and FilesystemsWhen is data actually safe, and what does a filesystem promise?

Then Lab 12 follows one write() from the syscall to the device and back.


How to Read It

 1. Documentation/filesystems/vfs.rst -- what each ops table is for.

 2. The four structures above, drawn.

 3. ONE PATH: read().
      fs/read_write.c: vfs_read -> f_op->read_iter
      mm/filemap.c:    filemap_read -> the page cache lookup
      the miss path:   a_ops->read_folio -> the filesystem -> a bio

 4. THE SECOND PATH: the same for write(), and notice where it STOPS --
      the data is dirty in the page cache and the syscall returns.
      Then find writeback, separately, and see what completes it.

 5. block/blk-mq.c: submit_bio -> request -> the hardware queue.

 6. Only then a filesystem. fs/ext2/ is the smallest real one and the best
    to read first; fs/ext4/ and fs/xfs/ are production-grade and large.

Observing It

# What is happening, right now
iostat -x 1 3 2>/dev/null || vmstat 1 3
cat /proc/diskstats | head
grep -E 'Dirty|Writeback' /proc/meminfo
cat /proc/pressure/io 2>/dev/null

# Tracepoints -- the block ones are excellent
ls /sys/kernel/tracing/events/block/
ls /sys/kernel/tracing/events/writeback/
ls /sys/kernel/tracing/events/ext4/ 2>/dev/null | head

# Latency of every block I/O, as a histogram
sudo bpftrace -e '
  tracepoint:block:block_rq_issue    { @s[args.dev, args.sector] = nsecs; }
  tracepoint:block:block_rq_complete /@s[args.dev, args.sector]/ {
      @us = hist((nsecs - @s[args.dev, args.sector]) / 1000);
      delete(@s[args.dev, args.sector]); }'

# Who is doing I/O
sudo bpftrace -e 'tracepoint:block:block_rq_issue { @[comm, args.rwbs] = count(); }'

# The full trace of a device
sudo blktrace -d /dev/nvme0n1 -o - | blkparse -i -    # if available

What Is Moving

git log --oneline --since="6 months ago" -- block/ io_uring/ fs/iomap/ | head -30
ChangeWhy it matters
io_uringA shared-memory ring interface: submit and complete without a syscall per I/O. Its own top-level directory, rapid development.
iomapThe modern buffered-and-direct-I/O framework, replacing buffer_head for xfs, ext4, btrfs and others
Large folios in the page cacheFewer, bigger units; a large ongoing conversion across filesystems
Zoned storage / ZNSDevices where writes must be sequential within a zone
blk-mq refinementsPolling, tag allocation, per-device schedulers
fanotify/statx extensionsNewer uapi at the VFS layer

What a Good First Contribution Looks Like

Storage is the friendliest of the big subsystems, and these are genuinely achievable:

TargetWhy it is plausible
Filesystem selfteststools/testing/selftests/filesystems/ is thin, and xfstests (out of tree) always wants cases
A syzbot filesystem bugMany are open, they come with reproducers, and they are usually well-scoped
Documentation in Documentation/filesystems/Large and unevenly maintained
Error-path fixes in a driverdrivers/md/, drivers/nvme/, and the smaller fs/ implementations
A buffer_head → iomap conversion in a small filesystemWanted, mechanical-ish, and a real service
A block-layer tracepoint or a debugfs counterAdditive and low risk
Support for a device you ownUnarguable

Tip: syzbot is unusually productive here. Filesystem fuzzing finds a steady stream of bugs in the less-travelled filesystems, they come with C reproducers, and many go unclaimed for months. Start at syzkaller.appspot.com, filter by subsystem, and pick one with a reproducer and no assigned fix.


Common Misconceptions

MisconceptionReality
"write() returning means the data is safe"It is dirty in the page cache. fsync() is the contract, and it is expensive for a reason.
"O_DIRECT bypasses everything"It bypasses the page cache. It does not bypass the block layer, the scheduler, or the device's own cache.
"fsync() guarantees the file is durable"It guarantees that file's data and metadata. A newly created file also needs its directory fsynced.
"The I/O scheduler reorders my writes"It merges and orders requests; the filesystem and page cache have already reordered far more
"One write() is one I/O"It may be zero (page cache), or many (readahead, journal, metadata)
"Direct I/O is faster"Often slower, because you gave up caching and readahead. Measure.
"A filesystem is about on-disk layout"It is mostly about ordering: what must reach the device before what, so a crash is recoverable
"The dcache caches file contents"It caches names. Contents are the page cache.

Validation / Self-check

  1. Name the four VFS _operations tables and say what each is for.
  2. What is the relationship between struct file, struct inode, and struct dentry? Which is per-open?
  3. What is a negative dentry, and what does it make cheap?
  4. Draw the layers from write() to the device, naming the file each lives in.
  5. Where does write() stop, and what completes the journey later?
  6. What is a bio and what is a request? Where does one become the other?
  7. Why does fsync() on a newly created file not guarantee it survives a crash?
  8. What does O_DIRECT actually bypass, and what does it not?
  9. Which single tool gives you a latency histogram for every block I/O, and what does the shape tell you?
  10. Why is storage the friendliest large subsystem for a first contribution? Give three specific entry points.
  11. What is iomap replacing, and why?
  12. Name three things a filesystem is responsible for that are about ordering rather than layout.

Next: The VFS — what a file is, and how a path becomes one.