Flat File System
Part of Data Storage
A single directory with file names and locations — the simplest usable file system and the starting point for understanding all more complex designs.
Why This Matters
A flat file system is the minimum viable way to organize storage. Instead of the nested folders most users are familiar with, a flat file system has exactly one directory listing all files, each with a name and a physical location. That is it.
This simplicity is a feature, not a limitation, in certain contexts. A flat file system is:
- Implementable in under 200 lines of code
- Easy to debug (any corruption is easy to identify — there is only one metadata structure)
- Easy to recover (only one directory structure to repair)
- Transparent (you can understand the entire layout of the volume at a glance)
- Fast to implement from scratch when building a system from nothing
Many early operating systems (CP/M for its first versions, some MS-DOS configurations, ROM-based embedded systems) used flat file systems successfully. For a system with dozens to a few hundred files, the lack of subdirectories is a minor inconvenience, not a fundamental limitation.
Understanding flat file systems also makes hierarchical file systems easier to understand — a hierarchical file system is simply a tree of flat directories, where each directory entry can point to another directory rather than just a file.
Structure of a Flat File System
A flat file system on a block device consists of two regions:
The directory (typically at fixed positions at the start of the device): A sequence of fixed-length directory entries. Each entry describes one file:
- File name: fixed-length character array (8.3 format in FAT: 8-character name, 3-character extension, padded with spaces)
- Starting block: the block number where the file’s data begins
- File length: in bytes (or in blocks, for simpler implementations)
- Status byte: indicates whether the entry is free, active, or deleted
The data region (everything after the directory): Fixed-size blocks containing file data. For the simplest design, each file is allocated contiguous blocks — no fragmentation, no linked-list chains, just file[i] lives at blocks [start..start+size-1].
Example layout for a 1 MB flat file system with 512-byte blocks and 32-byte directory entries:
- Blocks 0–3: 4 directory blocks × 512 bytes / 32 bytes per entry = 64 directory entries (maximum 64 files)
- Blocks 4–2047: 2,044 data blocks = ~1 MB of file storage
Finding a file: scan 64 directory entries for matching name. Linear search — at most 64 comparisons, negligible.
Accessing file data: read starting block from directory entry, read blocks [start..start+(length/512)] sequentially.
Contiguous Allocation
In a flat file system with contiguous allocation, every file occupies an unbroken run of consecutive blocks. File foo.dat at start block 100 with length 5 blocks occupies blocks 100, 101, 102, 103, 104.
Advantages of contiguous allocation:
- Maximum read speed: sequential block access, no seeking between scattered blocks
- Trivial random access within file: byte N of the file is at block (start + N/blocksize), offset (N mod blocksize)
- No allocation table needed beyond the start block and length in each directory entry
Disadvantages:
- External fragmentation: As files are created and deleted, the free space becomes scattered in many small gaps. A 10-block file may not fit even if 50 blocks are free, because no contiguous 10-block run exists.
- Cannot easily extend files: If a file needs to grow, the next blocks may be occupied by another file. Growing requires either copying the entire file to a new location or refusing the extension.
Mitigation for fragmentation: Compact (defragment) the volume periodically by copying all files to contiguous positions from the beginning of the data region, leaving a single free pool at the end. This is a simple operation on a flat file system — read all files in directory order, write each to the next available position, update directory entries.
For a small embedded system or a write-mostly system (build programs off-line, load from tape or ROM, rarely delete), contiguous allocation is entirely practical.
Linked Allocation
An alternative to contiguous blocks: each block contains a pointer to the next block in the file. The directory entry contains only the first block number and the file length. To read the whole file, follow the chain of block pointers.
Block structure with linking: Reserve the last 2 or 4 bytes of each block as a “next block” pointer. A value of 0xFFFF (or similar sentinel) marks the last block.
Advantage: Files can grow without needing contiguous free space. Any free block can be chained to any existing file.
Disadvantage: No random access — to reach block N of a file, you must follow N link pointers from the start. Reading the 100th block requires reading all 100 preceding blocks to find the chain. Disk seeking between scattered blocks is slow.
FAT as a linked allocation variant: The File Allocation Table takes linked allocation and moves the pointers out of the data blocks into a separate table. Each FAT entry corresponds to one cluster (allocation unit, typically 2–8 sectors). The FAT entry for cluster N contains either: the number of the next cluster in the file (linkage), an end-of-chain marker, a bad cluster marker, or 0 (free cluster). Data blocks are completely separate from the chain pointers, giving full-sized data blocks and centralizing the allocation map.
Implementing a Minimal Flat File System
A working flat file system for a simple embedded computer:
Directory format (32 bytes per entry):
Offset Size Field
0 1 Status: 0x00=free, 0xE5=deleted, else=active
1 8 Name (8 bytes, padded with spaces, uppercase)
9 3 Extension (3 bytes, uppercase)
12 2 Starting block (16-bit little-endian)
14 2 Block count (16-bit)
16 4 File size in bytes (32-bit)
20 12 Reserved (zeroed)
Free space tracking (simple bitmap approach): A small bitmap at a fixed location (say, blocks 1–3 for a 512-byte block device with up to 24,576 blocks). One bit per block; 1=used, 0=free.
Operations:
Create file: Scan directory for free entry. Scan bitmap for a contiguous run of required blocks. Write the starting block address and the free-flag-cleared entries in the bitmap. Initialize the directory entry.
Read file: Find directory entry by name scan. Read blocks [start..start+count-1].
Delete file: Mark directory entry as deleted (first byte = 0xE5). Mark blocks as free in bitmap.
List directory: Scan all directory entries; print name, extension, size, and starting block for each active entry.
Total implementation: approximately 150–200 lines in C, or 400–500 lines in assembly for a Z80 or 6502 processor. This is achievable as a first computing project on rebuilt hardware.
Managing a Flat File System in Practice
Naming conventions: Without subdirectories, naming becomes critical for organization. Use a consistent scheme: DD-MMYY.DAT for daily data files, PROG-NNN.COM for sequentially numbered programs, DOC-XXXX.TXT for documentation. Without discipline, 64 files become indistinguishable.
File catalogs: Maintain a separate printed or written catalog listing every file’s purpose, creation date, and a brief description. The file system only stores the name — human-readable context must live in supplementary documentation.
Backup discipline: A flat file system on a single disk has no inherent redundancy. Establish a backup schedule: copy all modified files to tape or a second disk at least daily. For critical programs, maintain three copies on different media.
Volume labels: Reserve directory entry 0 for a volume label: the name of the volume (the purpose of this disk), the creation date, and the name of the creator or project. This makes it trivially easy to identify disks pulled from storage years later.
Transition to hierarchical: When the number of files grows to the point where a flat directory becomes confusing (typically above 50–100 files), it is time to implement subdirectories. The implementation is a natural extension: a directory entry can point to a block that is itself a directory (a recursive structure). FAT and Unix both implement directories exactly this way.