FAT
FAT (File Allocation Table) remains one of the most common first filesystems a hobby kernel implements, not because it is modern (it dates to the earliest MS-DOS systems) but because its on-disk structure is unusually simple to parse correctly, well documented, and still universally readable, making it a practical choice for anything from a boot partition to a hobby kernel’s first working filesystem driver.
Overall layout
Section titled “Overall layout”A FAT volume is organized as a small number of large, contiguous regions, in a fixed order: a boot sector (containing both boot code and a structure describing the volume’s geometry), one or more copies of the File Allocation Table itself, a root directory region (FAT12/16 only; FAT32 stores the root directory as an ordinary cluster chain instead, described below), and finally the data region, holding every file and subdirectory’s actual contents.
The boot sector and BIOS Parameter Block
Section titled “The boot sector and BIOS Parameter Block”The first sector describes the volume’s own geometry through a structure called the BIOS Parameter Block (BPB), embedded within otherwise-executable boot code, with fields including bytes per sector, sectors per cluster, the number of reserved sectors before the FAT region begins, the number of FAT copies, and (for FAT32) the starting cluster of the root directory:
struct fat_bpb { uint8_t jump[3]; char oem_name[8]; uint16_t bytes_per_sector; uint8_t sectors_per_cluster; uint16_t reserved_sectors; uint8_t fat_count; uint16_t root_entry_count; // 0 on FAT32 uint16_t total_sectors_16; // 0 if too large; see total_sectors_32 uint8_t media_type; uint16_t sectors_per_fat_16; // 0 on FAT32; see fat32-specific extension uint16_t sectors_per_track; uint16_t head_count; uint32_t hidden_sectors; uint32_t total_sectors_32;} __attribute__((packed));Every other structure on the volume (where the FAT itself starts, where the root directory or data region begins, how large a cluster is in bytes) is computed from these fields rather than assumed at any fixed offset, which is what lets a single driver correctly handle volumes formatted with different cluster sizes or sector counts.
The File Allocation Table
Section titled “The File Allocation Table”A file’s contents are not necessarily stored contiguously; they’re stored as a chain of fixed-size clusters, and the File Allocation Table is, functionally, an array with one entry per cluster on the volume, where each entry holds either the number of the next cluster in that file’s chain, a reserved value marking the chain’s end, or zero marking the cluster as free. Reading a file therefore means starting from its first cluster number (found in its directory entry) and repeatedly looking up the current cluster’s FAT entry to find the next one, until an end-of-chain marker is reached:
uint32_t fat_next_cluster(uint8_t *fat, uint32_t cluster, int fat_bits) { if (fat_bits == 16) { uint16_t *table = (uint16_t *)fat; return table[cluster]; // >= 0xFFF8 marks end of chain } // FAT12 packs entries into 1.5 bytes each, requiring bit-level extraction // FAT32 uses 32-bit entries with the top 4 bits reserved return 0; // simplified for illustration}FAT12’s entries are the awkward case: at 12 bits each, two consecutive entries pack into exactly 3 bytes, requiring a byte read followed by a shift and mask to extract either the low or high 12 bits depending on whether the cluster number is even or odd, a detail that trips up more naive implementations that assume every FAT variant uses a whole number of bytes per entry.
Directory entries
Section titled “Directory entries”A directory is simply a region of storage (a fixed area for FAT12/16’s root directory, an ordinary cluster chain for everything else, including FAT32’s root directory), formatted as a sequence of fixed-size 32-byte entries, each describing one file or subdirectory:
struct fat_dirent { char name[8]; char ext[3]; uint8_t attributes; uint8_t reserved; uint8_t create_time_tenths; uint16_t create_time; uint16_t create_date; uint16_t access_date; uint16_t first_cluster_high; // 0 on FAT12/16 uint16_t modify_time; uint16_t modify_date; uint16_t first_cluster_low; uint32_t file_size;} __attribute__((packed));The 8+3 name/ext split is the origin of FAT’s traditional “8.3” short filename limitation: eight characters, a period, then a three-character extension, all uppercase, with no support for lowercase or many special characters in the base format. Long File Names, a later extension, work around this without changing the underlying 32-byte entry format at all: a long name is stored across several additional directory entries immediately preceding the real entry, each marked with a special attribute value (0x0F) identifying it as a long-name fragment rather than a real file, and each holding a portion of the name encoded in UTF-16. Such entries are ones a driver that only supports short names can (and, per the specification, should) simply skip over as unrecognized, falling back to the always-present short name alongside them.
Implementation notes
Section titled “Implementation notes”FAT32, despite the name, does not actually use the full 32 bits of a cluster number: the top 4 bits of each 32-bit FAT entry are reserved and must be masked off before use, an easy detail to miss that produces cluster numbers that appear valid but are actually corrupted by leftover reserved bits from whatever wrote the entry previously. The FAT is also conventionally stored in more than one identical copy (fat_count in the BPB, typically 2), specifically so a filesystem checker can potentially recover from corruption in one copy by comparing against the other, a detail a minimal read-only driver can safely ignore, reading only the first copy, but one a driver that writes to the filesystem needs to handle correctly by keeping every copy in sync.
References
Section titled “References”- ^ Microsoft, FAT32 File System Specification (the authoritative reference for the BPB layout, FAT entry encoding, and directory entry format described above).
See also
Section titled “See also”- The VFS: the layer a FAT driver would sit behind in a kernel supporting multiple filesystem formats.
- Physical Memory: where buffers holding sectors read from disk are ultimately allocated from.