Chapter 7 File system data structures
The disk driver and buﬀer cache (Chapter 6) provide safe, synchronized access to disk blocks. Individual blocks are still a very low-level interface, too raw for most programs. Xv6, following Unix, provides a hierarchical ﬁle system that allows programs to treat storage as a tree of named ﬁles, each containing a variable length sequence of bytes. The ﬁle system is implemented in four layers: ------------pathnames ------------directories ------------inodes ------------blocks -------------
The ﬁrst layer is the block allocator. It manages disk blocks, keeping track of which blocks are in use, just as the memory allocator in Chapter 2 tracks which memory pages are in use. The second layer is unnamed ﬁles called inodes (pronounced inode). Inodes are a collection of allocated blocks holding a variable length sequence of bytes. The third layer is directories. A directory is a special kind of inode whose content is a sequence of directory entries, each of which lists a name and a pointer to another inode. The last layer is hierarchical path names like /usr/rtm/xv6/fs.c, a convenient syntax for identifying particular ﬁles or directories.
File system layout
Xv6 lays out its ﬁle system as follows. Block 0 is unused, left available for use by the operating system boot sequence. Block 1 is called the superblock; it contains metadata about the ﬁle system. After block 1 comes a sequence of inodes blocks, each containing inode headers. After those come bitmap blocks tracking which data blocks are in use, and then the data blocks themselves. The header fs.h (3550) contains constants and data structures describing the layout of the ﬁle system. The superblock contains three numbers: the ﬁle system size in blocks, the number of data blocks, and the number of inodes.
Code: Block allocator
The block allocator is made up of the two functions: balloc allocates a new disk block and bfree frees one. Balloc (4104) starts by calling readsb to read the superblock. (Readsb (4078) is almost trivial: it reads the block, copies the contents into sb, and releases the block.) Now that balloc knows the number of inodes in the ﬁle system, it can consult the in-use bitmaps to ﬁnd a free data block. The loop (4112) considers every block, starting at block 0 up to sb.size, the number of blocks in the ﬁle system, checking for a block whose bitmap bit is zero, indicating it is free. If balloc ﬁnds such a block, it updates the bitmap and returns the block For eﬃciency, the loop is split into two pieces: the inner loop checks all the bits in a single bitmap block—there are BPB—and the outer loop considers all the blocks in increments of BPB. There may be multiple processes calling balloc simultaneously, and yet there is no explicit locking. Instead, balloc relies on the fact that the buﬀer cache (bread and brelse) only let one process use a buﬀer at a time. When reading and writing a bitmap block (4114-4122), balloc can be sure that it is the only process in the system using that block. Bfree (4130) is the opposite of balloc and has an easier job: there is no search. It ﬁnds the right bitmap block, clears the right bit, and is done. Again the exclusive use implied by bread and brelse avoids the need for explicit locking. When blocks are loaded in memory, they are referred to by pointers to buf structures; as we saw in the last chapter, a more permanent reference is the block’s address on disk, its block number.
In Unix technical jargon, the term inode refers to an unnamed ﬁle in the ﬁle system, but the precise meaning can be one of three, depending on context. First, there is the on-disk data structure, which contains metadata about the inode, like its size and the list of blocks storing its data. Second, there is the in-kernel data structure, which contains a copy of the on-disk structure but adds extra metadata needed within...