A file system is logical way to store, read, and write data on persistent storage. They act as layers of abtraction into how data are structured and organised (usually into groups). All filesystems support CRUD operations, along with other features like caching (to speed up access) and permissions to only allow permitted users to view data.
Firstly, FAT stands for File Allocation Table and was primarily used in windows systems. It uses 2 tables (one acting as a backup) which act essentially store cluster numbers to actual data like a contents page within a book. The clusters can be scattered throughout the filesystem but with FAT, you follow the chain of clusters. There are a whole host of FAT file systems starting with FAT-12 which is rumoured to be written by Bill Gates and importantly as memory was scarse then, uses 12 bits to address clusters. If you are planning on writing a FAT driver, I would reccomend FAT-16 which uses 2 bytes (16 bits) to address a cluster as I found I could make a rather small disk file of 10MB compared to something like FAT32 which requires more.
If you want an initial ramdisk or initrd, follow these steps:
dd id=/dev/zero of=fs.img count=10 bs=1Mfdisk fs.img in which press:np, enter, entert, cw mkfs.vfat fs.img\
You'll want to copy this file to your grub modules folder and specify in grub.cnf
There are helpful tools like mcopy which allow you to copy files from your ssd to the fat file system. Likewise, fatcat can list all files the file has been copied over correctly which can help when debugging.The disk is organised into three main sections: boot record, FAT, data. The boot is placed first and a struct encapsulating it's data looks like this:
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
struct BiosParameterBlock {
jmp: [u8; 3],
oem: [u8; 8],
bytes_per_sector: u16,
sectors_per_cluster: u8,
reserved_sector_count: u16,
table_count: u8,
root_entry_count: u16,
sector_count_16: u16,
media_type: u8,
table_size_16: u16, // Number of sectors per FAT
sectors_per_track: u16, // Number of sectors per track
head_count: u16,
hidden_sector_count: u32,
sector_count_32: u32,
}
This is placed at the very start of memory and thus can be accessed like this:
let bpb = unsafe { &*(start_address as *const BiosParameterBlock) };
There may be some confusion between clusters and sectors. A cluster is a unit of storage which is physically set by the filesystem whilst a sector is a unit of storage on a drive level. The very first step is just to locate the first fat table, the root directory, and first data sector. There are formulas for each of these here: https://wiki.osdev.org/FAT#Reading_the_Boot_Sector or you can checkout my code at https://github.com/sid-shakthivel/os64/blob/main/kernel/src/fs.rs. The root directory holds file entries which holds useful data including name and cluster_low. Note that for attribute, 0x10 is a directory, 0x20 is a file and 0x0F is a long directory entry (I'll go into these later).
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
struct StandardDirectoryEntry {
filename: [u8; 8],
ext: [u8; 3],
attributes: u8, // Could be LFN, Directory, Archive
unused: [u8; 8], // Reserved for windows NT
cluster_high: u16, // Always 0
time: u16,
date: u16,
cluster_low: u16,
file_size: u32,
}
To read a file we can inspect the cluster_low field. The cluster_low is the first cluster address and is linear however sectors (which actually hold data) use segment addresses, so it needs to be converted. From here, if there are other clusters linked, we can follow the chain by checking the FAT table, if it's in the range 0xFFF8..=0xFFFF that means there is no more to be read. You can copy this data into a temporary buffer and present that to the user if necessary.
You'll notice that the filename field only stores 8 characters which means you can't have names like theverybigfile.txt, this is where LongFileEntries come into play (there can be a multitude of these but they must be placed before the standard entry) and they act as more storage to hold more data.
VFS stands for virtual file system and is yet another abstraction on top of a file system driver which allows any program to work with the filesystem and it does this through a graph of nodes which represent file/directories.
This is only a rough guide on FAT, I'd reccomend reading a number of other articles before starting your own driver but I hope this has been of some help.