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
| Because | Consequence |
|---|---|
| Every persistent byte passes through it | A bug here loses data, which is the worst failure class there is |
| The page cache decides when data reaches the device | Which 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 owners | VFS, filesystem, block layer, driver — and a bug can be in any of them |
io_uring changed the syscall model | The 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 │
└────────────────────────────────────────────────────────────┘
| Area | Key files |
|---|---|
| VFS core | fs/open.c, fs/read_write.c, fs/namei.c (path lookup), fs/dcache.c, fs/inode.c |
| The generic I/O paths | mm/filemap.c, fs/iomap/ |
| Block | block/blk-core.c, block/blk-mq.c, block/bio.c, block/mq-deadline.c |
| io_uring | io_uring/ (a top-level directory since it outgrew fs/) |
| Device mapper / RAID | drivers/md/ |
| NVMe | drivers/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
_operationstables —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
| Chapter | Answers |
|---|---|
| The VFS | What is a file, and how does a path become one? |
| The Block Layer | How does a page become a device command? |
| Durability and Filesystems | When 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
| Change | Why it matters |
|---|---|
io_uring | A shared-memory ring interface: submit and complete without a syscall per I/O. Its own top-level directory, rapid development. |
iomap | The modern buffered-and-direct-I/O framework, replacing buffer_head for xfs, ext4, btrfs and others |
| Large folios in the page cache | Fewer, bigger units; a large ongoing conversion across filesystems |
| Zoned storage / ZNS | Devices where writes must be sequential within a zone |
blk-mq refinements | Polling, tag allocation, per-device schedulers |
fanotify/statx extensions | Newer 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:
| Target | Why it is plausible |
|---|---|
| Filesystem selftests | tools/testing/selftests/filesystems/ is thin, and xfstests (out of tree) always wants cases |
| A syzbot filesystem bug | Many 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 driver | drivers/md/, drivers/nvme/, and the smaller fs/ implementations |
A buffer_head → iomap conversion in a small filesystem | Wanted, mechanical-ish, and a real service |
| A block-layer tracepoint or a debugfs counter | Additive and low risk |
| Support for a device you own | Unarguable |
Tip:
syzbotis 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 atsyzkaller.appspot.com, filter by subsystem, and pick one with a reproducer and no assigned fix.
Common Misconceptions
| Misconception | Reality |
|---|---|
"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
- Name the four VFS
_operationstables and say what each is for. - What is the relationship between
struct file,struct inode, andstruct dentry? Which is per-open? - What is a negative dentry, and what does it make cheap?
- Draw the layers from
write()to the device, naming the file each lives in. - Where does
write()stop, and what completes the journey later? - What is a
bioand what is arequest? Where does one become the other? - Why does
fsync()on a newly created file not guarantee it survives a crash? - What does
O_DIRECTactually bypass, and what does it not? - Which single tool gives you a latency histogram for every block I/O, and what does the shape tell you?
- Why is storage the friendliest large subsystem for a first contribution? Give three specific entry points.
- What is
iomapreplacing, and why? - 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.