File Systems
open and read to fsync, rename, links and permissions. See how a simple file system lays out inodes, bitmaps and data blocks, how the Fast File System keeps related data close together, and how fsck and journaling keep the disk consistent after a crash. Finish with log-structured file systems and with how NFS and AFS share files across a network.IntermediateLesson 8 of 9 in Operating Systems Course
What you will learn
- Use the system calls a program relies on to create, read, write, name, protect and organize files, and predict what each call does to offsets, names and data on disk.
- Read and write files through file descriptorsOpen files, read and write them through file descriptors, move the file offset, and explain how processes share open files.
- Make writes durable and updates atomicForce data to stable storage when it must survive a crash, and replace a file's contents so that readers see either the old or the new version.
- Organize names with directories and linksCreate, list and remove directories, and explain how hard links, symbolic links and deleting a file affect names and inodes.
- Read metadata, control access and mount file systemsRead a file's metadata with stat, set and interpret permission bits, and explain how separate file systems are joined into one directory tree.
- Read and write files through file descriptors
- Describe the on-disk structures of a simple Unix-style file system, compute where an inode and a file's blocks live, and trace the disk reads and writes behind opening, reading and writing a file.
- Lay out a disk in blocks, bitmaps, inodes and directoriesDivide a disk into the superblock, allocation bitmaps, inode table and data region, locate an inode by its number, and describe how directories and free space are stored.
- Locate a file's data with pointers, indirect blocks and extentsExplain how an inode's direct pointers, indirect blocks and extents map a file's offsets to disk blocks, and compute the largest file a pointer layout supports.
- Trace the disk I/O of file operations and the effect of cachingCount the block reads and writes behind opening, reading, creating and writing a file, and explain how caching and write buffering change that traffic.
- Lay out a disk in blocks, bitmaps, inodes and directories
- Diagnose why early Unix file systems were slow on hard disks, and explain how the Fast File System's groups, placement rules and large-file policy keep related data close together.
- Explain why early Unix file systems were slow and how groups fix itIdentify the layout problems that wasted disk bandwidth in the original Unix file system, and describe how cylinder groups and block groups organize the disk.
- Apply FFS placement rules and size large-file chunksUse FFS's placement heuristics for directories and files, apply the large-file exception, compute how large a chunk must be to amortize positioning, and describe FFS's other improvements.
- Explain why early Unix file systems were slow and how groups fix it
- Explain why a crash in the middle of an update can corrupt a file system, and compare repairing it afterwards with fsck, preventing damage with journaling, and other approaches.
- Explain the crash-consistency problemShow why one file operation needs several block writes, list what a crash between them can leave on disk, and classify each outcome.
- Check and repair a file system with fsckDescribe what the file system checker examines and repairs after a crash, and explain why scanning the whole disk scales poorly and cannot catch every problem.
- Protect updates with data journaling and replay the logWrite an update to a journal before changing the file system in place, explain why the commit block is written last, and recover after a crash by replaying committed transactions.
- Reduce journaling cost and compare other approachesExplain how metadata journaling avoids writing data twice, why ordered journaling writes data before the commit, how revoke records prevent a block-reuse bug, and how other designs keep file systems consistent.
- Explain the crash-consistency problem
- Explain why writing everything sequentially to a log speeds up writes, how the inode map and checkpoint region let the file system find data in that log, and how cleaning and crash recovery work.
- Write sequentially in segments and find inodes through the inode mapExplain the motivation for a log-structured file system, size a segment to reach a target bandwidth, and trace how the inode map and checkpoint region locate a file.
- Reclaim space with the cleaner and recover after a crashExplain how LFS finds and compacts live blocks, chooses which segments to clean, recovers after a crash, and how its ideas live on in copy-on-write and flash file systems.
- Write sequentially in segments and find inodes through the inode map
- Explain how NFS and AFS let many client machines use files stored on servers, how they handle crashes, and how their caching choices decide when one client sees another's changes.
- Explain how NFS recovers from failures with stateless requestsDescribe the client/server structure of NFS, how file handles and idempotent requests let a client recover from server crashes by retrying, and why the server must commit writes before replying.
- Compare NFS and AFS caching and consistencyExplain how NFS and AFS cache file data on clients, when each makes one client's changes visible to others, and which workloads favor each design.
- Explain how NFS recovers from failures with stateless requests
File Systems study guide
1Use the file and directory interface
File operations expose several different kinds of state: bytes, names, open positions and permissions. Follow a descriptor through reads and shared offsets, then distinguish a completed call from a durable save. Directories and links explain why deleting a name need not delete the object. Metadata and mounting complete the picture of how a program reaches and uses a file through one directory tree.
1.1 Read and write files through file descriptors
A file is a persistent sequence of bytes. The file system stores those bytes without deciding whether they mean text, an image or machine code. A directory gives the file a readable name; its inode number identifies it within the file system.
Open a file and check the result
open(path, flags, mode) returns a file descriptor, a nonnegative integer used in later calls. An error returns -1 and sets errno. Choose O_RDONLY, O_WRONLY or O_RDWR for access. O_CREAT creates a missing file, O_TRUNC empties an existing file, and O_APPEND places each write at the current end.
For a new file, mode requests permissions and the process's umask removes bits. Without a default directory access control list (ACL), the result is mode & ~umask. Requesting 0666 with umask 0022 gives 0644, or rw-r--r--. This argument does not change the permissions of an already existing file.
Descriptors belong to a process. Normally 0, 1 and 2 are standard input, output and error. open chooses the lowest unused descriptor, often 3. On Linux, strace cat notes.txt exposes the calls: opening the input, reading it, writing to descriptor 1, then closing the input.
Follow the offset, not the requested count
read(fd, buf, n) returns the bytes actually read, up to n. For a nonzero request on a regular file, zero means no bytes remain at or beyond end of file; -1 means an error. write also returns an actual count, which can be short. Check that count before advancing a buffer or deciding the operation is complete.
Suppose log.txt contains 300 bytes. Four requests for 100 bytes return 100, 100, 100 and 0. The offset, the position of the next operation, becomes 100, 200, 300 and 300. End of file is a returned count, not a special byte stored in the file.
lseek(fd, 200, SEEK_SET) sets the offset to 200. Reading 50 bytes then returns bytes 200–249 and leaves offset 250. SEEK_CUR measures from the current offset; SEEK_END measures from the file's end. Seeking changes a number in memory. It does not move a disk head or itself read data. pread and pwrite use an explicit position without changing this offset.
Decide which descriptors share an offset
The process's descriptor table points to entries in the kernel's open file table. Each open entry holds the access mode, offset, inode reference and reference count.
| How descriptors arise | Offset behavior |
|---|---|
Two separate open calls | Independent offsets, even for the same inode |
dup or dup2 | One shared open entry and offset |
Descriptors inherited through fork | Parent and child share the open entry and offset |
If a child seeks a shared descriptor to byte 10, the parent's next read through that entry starts at 10 too. The inode identifies the file, but the open entry holds the offset: two readers can share a file without sharing their position. Shell redirection uses dup2 to make standard output refer to another open entry. close releases one descriptor; the entry disappears after its last reference is released. Closing alone does not force buffered data to stable storage.
Key Takeaways
- Check the returned byte count from every
readandwrite. - An offset belongs to an open file table entry, not to the inode.
- Separate opens have separate offsets; duplicates and inherited descriptors share one.
lseekchanges the offset without performing disk I/O.closereleases a descriptor; it does not guarantee durability.
1.2 Make writes durable and updates atomic
A successful write and a crash-safe update answer different questions. A write reports that bytes were accepted. Durability asks whether they survive a crash; atomicity asks whether an update can be observed only as a whole.
Force the bytes to stable storage
Operating systems usually buffer writes in memory and send dirty blocks to storage later, often within roughly 5–30 seconds. This combines small writes and lets the program continue quickly. A crash can erase data still waiting in those buffers.
fsync(fd) forces the file's dirty data and metadata to storage and waits for the device to report completion. A program must check its result before reporting a durable save. close releases the descriptor and is not a substitute for this wait.
A file also needs a durable name. The name-to-inode entry is data belonging to its directory, not to the file. After creating a file, synchronizing only the file can leave its bytes durable without guaranteeing that its new name survives. Open the containing directory and synchronize it when the name must persist.
Replace a file without exposing a partial version
Overwriting a document in place creates an interval in which readers can encounter incomplete contents. O_TRUNC first sets its length to zero. If a crash interrupts the later writes, the old complete copy is already gone.
Instead, write the replacement under a temporary name in the same directory. Then use rename to replace the destination. For an existing destination on the same file system, POSIX makes that replacement atomic: the destination name refers to the old file or the replacement, without an intermediate missing name. Readers do not see a mixture of their contents through that name.
For settings.json, the durable replacement sequence is:
- Write all new contents to
settings.json.tmp, checking for short writes and errors. - Call
fsyncon the temporary file and check success. - Call
rename("settings.json.tmp", "settings.json")and check success. - Call
fsyncon the containing directory and check success.
The temporary file is complete and durable before the name changes. The final directory synchronization makes that name change durable. A crash during the sequence leaves the destination holding a complete old or complete new version; after the final successful synchronization, the new version is the durable one. Atomic name replacement alone does not promise that an unsynchronized rename survives power loss.
rename across file systems fails with EXDEV. A command that responds by copying and deleting performs several operations, so that fallback has no atomic-replacement guarantee.
Change length without writing every byte
truncate(path, length) and ftruncate(fd, length) set the file's length. Shrinking discards bytes beyond the new end. Extending makes the new region read as zeros. Seeking beyond the end and then writing also creates a zero-reading gap.
A sparse file can represent such a gap without allocating data blocks for it. Starting with an empty file, writing one byte at offset 8192 makes a file 8193 bytes long; the preceding unwritten region reads as zeros. Its logical size can exceed the space physically allocated to it.
Key Takeaways
fsyncmakes file data and metadata durable after successful completion.- Synchronize the directory to make a new or replaced name durable.
- Write, synchronize, rename, then synchronize the directory for a durable replacement.
- A sparse gap reads as zeros and can occupy no data blocks.
1.3 Organize names with directories and links
Directories separate the names people use from the objects the file system stores. Each directory has its own inode and data blocks; its entries map names to inode numbers.
Follow paths through the tree
An absolute path starts at /. A relative path starts at the process's current working directory. The entry . names the directory itself and .. names its parent; the root's parent is the root. From /home/lee, the relative path ../shared reaches /home/shared.
An extension such as .txt is part of a name. Programs can use it to choose how to interpret the bytes, but the file system does not require a .txt file to contain text.
mkdir creates a directory containing . and ... To list entries, a program uses opendir, calls readdir until no entries remain, and calls closedir. A readdir result includes a name and inode number. Programs change directory contents through calls such as link, unlink and rename, not ordinary write calls. rmdir removes an empty directory.
Give one file two equal names
link(old, new) creates a hard link: another directory entry pointing to the same inode. Neither name is privileged as the original. The inode's link count records how many names refer to it.
Consider these commands when no process holds the file open:
echo hi > a
ln a b
rm a
cat b
rm b
Creating b raises the link count from 1 to 2. Removing a lowers it to 1; b still reaches the same bytes, so cat b prints "hi". Removing b drops the count to 0, allowing the inode and its blocks to be reclaimed.
An open descriptor adds a second lifetime rule. If the final name is unlinked while a process still has the file open, that process can keep accessing it. Reclamation waits for both zero links and no open references. This explains why deleting a large open log may not immediately release its space.
A hard link cannot cross file systems: the destination's inode number would have no meaning in the other inode table. Most Unix systems also prohibit hard links to directories to preserve a tree without cycles. In classic Unix directory accounting, an empty directory has two links, from its parent entry and its own .. Each subdirectory contributes one more through ..; three subdirectories give a count of 5.
Store a path in a symbolic link
A symbolic link, or soft link, is a separate file whose contents are a target path. ln -s /home/ana/report.txt r creates r with the 20-byte target path as its contents. Following it resolves that path. Symbolic links can cross file systems and point to directories.
| Link kind | What the directory entry reaches | Effect of removing the target name |
|---|---|---|
| Hard link | The shared inode | Another hard link still reaches the file |
| Symbolic link | A separate link object containing a path | The link dangles if its stored path no longer resolves |
A symbolic link does not increase its target's link count. Keeping the link object therefore does not keep the target alive.
Key Takeaways
- Directories map names to inode numbers; paths select a sequence of those names.
- Hard links are equal names for one inode.
- Unlinking removes a name; reclamation also waits for open references to end.
- Symbolic links store paths and can dangle.
- Hard links stay within one file system.
1.4 Read metadata, control access and mount file systems
A file's contents are only part of its state. Metadata records who may use it, its size and type, and when it changed. Mounting makes objects from several file systems reachable through a single tree of names.
Read the metadata correctly
stat(path, &buf) reads metadata through a path; fstat(fd, &buf) reads it through an open descriptor. The results include the inode number, type, permissions, owner and group IDs, size, allocated blocks and link count. File size counts logical bytes; allocated blocks measure storage actually used, which can be smaller for a sparse file.
Three timestamps describe different events:
| Field | Meaning | Example event |
|---|---|---|
| atime | Last access | Reading contents |
| mtime | Last content modification | Writing bytes |
| ctime | Last inode status change | Writing, changing permissions or adding a hard link |
ctime is not creation time. Updating atime alone does not change ctime. Classic Unix stat has no creation-time field; Linux statx can expose a recorded birth time when the file system supports it.
Decode permission bits
Read, write and execute rights are recorded separately for owner, group and others. Each class is an octal digit: read is 4, write 2 and execute 1. For rw-r-----, the owner gets 4 + 2 = 6, the group gets 4, and others get 0. chmod 640 report.txt sets those bits.
Directory permissions apply to names and traversal. Read allows listing names. Execute allows search, meaning use of the directory in a path. Write together with execute allows creating, deleting and renaming entries. Knowing a name can be enough to open a file in a directory with execute but no read permission, provided the file's own permissions allow access. Listing that directory still fails.
This difference matters for deletion: removing an entry changes its containing directory. A file's write bit controls changing its bytes; it is not the directory's permission to remove its name. Root bypasses most ordinary permission checks. Access control lists (ACLs) can grant rights to named users and groups beyond the three basic classes.
Attach another file system
mkfs initializes a file system on a partition or other device, including its own root directory and metadata. It destroys the device's previous file-system contents, so run it only on a device intended for initialization.
mount attaches that root at an existing directory called the mount point. A drive mounted at /media/usb makes its root contents accessible there. The previous contents of /media/usb are hidden while the mount is active; mounting does not merge them with the mounted tree. Running mount without arguments lists current mounts.
The kernel's virtual file system (VFS) layer gives ext4, XFS, FAT, memory-based and network file systems a common interface. Programs can call open and read through any of them. The combined namespace does not combine their inode tables: equal inode numbers on different file systems can identify unrelated files. Hard links and atomic renames therefore stay within their own file system.
Key Takeaways
statandfstatdescribe metadata; ctime records status changes.- Permission digits add read = 4, write = 2 and execute = 1 for each user class.
- Directory execute permission allows path traversal; read permission allows listing.
- Mounting attaches a file system root and hides the previous mount-point contents.
- The VFS shares an interface across file system types, not one global inode table.
- Logical file size and allocated space can differ.
2Explain how a simple file system stores and finds data
A readable path hides a sequence of lookups, allocations and block accesses. Build a small disk layout, calculate an inode address, and map byte offsets through direct pointers, indirect blocks or extents. Then trace the I/O behind common calls. Keeping cache contents explicit lets you explain why the same program can cause many device operations on one run and very few on the next.
2.1 Lay out a disk in blocks, bitmaps, inodes and directories
A file system must remember both where bytes live and which space is available. A simple Unix-style layout makes these jobs visible by separating allocation records, inodes and contents.
Divide the partition into regions
A block is the file system's basic allocation unit. A 4 KiB block contains 4096 bytes, or eight traditional 512-byte sectors. Binary sizes use KiB, MiB and GiB; transfer rates such as MB/s use decimal millions of bytes per second.
The teaching design vsfs, the very simple file system, contains these structures:
| Structure | Information stored |
|---|---|
| Superblock | File system size, region locations, inode count and identifying magic number |
| Inode bitmap | One allocation bit per inode |
| Data bitmap | One allocation bit per data block |
| Inode table | Metadata and data pointers for every inode slot |
| Data region | Contents of regular files and directories |
A bitmap bit of 1 means allocated; 0 means free. The superblock tells the operating system how to interpret the rest of the partition when mounting it. The two bitmaps describe different resources: a file can have an allocated inode before it has any data blocks.
Locate an inode by number
Take a 64-block partition with 4 KiB blocks. Put the superblock at block 0, the inode bitmap at 1, the data bitmap at 2, and the inode table in blocks 3–7. Data occupies blocks 8–63. Five inode-table blocks with 256-byte inodes hold 80 inodes, because each block holds 16. There are 56 data blocks. One 4 KiB bitmap can describe 32768 objects, so much of each bitmap is unused in this tiny layout.
Numbering inode slots from zero, inode 37 starts 37 × 256 = 9472 bytes after the table begins. The table starts at byte 12288, giving absolute byte address 21760. Dividing by 4096 gives disk block 5 with remainder 1280. Dividing by 512 instead gives sector 42 with remainder 256. Read the containing block or sector, then select the inode's bytes within it.
The general method is: table start in bytes plus inode number times inode size. Keep the table's starting address separate from the inode's offset within the table. Omitting that base address locates the wrong disk block.
Separate names, metadata and contents
An inode, or index node, records type, permissions, ownership, size, times, link count, allocated blocks and data locations. It does not contain the filename. The name belongs in a directory entry, allowing several names to share one inode.
A directory has an inode of type directory and data blocks of entries. A simple entry stores an inode number, record length, name length and name. Record length lets traversal skip the whole entry, including reusable space left by deletion. Large directories benefit from indexes: XFS uses B-trees and ext4 uses hashed tree indexes.
Creating a file finds a free inode bitmap bit and sets it. Allocating data similarly changes the data bitmap. An allocator may reserve a run of blocks so the file can grow contiguously. The bitmap says whether a block is free; the inode says which file owns it. These structures must agree.
Key Takeaways
- The superblock describes the layout; bitmaps track allocation.
- An inode contains metadata and data locations, while directories contain names.
- Compute an inode address from the table base, inode number and inode size.
- Inode allocation and data allocation use separate bitmaps.
2.2 Locate a file's data with pointers, indirect blocks and extents
A program sees consecutive byte offsets, but the corresponding data blocks can be scattered across the disk. The file system needs a mapping from each logical file block to its physical location.
Follow direct and indirect pointers
A direct pointer in an inode names one data block. With 4 KiB blocks, byte offset 10000 lies in logical block 2, because two full blocks use 8192 bytes. The offset within that block is 1808. Logical blocks start at 0, so block 2 uses the third direct pointer.
An indirect pointer names a block containing data-block addresses. A 4096-byte block with 4-byte addresses holds 1024 pointers. A double indirect pointer names a block whose pointers lead to 1024 indirect blocks. Triple indirection adds another level. This multi-level index is deliberately unbalanced: small files use direct pointers without extra pointer-block reads, while large files pay for additional levels.
For an inode with 12 direct pointers and one pointer of each indirect level:
| Highest level used | Data capacity with 4 KiB blocks |
|---|---|
| Direct only | 12 blocks = 48 KiB |
| Single indirect | 12 + 1024 blocks = 4144 KiB |
| Double indirect | 12 + 1024 + 1024² blocks = 4299210752 bytes |
| Triple indirect | Adds 1024³ blocks = another 4 TiB |
These are capacities of this pointer layout. Pointer blocks occupy storage too, but their bytes are not file contents.
Count pointer blocks for a file
A 5 MiB file occupies 1280 data blocks at 4 KiB each. Assign the first 12 to direct pointers. Assign the next 1024 to the single indirect block. That leaves 244 data blocks.
Those 244 need a double indirect root and one indirect block beneath it. In total, the file needs three pointer blocks: the single indirect block, the double indirect root and its one child. Counting only the double indirect root misses the child that actually contains data addresses. Counting all 1024 possible children wastes space: only the required child is allocated.
Compare extents and chains
An extent records a starting disk block and a run length. A contiguous run of 1000 blocks can use one extent instead of 1000 separate pointers. Ext4, XFS and NTFS use extents. Ext4 can hold up to four extents in its inode, with a tree for more. Fragmentation splits runs and increases the number of extent records needed.
Linked allocation follows a chain of blocks. The File Allocation Table (FAT) stores the next-block links in a separate table, usually cached in memory. A directory entry identifies the first block, and reaching a late block requires following the earlier links. FAT does not use Unix inodes and does not support two hard links to one file.
| Mapping | Main strength | Main cost |
|---|---|---|
| Direct and indirect pointers | Direct access to small files; scalable indexing | Extra pointer blocks for large files |
| Extents | Compact descriptions of contiguous runs | Many records when fragmented |
| FAT chains | Simple linked allocation with cached links | Chain traversal for access near the end |
Consecutive offsets never prove physical contiguity. The allocator chooses locations, and the mapping records those choices.
Key Takeaways
- Divide a byte offset by block size to find its zero-based logical block.
- An indirect block holds block addresses; each extra level multiplies reach.
- Count allocated pointer blocks separately from data blocks.
- Extents describe contiguous runs compactly.
- FAT follows next-block links instead of a Unix inode index.
- Logical adjacency does not guarantee physical adjacency.
2.3 Trace the disk I/O of file operations and the effect of caching
One system call can require several disk operations, or none. To trace the work, follow the structures the file system must consult and state which of them are already cached.
Walk a path before reading contents
In the simple uncached model, opening /docs/plan.txt reads five blocks:
- The root inode, whose number is known.
- Root directory data, to find
docs. - The
docsinode. docsdirectory data, to findplan.txt.- The file's inode.
Each directory fits in one block, and no needed block begins in memory. The file system checks permissions and creates an open file entry. Opening has found the file's metadata; it has not yet read its contents. Root inode number 2 is used in ext2 and ext4.
For a deliberately uncached reading model, each block read then reads the file inode, reads the data, and writes the inode's access time. Two blocks add six I/Os, giving eleven including open. Closing needs no disk I/O in this model. These counts describe the model's explicit rereading and timestamp rules; a real cache changes them.
Account for allocation during creation
Creating /docs/log.txt needs more than finding an existing inode. In the same simple model, it performs four reads for the root and docs inodes and directory data. Reading and writing the inode bitmap adds two I/Os. Updating docs data adds one write. Reading and writing the new inode adds two, and updating the docs inode adds one. Total: ten.
Each newly allocated data block adds five I/Os: read and write the data bitmap, read and write the file inode, and write the data block. Two blocks therefore give twenty I/Os including creation. This trace counts the simple data structures before adding journaling traffic or real cache behavior.
Keeping a ledger helps: list the structure, whether it is read or written, and why. A data write also needs an allocation record and a pointer update. A directory change also changes that directory's metadata.
Let the cache change the trace
A cache hit uses a block already in memory. Linux uses a unified page cache shared with virtual memory, not a permanently separate fixed-size file cache. A repeated open can reuse the directory and inode blocks from the previous traversal. Reading cached contents needs no device read. Linux's usual relatime policy also avoids an atime write on every read.
Write buffering delays sending dirty blocks to disk. Two nearby file creations may update the same inode bitmap in memory and later write it once. The file system can reorder work for better throughput. A short-lived temporary file may be created and removed before its contents ever reach storage.
Buffering improves performance but leaves a durability window. fsync closes that window for a file by waiting for its required writes. Linux O_DIRECT lets suitable programs bypass the page cache when they manage their own buffers; bypassing a cache is not itself the same operation as synchronizing durable storage.
Key Takeaways
- Trace path traversal separately from reading file data.
- I/O counts depend on cache contents and timestamp policy.
- Creating a file updates allocation records, directory entries and inodes.
- Write buffering combines updates and improves scheduling but delays durability.
- A system call does not correspond to a fixed number of device operations.
3Explain how the Fast File System uses locality for speed
A fast interface needs a layout that fits the storage device. Early Unix file systems wasted hard-disk bandwidth on small transfers and distant metadata. FFS groups related objects and balances that locality against the needs of large files. Its placement rules and chunk-size calculation show how a design can improve performance while programs continue to use familiar operations.
3.1 Explain why early Unix file systems were slow and how groups fix it
A hard disk can transfer a long run of bytes quickly once its head reaches them. Small scattered requests waste time seeking and waiting for rotation. The Fast File System changes placement so ordinary file operations use more of the disk's transfer bandwidth.
Diagnose the old Unix layout
The early Unix layout placed all inodes near the start and all file data after them. Reading a file could therefore require a long seek from metadata to contents. A free list gradually scattered allocations as files were created and deleted. Even a sequential file could become many unrelated disk accesses.
Small 512-byte blocks made this worse. Each positioning delay bought only a short transfer before the next request needed another seek or rotation. Historical measurements found the old design delivering as little as 2% of the disk's bandwidth. That figure describes the measured early design, not a fixed limit of Unix interfaces.
The Fast File System (FFS), developed at Berkeley in the early 1980s, kept familiar calls such as open, read and write while changing the layout and allocation policy. Larger blocks, typically 4 KiB, increased the bytes transferred per request. Keeping metadata and contents close reduced the distance between related requests.
Build groups with their own resources
A cylinder is the set of tracks at the same radius across a disk's platter surfaces. FFS forms cylinder groups from neighboring cylinders. A file can have its inode and data in the same group, keeping a metadata-to-data seek short.
Each group contains a superblock copy, its own inode and data allocation bitmaps, part of the inode table, and data blocks. The group resembles a small file system, but all groups belong to one file system. A superblock copy also helps recovery if one copy is damaged. Ext4 keeps copies in selected groups instead of every group.
Modern drives hide their physical geometry. Ext2, ext3 and ext4 therefore use block groups, runs of consecutive logical block addresses, to pursue the same locality goal without relying on exact cylinders.
Trace a small file creation
Creating /docs/a.txt needs a free inode, a data block, allocation-map changes, and a new directory entry. FFS tries to place the file in its parent directory's group. The new inode, file data, bitmaps and directory updates can then lie nearby.
The operation still needs several distinct updates. Grouping saves positioning time; it does not collapse all metadata and data into one block. A disk-aware layout improves the cost of reaching those structures without changing the program's file operations.
| Storage feature | Consequence for placement |
|---|---|
| Hard-disk seek and rotation | Nearby blocks avoid repeated long positioning delays |
| High sequential transfer rate | Larger requests move more bytes per delay |
| SSD without seek or rotation | Distance matters much less, but request size and write behavior still matter |
Key Takeaways
- Long seeks, fragmentation and small blocks limited early Unix disk throughput.
- FFS groups place inodes, allocation maps and data near each other.
- Modern block groups preserve locality without needing physical disk geometry.
- FFS improves placement while retaining the existing file interface.
3.2 Apply FFS placement rules and size large-file chunks
FFS uses names as clues about which files a program will access together. It also limits how much space one large file can occupy in a group, balancing locality with room for future allocations.
Place directories and files
A new directory goes into a group with relatively few directories and many free inodes. This spreads different directories while leaving room for their future files. A regular file's inode goes in its parent directory's group, and its data goes near its inode.
For /a/b, /a/c and /a/d, the three files are placed near directory /a. A file /b/f follows its own parent /b, usually into another group. This exploits namespace locality: a compiler working on one project often opens several files in the same directory. The directory relation predicts access patterns; it is not a guarantee about every program.
Make an exception for a large file
One large file could consume its group's free blocks and force all later neighbors elsewhere. FFS keeps the first portion, reached by 12 direct pointers, in the inode's group. With 4 KiB blocks that is 48 KiB. Later chunks, each reached by an indirect block holding 1024 pointers, cover 4 MiB of data and go into other lightly used groups.
This sacrifices some locality within the large file to preserve locality for the directory's other files. The large file pays a positioning delay between chunks, so each chunk must be large enough to make that cost worthwhile.
Derive the chunk size
Let positioning take T seconds and transfer rate be R bytes per second. A chunk C takes C/R seconds to transfer. The fraction of total time spent transferring is:
Solving gives:
At 100 MB/s with 10 ms positioning, R times T is 1 MB. A 1 MB chunk needs 10 ms transfer plus 10 ms positioning, giving 50% of peak bandwidth. For 90%, multiply 1 MB by 0.9/0.1 to obtain 9 MB. For 99%, the multiplier is 99, giving 99 MB. Convert milliseconds to seconds before multiplying by MB/s. The denominator includes both transfer and positioning time; dividing only by transfer time would hide the overhead.
Transfer rates have improved faster than positioning delays, so achieving the same fraction of peak requires larger chunks as the gap widens.
Fit small files and preserve compatibility
FFS also used 512-byte sub-blocks so small files did not waste a whole 4 KiB block. Growing files were copied into full blocks; the C library buffered writes in 4 KiB pieces to reduce repeated movement. Historical parameterization left rotational gaps between consecutive blocks so a requested next block had not already passed the head. Modern track buffers make that technique unnecessary by fetching a track internally.
Long names, symbolic links and atomic rename were additional FFS capabilities. The layout speedup itself did not require changing existing programs.
Key Takeaways
- FFS uses a directory as a clue that its files will be accessed together.
- Directories are spread across groups; their regular files are kept nearby.
- Large-file chunks spread out to leave space for neighboring files.
- Include positioning and transfer time when computing effective bandwidth.
- Sub-blocks reduce small-file space waste; track buffering replaces rotational-gap tuning.
- FFS added capabilities as well as accelerating existing calls.
4Keep a file system consistent across crashes
A crash can interrupt one logical file operation between several physical writes. Compare the states left by an unfinished append, then examine how a checker repairs metadata and how a journal makes recovery depend on recorded transactions. Data ordering and block reuse reveal why a valid commit needs more than a marker. Other consistency designs solve the same problem with different costs.
4.1 Explain the crash-consistency problem
One append looks like one action to a program. On disk it changes several structures, and a crash can leave only some of them updated. Crash consistency asks how those partial results can be prevented or repaired.
Track three independent writes
Appending one 4 KiB block to an existing file changes:
- Db, the new data block containing the appended bytes.
- I, the inode with a larger size and a pointer to that block.
- B, the data bitmap marking the block allocated.
All three reaching storage gives the intended consistent update. None reaching storage leaves the old consistent state. The danger lies in the six partial combinations. The device can reorder requests, so issuing them in a particular order without waiting does not establish completion order.
Classify each partial outcome
| Writes that survive | Result |
|---|---|
| Db only | New bytes exist, but no pointer reaches them; the old file system state remains consistent |
| I only | Inode points to old contents and the bitmap still calls the block free |
| B only | An allocated block has no owner: a space leak |
| I and B | Metadata agrees, but the new file region contains old contents |
| I and Db | File points to valid new data that the bitmap still calls free |
| B and Db | New bytes are allocated but unreachable: lost data and leaked space |
An inconsistency occurs when metadata disagrees, such as an inode claiming a block whose allocation bit is free. The allocator might then give that same block to another file. A space leak reserves storage no file can reach. Garbage data can be harder to detect: I and B agree perfectly even though Db never arrived.
Db alone does not leak allocated space, because the bitmap still permits that block to be reused. Writing bytes to a free location is not the same as allocating it. B alone does leak space, because allocation survived without an owning pointer.
Separate structure from meaning
A checker can compare pointers with bitmaps. It cannot generally tell whether plausible bytes are the bytes a program meant to save. The I-and-B outcome demonstrates this limit: a structurally valid file can have incorrect contents.
The desired transition is atomic across the update: recover to the complete old state or the complete new state. A simple storage model gives atomic writes only for one sector, traditionally 512 bytes. Several file-system blocks exceed that unit, so the file system must construct a stronger protocol. Real device atomic-write guarantees depend on the device and interface; the journal protocol must fit its commit record within the supported atomic unit.
Buffering increases the number of changes that can be lost, but it is not the root cause. Even immediately issuing each write leaves a crash boundary between separate completions. File creation, rename and deletion also change multiple structures. Understanding their failure states begins with the same exercise: identify every block changed and ask what happens when only a subset survives.
Key Takeaways
- One logical file operation can require several independently completed block writes.
- Compare data, inode pointers and allocation bits after each possible crash point.
- Consistent metadata can still point to incorrect file contents.
- Multi-block atomicity needs a file-system protocol beyond a single atomic device write.
4.2 Check and repair a file system with fsck
A file system checker repairs relationships among stored structures after damage has occurred. Its strength is a broad view of metadata; its weaknesses are the cost of scanning and its lack of knowledge about intended file contents.
Reconstruct the allocation picture
fsck, short for file system check, examines an unclean file system before normal mounting. It checks whether the superblock's size and counts make sense and can use a backup superblock if the main copy is damaged.
It then scans inodes and indirect blocks to determine which data blocks files actually reference. From that ownership information it rebuilds the allocation bitmaps. If an otherwise valid inode points to block 74 while the bitmap calls 74 free, the bitmap must change to allocated. Merely trusting the bitmap could allow a later allocation to overwrite the file's data.
The opposite mismatch is a bitmap bit marked allocated without an owning inode pointer. Reconstructing the map can reclaim that space. Repair chooses a consistent relationship; it cannot reconstruct arbitrary missing application bytes.
Check objects, names and links
Every allocated inode needs a valid type, such as regular file, directory or symbolic link. An inode that cannot be repaired is cleared and the allocation records adjusted.
Walking from the root lets fsck count directory references to each inode. It compares these observed references with recorded link counts and corrects errors. An allocated inode that no directory reaches may be moved into lost+found, giving recovered content a reachable name without knowing its original path.
Other checks include:
- Duplicate ownership: two inodes claim one data block. A repair can clear one inode or give each file a separate copy.
- Invalid addresses: pointers outside the file system's valid range must be cleared.
- Directory structure:
.and..must be valid and occupy the first entries in the simple directory layout, entries must refer to allocated inodes, and directories must not have multiple parents.
These checks interact. Clearing an invalid inode also changes which blocks should be allocated and which directory entries remain valid. A repair therefore needs a coherent view of the metadata, not just a list of suspicious individual bits.
Understand what a clean result establishes
Suppose an append's inode and bitmap reached disk but its data did not. The pointer is in range, the allocation bit is set, and the link structure is valid. fsck has no metadata disagreement to repair. The file can pass checking while holding old bytes in its newly added region.
The running time also depends mainly on the file system's size, not the amount changed before a crash. A crash during one three-block update can still require scanning all metadata. On a large file system this can take minutes to hours, keeping normal access unavailable during the scan.
Journaling reduces normal crash-recovery work by recording recent updates. It does not make a checker obsolete: software bugs or failing hardware can damage structures outside what journal replay can repair. fsck remains a repair tool for those cases.
Key Takeaways
- fsck reconstructs allocation from inode pointers and corrects metadata relationships.
- Directory traversal checks link counts and finds unreachable allocated inodes.
- Checking can repair duplicate ownership and invalid pointers.
- A clean metadata check cannot establish that application bytes are correct.
- Full metadata scanning scales with file-system size, even after a small interrupted update.
4.3 Protect updates with data journaling and replay the log
A journal records an update before the file system overwrites its ordinary locations. After a crash, that record tells recovery which complete changes can be redone. This is write-ahead logging. Ext3, ext4, XFS and NTFS use journaling, although their exact log formats differ.
Describe an update as a transaction
For an append, data journaling logs copies of the new inode I, bitmap B and data block Db. A transaction-begin record, TxB, carries an ID and the blocks' final destinations. A transaction-end record, TxE, carries the matching ID and marks commitment.
Logging complete replacement block images is physical logging. Logging an operation description instead is logical logging. Physical redo has a useful property: writing the same image to the same address again has the same effect. Recovery can therefore repeat a checkpoint that partly finished before the crash.
Put commitment after the payload
The basic protocol has four stages:
- Journal write: write TxB and I, B and Db to the journal, and wait for durable completion.
- Commit: write TxE, and wait. The transaction is now committed.
- Checkpoint: copy I, B and Db from the journal to their final locations.
- Free: release that transaction's journal space for later reuse.
TxE must not become durable before its payload. If all five blocks were issued without an ordering guarantee, a device could complete TxB and TxE while leaving Db unwritten. Recovery would mistake an incomplete transaction for a valid one. In the basic model, TxE fits within one atomic sector write, so it is either present or absent as a complete commit record.
Device write caches must respect the durability boundary too. Cache flushes or forced writes prevent a volatile device cache from making the protocol believe a payload is safe when power loss could still erase it.
Recover from two crash points
A crash after the payload but before TxE leaves an uncommitted transaction. Recovery ignores it, keeping the old file state.
A crash after TxE but during checkpointing leaves a committed transaction. Recovery writes all its images to their final locations in order. Some writes may repeat work already done, which is harmless for these block images. This is redo recovery. Journal space cannot be reused until the committed images have reached their final locations.
The journal is a circular log. Its superblock tracks the active range, allowing completed space to be reused. Batching several updates into one transaction can log one shared directory block or bitmap once. Ext4's default commit interval is 5 seconds, so commitment is not automatically immediate after every application write.
Separate checksums from durability
A transaction checksum can reveal missing or torn payload blocks even when a commit record is present. A checksum-based protocol can issue journal writes together and accept only transactions whose validation succeeds. Checksums detect incomplete records; cache flushes establish whether acknowledged bytes really survive.
An application's buffered, uncommitted write can still disappear. A program requiring durable completion uses fsync and checks success.
Key Takeaways
- Write-ahead logging records complete updates before changing their final locations.
- In the basic protocol, payload durability precedes the commit record.
- Recovery redoes committed transactions and ignores uncommitted ones.
- Redo of identical block images is safe after a partial checkpoint.
- Checkpointing must finish before journal space is reused.
- Batching reduces metadata traffic, while
fsyncestablishes needed durability.
4.4 Reduce journaling cost and compare other approaches
Data journaling writes file contents to the journal and then to their final locations. Journaling only metadata reduces that traffic, but the order between data and metadata still determines what recovery can expose.
Keep data ahead of committed pointers
In ordered metadata journaling, Db goes directly to its final location. TxB, I and B go to the journal. These two sets of writes may run in parallel, but both must finish before TxE commits the metadata. After commitment, I and B can be checkpointed and the journal space freed.
The dependency is the key: the block a pointer names must be ready before that pointer becomes recoverable. If I commits first, recovery may install a valid pointer to bytes left by an earlier user of the block. Metadata would agree without holding the intended data.
| Ext4 mode | What enters the journal | Data-before-commit rule |
|---|---|---|
data=journal | Data and metadata | Data is part of the logged transaction |
data=ordered | Metadata | Referenced data reaches its final place before commit |
data=writeback | Metadata | No such data ordering guarantee |
Ordered mode is the default. Metadata journaling alone does not imply ordered mode: writeback mode can recover structurally sound files with stale contents. The data-journal mode illustrates the full protocol but costs a second write of the file data.
Prevent replay from damaging a reused block
Directory contents count as metadata. Suppose a directory block at address 1000 is logged, then the directory is removed and the same address becomes ordinary file data. That new user data is not logged in metadata-only mode. Replaying the old directory image after a crash would overwrite the new file's bytes.
A committed revoke record tells recovery to skip logged copies of that block from transactions up to and including the revoking transaction. It does not forbid every future use of the address. If a later transaction logs that block again, the later image can still be replayed. This time boundary distinguishes revocation from permanently marking a block unusable.
Compare alternatives
Soft updates enforce dependencies between ordinary writes without a journal. Writing initialized data before a pointer prevents reachable garbage, although interrupted operations can leak space for a later background check to reclaim. FreeBSD’s FFS uses this approach. Tracking all dependencies makes implementation difficult.
Copy-on-write (COW) writes changed blocks to unused locations and uses an atomic root-pointer switch to publish the new tree. The old tree remains available until the switch. ZFS, Btrfs and WAFL use related designs; preserving old blocks also enables snapshots.
Backpointer-based research designs record ownership from data back to inodes so incomplete updates leave detectable mismatches. Optimistic crash consistency reduces waiting and uses checksums and other checks to detect incomplete work. Each approach trades write traffic, recovery guarantees and implementation complexity. Ordered metadata journaling keeps recovery short while writing ordinary file data once.
Key Takeaways
- Ordered journaling sends data to final locations before committing its metadata pointers.
- Metadata-only journaling and ordered journaling are not synonyms.
- Revoke records prevent obsolete logged images from overwriting reused blocks.
- Soft updates order dependencies; copy-on-write publishes a new tree.
- Less write traffic can require more careful ordering or validation.
5Explain how a log-structured file system writes and reclaims space
Sequential writes make good use of a disk, but moving every updated block creates new lookup and space-management problems. Follow LFS from buffered segments through the inode map and checkpoint region, then see how the cleaner finds live blocks and restores free segments. Recovery must recover the newest valid mapping without mistaking an interrupted checkpoint for a complete one.
5.1 Write sequentially in segments and find inodes through the inode map
A log-structured file system turns scattered small updates into large sequential writes. This helps a disk whose transfer rate is high but whose seek and rotation delays remain costly.
Gather updates into a segment
Berkeley's LFS, developed by Rosenblum and Ousterhout and published in 1991, started from changing workloads. Larger memories served more reads from cache, making writes a greater share of disk traffic. Sequential bandwidth grew faster than positioning speed. FFS still needed several short metadata and data writes for a small file creation, and small writes also worked poorly with parity RAID.
LFS buffers changed data, inodes and mapping information together in memory. It writes them to an unused disk region as one large segment. Updating a block creates a new version at a new address; the previous version remains physically present until cleaning reclaims its segment.
Segments amortize positioning just as large FFS chunks do. With rate R, positioning time T and desired transfer-time fraction F, the buffered size is:
At 200 MB/s and 5 ms, 95% requires 19 MB. Transfer takes 95 ms, positioning takes 5 ms, and the effective rate is 19 MB per 0.1 s = 190 MB/s. A high target fraction can require a segment larger than the usual hundreds of kilobytes to several megabytes. The required size follows the device and target, not the word "segment."
Find an inode that keeps moving
The fixed inode-table formula no longer works: every inode update writes a new address. LFS adds an inode map, or imap, mapping each inode number to its newest disk address. Updated imap pieces are written into the log beside the inodes they describe.
A fixed checkpoint region (CR) records where the newest imap pieces are. It is updated periodically, for example every 30 seconds, so the occasional fixed-location write does not dominate traffic.
The lookup chain is:
- Read the CR at mount to find the current imap pieces.
- Load the imap into memory.
- Look up the requested inode number in the imap.
- Read that newest inode and follow its data pointers.
Once the imap is cached, ordinary file reads use much the same inode-to-data path as in FFS. The extra mapping makes movement affordable without changing the file's stable identity.
Keep directory entries stable
A directory stores names and inode numbers. If /docs/report.txt is updated, its inode moves but keeps its number. The imap changes; the directory entry need not change merely because of that movement.
Without this indirection, a directory containing physical inode addresses would need rewriting whenever a child inode moved. Its own inode would then move, forcing a change in its parent, continuing toward the root. The imap breaks this recursive update problem.
LFS makes writes sequential, not all reads. A file's unchanged blocks can remain in older segments while later versions of other blocks move elsewhere. Reading that file may still seek between segments; large memory caches are part of the design's motivation.
Key Takeaways
- LFS writes buffered data and metadata together to unused space in large segments.
- The imap maps stable inode numbers to moving inode addresses.
- The fixed checkpoint region locates the newest imap pieces.
- Directory entries keep inode numbers, avoiding recursive parent rewrites.
- Segment size must amortize positioning; reads can still be scattered.
5.2 Reclaim space with the cleaner and recover after a crash
Writing new versions to unused locations keeps old bytes physically present. LFS needs to distinguish old unreachable versions from live blocks, reclaim large free regions, and recover a usable mapping after a crash.
Clean whole segments
The cleaner reads partly occupied segments, copies their live blocks into fewer new segments, and frees the old segments. If four equal-size segments each have one quarter live data, their combined live data fills one new segment. Freeing the four old segments after the copy gains three segments of usable capacity.
Cleaning whole segments preserves large contiguous regions for future sequential writes. Copying has a cost: live blocks are read and written again even though no application changed them. A nearly full disk with mostly live segments may require much copying to recover little free space.
Decide whether a block is live
A segment summary identifies each block's owner inode and logical file-block offset. To test a block at address A, use the imap to find that owner's newest inode. Follow its pointer for the recorded logical block. If the pointer still equals A, this is the live version. If it points elsewhere, A is obsolete.
For example, a summary associates address 600 with inode 14, logical block 2. The current inode 14 points logical block 2 to address 920. Block 600 is dead even if its bytes look like valid data. Liveness depends on current reachability, not how recently a block was written or whether its contents look useful.
Version numbers speed up rejection. Deleting a file or truncating it to zero advances its version in the imap. A segment block carrying an older version can be classified dead without reading the inode.
Cleaning policy also considers expected change. Hot segments contain frequently overwritten blocks; waiting can let more of their blocks die without copying. Cold segments change little, so the original LFS policy cleans them sooner. Policy must balance live fraction, age and available space; a segment's temperature alone does not measure the immediate copying cost.
Recover the mapping and recent work
LFS keeps two checkpoint regions at opposite ends of the disk and alternates updates. Each update writes a timestamped header, the body, and a final matching timestamp. Recovery chooses the newest region whose beginning and end match. An interrupted newer update does not displace an older complete checkpoint.
The checkpoint may not include the most recent completed segments. Recovery therefore rolls forward from its recorded log end, finding valid subsequent updates and applying them to rebuild the current mapping. Data that never formed a valid recoverable update cannot be recreated from memory after power loss.
Related copy-on-write systems preserve old blocks to support snapshots; ZFS, Btrfs and WAFL share that broad idea. SSD controllers commonly use log-structured flash translation layers, while F2FS applies log-structured ideas at the file-system level for flash storage. Each still needs a way to reclaim space left by obsolete versions.
Key Takeaways
- The cleaner copies live blocks into fewer segments and frees the old segments.
- Liveness follows the newest inode mapping, not a block’s apparent contents.
- Version mismatches can identify dead blocks quickly.
- Waiting to clean hot segments can avoid copying blocks that soon become obsolete.
- Recovery selects a complete checkpoint and rolls forward through valid later updates.
- Cleaning adds I/O and can become expensive when little free space remains.
6Compare how distributed file systems share files over a network
Sharing files across machines introduces uncertain replies and cached copies that may disagree. NFS shows how self-contained requests and safe retries simplify one kind of failure recovery. AFS shows how whole-file caching and callbacks reduce server work. Compare their sharing boundaries and failure state to predict when another client sees a change and when concurrent updates can be lost.
6.1 Explain how NFS recovers from failures with stateless requests
A distributed file system keeps files on a server while programs on client machines use familiar file calls. The client translates those calls into network requests. Delays and failures introduce a question local calls usually hide: what should a client do when it cannot tell whether a request completed?
Keep open-file state on the client
Sun's Network File System (NFS) began in the 1980s. NFSv2 made its server stateless with respect to open files. The server stores files but does not remember each client's descriptors or current offsets. Every request carries enough information to execute without an earlier open request.
A file handle identifies the server volume, inode number and inode generation. The generation changes when an inode number is reused, preventing an old handle from accidentally naming a different file in that reused slot.
To open /home/ana/notes.txt, the client starts with a root handle and sends LOOKUP for home, ana and notes.txt. It retains the resulting handle in its own open-file state. Reading the first 4 KiB becomes a READ request containing that handle, offset 0 and count 4096. Closing the local descriptor needs no NFSv2 CLOSE request because the server has no open entry to release.
Retry without repeating an effect
If no reply arrives, the client cannot distinguish a lost request, an unavailable server or a lost reply. It resends the request after a timeout.
An operation is idempotent when repeating it has the same effect as performing it once. READ, LOOKUP and GETATTR do not modify stored contents. An explicit-offset WRITE also safely repeats the same bytes at the same location in this model. Repeating a request that says "append these bytes at the current end" would instead add a second copy; the offset in an NFS WRITE avoids that ambiguity.
Some requests need extra care. Retrying a successful MKDIR after its reply was lost can return "already exists." The directory is present, but the repeated response differs from the original success. Stateless recovery does not turn every operation into a simple repeatable request.
Make acknowledgments trustworthy
NFSv2 must make a WRITE durable before acknowledging success. Otherwise the server could acknowledge a block, lose it from memory during a crash, and then accept later blocks. The client would believe all writes succeeded while the file contained a missing middle update.
Stable replies avoid that failure at a performance cost. NFSv3 added unstable writes followed by COMMIT, allowing several writes to become durable together. The client must distinguish an unstable acknowledgment from durable completion.
Separate versions of the protocol
A stateful protocol needs machinery to recover open and locking state after a server restart and reclaim state from crashed clients. NFSv4 deliberately accepts that complexity: it has OPEN, CLOSE, locks and time-limited leases that clients renew. The Linux client normally tries version 4.2 first, falling back when necessary. Calling NFSv2 stateless therefore does not describe every NFS version.
Key Takeaways
- NFSv2 keeps descriptors and offsets on clients; each request is self-contained.
- File handles include a generation to reject stale identities after inode reuse.
- Explicit offsets make repeating the same WRITE safe in the retry model.
- NFSv2 acknowledges writes only after stable storage; NFSv3 can defer durability to COMMIT.
- NFSv4 is stateful and uses recovery machinery and leases.
6.2 Compare NFS and AFS caching and consistency
Client caching saves network trips but creates multiple copies of shared files. The consistency rule determines when another machine must stop using an old copy and fetch the writer's changes.
Validate NFS caches at sharing boundaries
Under NFS close-to-open consistency, closing a modified file flushes buffered writes to the server. A later open on another client checks freshness and sees those changes. GETATTR or related validation compares server attributes with cached attributes, discarding stale cached data when a change is detected.
Checking on every access would burden the server. Linux caches regular-file attributes for default intervals ranging from 3 to 60 seconds. A process that keeps a file open can therefore read stale cached data between checks. Opening is a stronger sharing boundary than an arbitrary read through an already open descriptor. Close-to-open behavior does not promise immediate visibility of every individual write.
Reuse whole files with AFS callbacks
The Andrew File System (AFS) was designed at Carnegie Mellon in the 1980s to support many clients per server. When no valid local copy exists, opening fetches the whole file to local disk. Reads and writes then use that copy, and closing a modified file sends the whole new version to the server. A valid cached copy can be reopened without downloading it again.
AFSv1 checked validity on every open and left path traversal to the server. AFSv2 shifted traversal to clients, which cache directories, and introduced callbacks. The server promises to notify a client when a cached object changes. Until that promise is broken, the client reuses its copy without repeated checks. Its file identifier includes a volume, a file identifier and a uniquifier.
Follow two overlapping sessions
Suppose clients A and B both open a file. A changes its local copy and closes, sending the new version to the server. The server breaks other clients' callbacks. A later open on B fetches the new version, but B's already open session keeps its existing copy until close and reopen.
If both machines modify their copies, the last one to close replaces the server's whole file. Blocks from the two copies are not merged. Two edits to different offsets can still lose one client's changes because last closer wins chooses a complete version. Processes on one client machine see each other's writes immediately.
Callbacks are server state. After a client restart, cached files are suspect because notifications may have been missed. After a server restart, clients must detect the lost callback promises, through heartbeats or a server notice, and revalidate.
Match caching to the workload
| Workload or issue | NFS | AFS |
|---|---|---|
| Small part of a large file | Transfers requested blocks | Whole-file transfer can waste work |
| Repeated large-file reads beyond memory capacity | Depends on available cached blocks | Local disk copy avoids more network downloads |
| Freshness tracking | Attribute checks | Server callbacks |
| Concurrent sharing | Needs coordination | Needs coordination; last closer can overwrite another version |
AFS also offered a global namespace, access control lists and administrator-managed volumes. AFSv2 historically increased server scale to about 50 clients from roughly 20 for AFSv1. Neither design's cache rule replaces explicit coordination, such as file locks, when programs need safe shared updates.
Key Takeaways
- NFS uses close-to-open validation while attribute caching can leave open readers stale.
- AFS reuses a whole-file disk cache while a callback remains valid.
- AFS publishes on close; an already open remote session keeps its old copy.
- Concurrent AFS writers replace whole versions, so the last closer wins.
- Callback state requires revalidation after failures.
- Transfer granularity determines which workloads benefit from each cache design.
File Systems examples
1Follow a File from Open to Durable Replacement
19 steps
- 1IntroductionFollow four kinds of state
A file operation can change bytes, an open position, a directory name or a permission record. Follow a small program through these layers, then build a durable replacement. Keeping the layers separate explains two initially surprising results: a file can remain usable after its last name is removed, and closing a file does not by itself make a save durable.
- 2ExampleOpen and read a byte range
A 260-byte file is opened for reading. Suppose
openreturns descriptor 3 and the firstread(3, buf, 80)returns 80. The descriptor identifies this process’s open reference; the offset now equals 80. The returned count tells you how far to advance. The requested count is only an upper bound, so the program must inspect the return value. - 3ChallengeFind the next offset
The next request asks for 100 bytes but returns 60. At what offset will the following ordinary read begin?
---reveal---
It begins at byte 140: the previous offset 80 advances by the 60 bytes actually read. Advancing by the requested 100 would incorrectly skip bytes. A read returning 0 at end of file would leave the offset unchanged.
- 4AnalogyShare a bookmark
An open file entry is like a shared bookmark in one copy of a book.
dupgives another way to reach that same bookmark; two separateopencalls create two bookmarks. The analogy tracks position only: both opens still reach the same underlying file. Editing its bytes can affect both readers even though their offsets are independent. - 5ExampleTrace a duplicated descriptor
The program calls
other = dup(3), thenlseek(other, 20, SEEK_SET). Descriptor 3 now also uses offset 20 because both descriptors reach one open file entry. A 15-byte read through descriptor 3 leaves their shared offset at 35. The seek itself changes memory state; it does not read a block or move a physical disk head. - 6TipChoose the right position operation
Use
preadwhen a read should name an explicit byte position without changing the open entry’s offset. This matters when descriptors share that entry afterduporfork. Ordinaryreadadvances the shared offset;preaddoes not. Check either call’s returned count and errors before assuming the requested bytes were obtained. - 7ExplanationMake accepted bytes durable
A successful
writeusually places new bytes in memory buffers. A later crash can erase them before storage receives them.fsyncwaits for the file’s dirty data and metadata to reach storage as reported by the device. Closing a descriptor releases an open reference; it does not perform the same durability operation. - 8ExamplePrepare a complete replacement
An editor wants to replace
settings.json. It first writes all replacement contents intosettings.json.tmpin the same directory, checking write counts and errors. The old destination remains intact during this work. It then successfully synchronizes the temporary file, ensuring the complete replacement bytes are durable before changing the destination name. - 9ExamplePublish and persist the new name
Next the editor renames the temporary file over
settings.json. The same-file-system replacement is atomic: the name reaches the old or the new file, without a missing-name interval. Finally it synchronizes the containing directory. That last step persists the name change, because the directory entry belongs to the directory rather than to the file. - 10ChallengeFind the missing durability step
A program writes and synchronizes a new file, renames it over the destination, and immediately reports a durable save. What operation is still needed?
---reveal---
Synchronize the containing directory and check success. File synchronization protects the replacement’s contents, while directory synchronization protects the changed name. Atomic rename alone does not establish that the rename survives a crash.
- 11ExampleAdd a second name
The command
ln draft savedcreates a hard link. Both names refer to one inode and the regular file’s link count becomes 2. Removingdraftleavessavedusable with count 1. Nothing copied the contents: the operation added a directory entry. A symbolic link would instead be its own object storing a path to follow. - 12ChallengeRemove every name from an open file
A process has
savedopen. Another process removes its final hard link. Can the first process still read it, and when can storage be reclaimed?---reveal---
The open descriptor remains usable. Reclamation waits until there are no names and no open references. The link count records names, so reaching zero does not by itself end an existing open reference.
- 13Key InsightA path is not an object reference
A symbolic link keeps a target path, so deleting or renaming the target can leave it dangling. A hard link directly names the inode and survives removal of its other names. Symbolic links can cross file systems because paths can cross mount points; hard links cannot, because inode numbers have meaning within one file system.
- 14ExampleDecode access rights
The permission string
rw-r-----divides into ownerrw-, groupr--and other---. With read = 4, write = 2 and execute = 1, the digits are 6, 4 and 0. Thuschmod 640 reportsets owner read/write, group read and no rights for others. The three digits describe separate user classes, not one shared sum. - 15ChallengeReach a known filename
A user has execute but no read permission on
/srv/data, and the file’s permissions allow reading/srv/data/plan. Can the user open the known path and list the directory?---reveal---
The user can open the known path because execute permits directory search. Listing needs directory read permission, so listing fails. File permissions are checked as well; directory search alone does not grant access to every file inside.
- 16TipRead ctime correctly
After
chmod, ctime changes because the inode’s status changed. It is not a creation timestamp. mtime records changes to file contents; atime records access, often with delayed updates. Keeping these meanings distinct helps you interpretstatoutput without concluding that a permission change created a new file or rewrote its bytes. - 17ConnectionJoin trees without joining identities
Mounting attaches another file system’s root at a directory such as
/media/usb, hiding that directory’s previous contents while mounted. The virtual file system layer gives both trees common calls. It does not merge their inode tables: inode 25 on one file system can be unrelated to inode 25 on the other. - 18ChallengeReplace a File Through a Second Name
A program keeps the old file open through hard link
backup. An editor publishes a complete new file by renaming a temporary file overcurrent, then synchronizes the directory. Which contents does the old descriptor reach, and which does a new open ofcurrentreach?---reveal---
The descriptor and
backupstill reach the old inode. A new open ofcurrentreaches the replacement inode. Rename changes a directory entry without retargeting existing open references; synchronization persists that name switch. - 19RecapUse the correct state model
Track ordinary read positions through open file entries, and names through directories and inodes. For durable replacement, finish the temporary file, synchronize it, rename it, then synchronize the directory. When diagnosing access or deletion, examine the relevant directory rights and open references as well as the file’s own metadata.
2Find Bytes on Disk and Count the Work
17 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTranslate names into blocks
- 2ExampleBuild the partition layout
- 3ChallengeCount inode slots
- 4ExampleCompute an absolute inode address
- 5ExampleSelect bytes within the containing block
- 6Key InsightKeep the filename out of the inode
- 7ExampleLocate a logical data block
- 8ExplanationExpand the pointer tree
- 9ExampleAllocate the first part of a large file
- 10ChallengeFinish the pointer-block count
- 11ConnectionDescribe a run instead of every address
- 12ExampleWalk an uncached path
- 13ChallengeApply the reading model
- 14TipCount each changed structure
- 15Key InsightLet memory remove device work
- 16ChallengeRepeat a Lookup After Caching It
- 17RecapSeparate three translations
3Place Related Files and Size Their Transfers
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSpend time transferring useful bytes
- 2ExplanationDiagnose distant metadata and small transfers
- 3AnalogyKeep a working set together
- 4ExamplePut local resources in each group
- 5ChallengeExplain what grouping changes
- 6ExamplePlace a directory’s files
- 7Key InsightLeave room for neighboring files
- 8ExampleMeasure the overhead
- 9ExampleSolve for a 90% target
- 10ChallengePredict a faster drive’s chunk size
- 11TipMatch allocation to small files too
- 12ChallengeKeep Room for Files Created Together
- 13RecapBalance locality, space and transfer size
4Follow an Append Through a Crash and Recovery
18 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionRecover one complete update
- 2ExampleName the three changed blocks
- 3ChallengeClassify an allocation without a pointer
- 4ExampleFind hidden garbage
- 5Key InsightSeparate three failure types
- 6ExplanationReconstruct ownership with fsck
- 7ChallengeJudge a clean check
- 8ConnectionRepair names without inventing them
- 9ExampleWrite the redo payload first
- 10ExampleCommit the complete transaction
- 11ChallengeRecover after a partial checkpoint
- 12TipCheck commitment and durability separately
- 13ExampleAvoid journaling the data twice
- 14ChallengeSpot the missing ordering rule
- 15ExampleProtect a reused block from old replay
- 16ConnectionPublish a new tree instead
- 17ChallengeRecover Structure Without Recovering Intent
- 18RecapAsk what recovery can prove
5Write, Find and Clean a Moving Log
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionKeep identity stable while locations move
- 2ConnectionTurn many short updates into one transfer
- 3ExampleWrite a new version
- 4ExplanationUpdate the inode map
- 5ChallengeCheck the parent directory
- 6ExampleFind the map after mounting
- 7ExampleSize a sequential segment
- 8Key InsightDo not infer sequential reads
- 9ExampleTest the old block’s liveness
- 10ChallengeCompute the space recovered
- 11TipAvoid copying data that will soon die
- 12ExampleChoose a complete checkpoint
- 13ChallengeRecover work after the checkpoint
- 14ChallengeRecover the Map Before Testing Liveness
- 15RecapPay for sequential updates with mapping and cleaning
6Retry Requests and Predict Shared-File Visibility
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionHandle uncertainty across machines
- 2ExampleBuild a self-contained read
- 3Key InsightStateless does not mean storage-free
- 4ChallengeRetry a lost WRITE reply
- 5TipRecognize a retry that changes the response
- 6ExampleMake the acknowledgment durable
- 7ConnectionKeep protocol versions distinct
- 8ExampleShare at an NFS open boundary
- 9ChallengeExplain an old cached read
- 10AnalogyReplace repeated checks with a promise
- 11ExampleKeep an existing AFS session’s copy
- 12ChallengePredict two writers’ result
- 13Key InsightAccount for callbacks after a crash
- 14ConnectionChoose transfer granularity for the workload
- 15ChallengeSeparate a Safe Retry from a Safe Shared Edit
- 16RecapAsk two separate network questions
File Systems practice questions
Set 1: Diagnose File-System Designs10 Practice Questions
Question 1 of 10
Show answerHide answer
Question 2 of 10
one and two are hard links to a file, and shortcut is a symbolic link containing the path one. A process opens two, then all three names are unlinked. Before that process closes its descriptor, what remains?Show answerHide answer
Question 3 of 10
Show answerHide answer
Question 4 of 10
Show answerHide answer
Question 5 of 10
Show answerHide answer
Question 6 of 10
Show answerHide answer
Question 7 of 10
Show answerHide answer
Question 8 of 10
Show answerHide answer
Question 9 of 10
Show answerHide answer
Question 10 of 10
Show answerHide answer
Set 2: Map Inodes, Data Blocks and Device Work10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
/lab/raw/data before reading its contents?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
/lab/raw/data, the program reads three data blocks; each block access rereads the inode and writes atime. Closing has no I/O. What is the total?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 3: Descriptors, Names and Durable Saves10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
a and b, then reads 24 bytes through a. What is the offset of b before its first read?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
a, creates b = dup(a), and separately opens it as c. After fork, the child seeks b to 70. The parent reads 12 bytes through a, then calls pread(c, buf, 9, 100). What are the final offsets for a, b and c?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
red and blue, are hard links to one closed regular file. After red is unlinked, what happens to blue?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
/srv/team but cannot list its names. The user knows /srv/team/brief, and that file’s permissions permit reading. What access is possible?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Locality, Throughput and Partial Updates10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Check, Commit and Replay Safely10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 6: Moving Logs and Network Caches10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Absolute path
/srv/reports/today starts at root even when the process’s current directory is /home/lee.Access control list
Allocation bitmap
Amortization
Andrew File System
Append mode
O_APPEND.O_APPEND so each new message is placed after the file’s existing contents.atime
relatime policy avoids writing it after every access.Atomic replacement
rename replaces config while readers reach either the old or the new complete file.Atomic write unit
Attribute cache
B
Bad pointer
Block group
C
Cache hit
Callback
Checkpoint region
Checkpointing
Circular log
Cleaner
Close-to-open consistency
Commit record
Copy-on-write
Crash consistency
ctime
chmod changes ctime even though the bytes and therefore the content modification time stay unchanged.Cylinder group
D
Data block
Direct pointer
Directory entry
Dirty buffer
Distributed file system
read into a request identifying a server file and the desired byte range.Double indirect pointer
Duplicate pointer
Durability
fsync before reporting that a document’s new contents are durably saved.E
Extent
F
File Allocation Table
File descriptor
open returns 4, the program passes 4 to read, write and close for that open reference.File handle
File offset
Fragmentation
fsck
G
Garbage data
Generation number
H
Hard link
ln draft copy, removing draft leaves copy pointing to the same file and its existing bytes.Hot segment
I
Idempotency
Indirect block
Inode
Inode map
Inode table
J
Journal
L
Large-file exception
Last-closer-wins consistency
Lease
Link count
Live block
Log-structured file system
Logical file block
Logical logging
M
Metadata
fstat reads metadata through a descriptor without treating the file’s contents as a metadata record.Metadata journaling
Mount point
/media/usb hides that directory’s previous contents while the mount remains active.mtime
N
Namespace locality
Network File System
O
Open file table entry
dup point to one open entry, so a seek through either changes their shared position.Ordered journaling
P
Page cache
Path traversal
/team/result first finds team in root, then finds result in the team directory.Permission bits
750 gives the owner all three rights, the group read and execute, and others no rights.Physical logging
Positioning cost
R
Recursive update problem
Relative path
/srv/reports, the path ../logs reaches /srv/logs by first following the parent entry.Revoke record
Roll-forward recovery
S
Segment
Segment summary
Short write
write returns 200 for a 500-byte request, the program still needs to handle the remaining 300 bytes.Snapshot
Soft updates
Space leak
Sparse file
Stale cache
Stateless server
Sub-block
Superblock
Symbolic link
/media/archive can reach a different file system, but fails if its target path disappears.T
Track buffer
Transaction
Transaction checksum
Truncation
O_TRUNC empties it before the program writes any replacement contents.U
Umask
0022 changes a request for 0666 into permissions 0644.Unreachable inode
lost+found for inspection.V
Virtual file system
read on an ext4 file and an NFS file while their implementations handle different storage.W
Whole-file caching
Write buffering
Writeback journaling mode
data=writeback, metadata can be consistent even when a recently written file contains stale bytes.About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.