Simple file system for small disks.
The FAT12 file system is a simple, little-endian file system useful for the early stages OS development.
Segments of FAT12
- Partition Boot Sector: 512-bytes containing binary code for boot record entwined with metadata.
- Reserved: Reserved for extended boot code and optional backup of PBS.
- FAT1: File cluster maps as compact linked list indicating the disk clusters occupied by a file.
- FAT2: Exact duplicate of FAT1 table for redundancy checks.
- Root Folder: Directory entries for the root folder in the file system.
- Rest of File System Data: Subfolders and their directory entries as well as file contents.
Partition Boot Sector (PBS)
The PBS mixes executable code and metadata about the drive and file system. It is designed to be loaded and executed by the BIOS as part of the boot process.
struct Partition_Boot_Sector { uint8_t jump[3]; /* Jump command to jump into boot_code */ char oem_name[8]; /* OEM name, contents don't really matter */ uint16_t bytes_per_sect; /* Bytes per sector */ uint8_t sects_per_cluster; /* Sectors per cluster */ uint16_t nreserved; /* Number of reserved sectors (including this PBR) */ uint8_t nfats; /* Number of file allocation tables (FATs) */ uint16_t nroot_dir_entries; /* Number of root directory entries */ uint16_t nsectors; /* Number of sectors on this partition */ uint8_t media_desc; /* Media descriptor */ uint16_t sects_per_fat; /* Sectors per FAT */ uint16_t sects_per_track; /* Sectors per track */ uint16_t nheads; /* # of heads */ uint32_t nhidden; /* # of hidden sectors */ uint32_t nsectors_ext; /* # of sectors on partition (only used if nsectors = 0) */ uint8_t drive; /* Drive number */ uint8_t reserved; /* Reserved */ uint8_t boot_sig_ext; /* Extended boot signature */ uint32_t serial; /* Volume serial number */ char label[11]; /* Text label for volume */ char type[8]; /* File system type as 8 char string */ uint8_t boot_code[448]; /* Boot sector code (binary executable) */ uint16_t signature; /* Boot sector signature (must be 0xAA55 little endian) */ };
File Allocation Table (FAT)
The file allocation table (FAT) is the most important data structure used by the FAT12 file system. FAT divides the file-system up into fixed size chunks called clusters (for example, 1 cluster = 512 bytes). Files are stored in units of clusters instead of bytes, so storing a very small file still causes that file to occupy an entire cluster. The clusters occupied by a file also need not be sequential, so the parts of a single file can be stored all over the disk.
The FAT itself is a compact, byte encoded linked list that is used to determine all of the clusters occupied by the contents of a given file. To load a file you find its directory entry which tells you the cluster its contents start in. Each link in the FAT12 table contains a 12-bit entry that either points to the next cluster or contains the terminal value between 0xFF8 and 0xFFF indicating this is the last cluster for the file. To load a file simply navigate the FAT link-by-link until you hit a terminal value.
As an example, here is how a file that occupies 3 clusters might be organized in the FAT:
x000 x001 x002 x003 x004 x005 x006 x007 ... - - 003 005 - FFF - - ___/ \____/ \____________/ | / continues continues X File starts @ 003 @ 005 terminal @ Cluster 002
To compute the offset into the partition of a given FAT:
/* * Return the offset from the start of the partition to the start of file * allocation table (FAT) nfat. nfat is in range [0, pbr->nfats). */ static uint32_t fatoffset(Partition_Boot_Sector *pbs, uint32_t nfat) { return pbs->bytes_per_sect * ((nfat * pbs->sects_per_fat) + pbs->nreserved); }
Directory Entries
The root folder as well as all subdirectories are organized as a series of directory entries.
struct Directory_Entry { char name[8]; /* Space padded filename ([0] = E5h if deleted, 05h if pending delete) */ char ext[3]; /* File extension */ uint8_t attributes; /* File attributes bitfield */ uint8_t reserved[10]; /* Reserved, system dependent uses */ uint16_t created_time; /* [15:11] = hours(0..23), [10:5] = minutes, [4:0] = seconds/2 */ uint16_t created_date; /* [15:9] = year - 1980, [8:5] = month, [4:0] = day */ uint16_t start_cluster; /* Cluster where this directory entry starts */ uint32_t file_size; /* Size of the file in bytes */ };
To calculate the offset of the root directory you can use:
/* * Return the offset of the start of the root directory. */ static uint32_t rootdiroffset(Partition_Boot_Sector *pbs) { return pbs->bytes_per_sect * ((pbs->nfats * pbs->sects_per_fat) + pbs->nreserved); }
You can then read directory entry objects from the root directory until you find one where the first byte of name is 0x00. Once you have a directory entry you can read its corresponding FAT entry as follows:
/* * Return the value of the FAT entry for the given cluster. */ static uint16_t fatentry(Partition_Boot_Sector *pbs, uint8_t *disk, uint16_t ncluster) { uint16_t v, b1, b2; uint32_t foff; foff = fatoffset(pbs, 0); /* * Every 2 entries takes up 3 bytes. Compute the offset of the start of * 3 byte cluster for the requested entry. */ foff += 3 * (ncluster / 2); switch(ncluster % 2) { case 0: /* * For the even case the number is split in the first 2 bytes of the 3 byte * cluster in little-endian order. */ b1 = disk[foff]; b2 = disk[foff + 1]; v = ((b2 & 0x0F) << 8) | b1; break; case 1: /* * For the odd case the number is split in the second 2 bytes of the 3 byte * cluster in little-endian order. */ b1 = disk[foff + 1]; b2 = disk[foff + 2]; v = ((b2 << 4) & 0xFF0) | ((b1 & 0xF0) >> 4); break; } return v; }
To find the data for the cluster traverse to its cluster offset:
#define DIRENTSIZE 32 /* Size of a directory entry in bytes */ /* * Return the offset of the data segment following the root directory. */ static uint32_t dataoffset(Partition_Boot_Sector *pbs) { return rootdiroffset(pbs) + (pbs->nroot_dir_entries * DIRENTSIZE); } /* * Return the offset of the given cluster. */ static uint32_t clusteroffset(Partition_Boot_Sector *pbs, uint16_t ncluster) { /* Subtract two to account for first two FAT clusters holding special values */ return dataoffset(pbs) + ((ncluster - 2) * pbs->sects_per_cluster * pbs->bytes_per_sect); }
Last update on 7E4C0B, edited 2 times. 5/5thh