Unit 3 · OS & concurrency · Lesson 4 of 11
Files, page cache, storage and persistence
Builds on
Step 1: Learn the ideas
- Files, inodes, directories and file descriptors
- Open file descriptions shared through fork and dup
- User-space buffers versus the kernel page cache
- Writeback of dirty pages
- fsync, fdatasync, O_SYNC, O_DSYNC and O_DIRECT
- Atomic rename and directory fsync
- Crash consistency: fsck, journaling and copy-on-write file systems
- Device write caches and flush semantics
- Hard disks: seeks, rotation and sequential I/O
- Flash SSDs: FTL, garbage collection and wear leveling
- Memory-mapped files and msync
- Cold versus warm cache measurements
Why it matters
"The write call returned, so the data is safe" is one of the most common and most expensive misunderstandings in systems work. Databases, message queues, model checkpointing and anything that promises not to lose acknowledged data depend on knowing exactly which call moves data across which boundary. The same layers explain performance puzzles: a benchmark that reads a file twice measures memory the second time, and a service on network-attached storage can see fsync latencies far above a laptop's. Backend, infrastructure and database interviews ask what "durable" means and how to make an update crash-safe.
Core ideas
The path of a write. Data typically passes through a user-space buffer in the language runtime or stdio, then the kernel page cache, where write() copies it and marks the page dirty, then the device's volatile write cache, and finally persistent media. A successful write() only means the page-cache step happened. The kernel writes dirty pages back later, based on their age and on dirty-memory thresholds; a crash before that loses them.
What each call guarantees.
fflush(or a language's flush) moves data from the user buffer into the kernel and nothing more.fsync(fd)writes the file's dirty data and metadata to the device and, on Linux, is specified to write through or flush a volatile device cache before returning.fdatasync(fd)skips metadata that is not needed to read the data back, such as modification times.O_SYNCandO_DSYNCmake every write behave like a write followed by fsync or fdatasync.O_DIRECTbypasses the page cache, with alignment rules. It is a caching choice, not a durability guarantee; the man page says to combine it withO_SYNCwhen durability matters.
Durable, defined. Acknowledged data survives a crash or power loss. That requires every volatile layer above the media to be flushed or power-loss protected, and devices that honor flush requests. Durability also has a scope: surviving power loss differs from surviving the loss of the machine (replication) or silent corruption (checksums). Platforms differ: on macOS, plain fsync does not make the drive flush its cache; fcntl(F_FULLFSYNC) does.
Names are data too. Creating or renaming a file changes its directory, and fsync on the file does not necessarily persist the directory entry. The standard crash-safe replacement:
write new contents to tmp; fsync(tmp)
rename(tmp, target) # atomic switch of the name
fsync(directory of target) # make the rename itself durable
Crash consistency inside the file system. One logical update, such as appending a block, touches several on-disk structures: a bitmap, the inode and the data block. A crash between those writes leaves an inconsistent state. Older systems repaired it with a full scan (fsck); journaling file systems first write the intended changes to a log, the same write-ahead idea databases use; copy-on-write file systems never overwrite live data. File systems differ in the ordering they promise for application data, which is why portable applications fsync explicitly.
Errors are part of the contract. If background writeback fails, Linux may report the error once and leave the affected pages marked clean, so a retried fsync can succeed even though the data never reached storage. Robust systems treat an fsync failure as fatal and recover from their own log.
Devices. Hard disks pay milliseconds for seeks and rotation, so sequential I/O dominates their design. Flash SSDs read randomly well but write in pages and erase in much larger blocks; the flash translation layer remaps writes and garbage-collects, causing write amplification and latency spikes under sustained random writes. Cloud block volumes add a network round trip and replication, and enforce IOPS and throughput limits.
Common misconceptions
- "A successful write() means the data is on disk." It is in the page cache until writeback or fsync.
- "fsync on the new file makes its creation durable." The directory entry needs an fsync of the directory too.
- "O_DIRECT means durable." It bypasses the page cache; without O_SYNC or fsync, data can still sit in the device cache.
- "If fsync fails, retry it." On Linux the retry may succeed falsely after the data is gone.
- "fsync means the same on every platform." macOS needs
F_FULLFSYNCto flush the device cache. - "SSDs make random writes free." Random reads are cheap; sustained small random writes trigger garbage collection and tail-latency spikes.
How to approach it
Be able to draw the layers and label each call with the boundary it crosses. Practice the atomic-replace recipe from memory and explain the failure each step prevents. When discussing a storage engine, ask where its durability point is: exactly when a commit is acknowledged relative to which fsync. On Linux, time small appends with and without fsync, and run strace on a database commit to see which calls it actually makes.
Suggested order
- 1Read OSTEP Ch. 39 (Interlude: Files and Directories), especially 39.7 Writing Immediately With fsync() and 39.8 Renaming Files.
- 2Read OSTEP Ch. 40 (File System Implementation), focusing on the access paths and on caching and buffering (40.6–40.7).
- 3Read the LWN article Ensuring data reaches disk, then fsync(2) and the O_DIRECT notes in open(2).
- 4Read OSTEP Ch. 42 (Crash Consistency: FSCK and Journaling) and skim Ch. 44 (Flash-based SSDs) for the FTL and write amplification.
- 5Read Atomic Commit In SQLite for a real system’s durability assumptions, then the introduction and findings of Pillai et al. (OSDI 2014).
- 6On Linux, time appends with and without fsync, implement the atomic-replace recipe, and do the 6.1810 file system lab if you want kernel-level depth.
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
Operating Systems: Three Easy PiecesStart here
University of Wisconsin–Madison (free online book)Section: Ch. 39: Interlude: Files and Directories
File descriptors, fsync and rename semantics from the application’s point of view; the base for every durability question.
Open(opens in a new tab)Link responded, 5 Oct 2026Ensuring data reaches diskStart here
LWN.netThe clearest layer-by-layer account of which call moves data where, including directory fsync and O_DIRECT.
Open(opens in a new tab)Link responded, 5 Oct 2026The Linux man-pages project (man7.org)
man7.org (Linux man-pages project)Section: fsync(2)
Authoritative statement of what fsync and fdatasync promise on Linux, including the note about the containing directory.
Open(opens in a new tab)Link responded, 5 Oct 2026Atomic Commit In SQLite
SQLite documentationA production system’s step-by-step durability protocol and the OS and hardware assumptions it depends on.
Open(opens in a new tab)Link responded, 5 Oct 2026All File Systems Are Not Created Equal: On the Complexity of Crafting Crash-Consistent Applications
USENIXEvidence that file systems differ in crash behavior and that real applications mis-order updates; read the introduction and the application findings.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Do the durability experiments first, on Linux: (1) append 1,000 small records with and without an fsync after each and compare total time; (2) implement atomic replacement (temporary file, fsync, rename, directory fsync) and explain which crash each step defends against; (3) read a large file twice and compare times, then drop the page cache as root (write 3 to /proc/sys/vm/drop_caches) and read it again. Run strace -f on a small database or your own program to see where fsync happens. The xv6 file system and mmap labs add kernel-level depth.
- MIT 6.1810 Lab: file system (Fall 2026)Open(opens in a new tab)Not attemptedMIT 6.1810
- MIT 6.1810 Lab: mmap (Fall 2026)Open(opens in a new tab)Not attemptedMIT 6.1810
- MIT 6.1810 xv6 labs (system calls, page tables, traps, copy-on-write, locks, file system)Open(opens in a new tab)Not attemptedMIT 6.1810
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1What exactly does write() guarantee when it returns successfully?
- Question 2Why is rename() used for atomic file replacement, and why is it not enough on its own?
- Question 3What does durable mean for a single machine, and which further failures does replication address?
- Question 4Why is O_DIRECT not a durability guarantee?
- Question 5Why do sustained random writes on an SSD cause latency spikes?
- Question 6How would you make sure an I/O benchmark measures the device rather than the page cache?
You’re done when you can
- Draw the write path and state what fflush, fsync, fdatasync, O_SYNC and O_DIRECT each guarantee, without notes.
- Write the atomic replacement sequence from memory and explain the failure each step prevents.
- Explain why a retried fsync can report success after data loss on Linux.
- Explain journaling and what a crash at different points of a journaled update leaves behind.
- Measure and explain the cost difference between buffered appends and appends with fsync on your machine.
Finish the lesson
Unlocks next: I/O models, readiness and event loops, Write-ahead logging, recovery and durability, Indexes, B+ trees, LSM trees and Bloom filters


