Introduction: Persistent Storage
Phase 7 Goals: By the end of this phase, your kernel will read and write files from disk. You'll have an ATA driver for disk access, FAT filesystem support, and a VFS layer that abstracts filesystem differences.
Phase 0: Orientation & Big Picture
OS fundamentals, kernel architectures, learning path
Phase 1: How a Computer Starts
BIOS/UEFI, boot sequence, dev environment
Phase 2: Real Mode - First Steps
Real mode, bootloader, BIOS interrupts
Phase 3: Entering Protected Mode
GDT, 32-bit mode, C code execution
Phase 4: Display, Input & Output
VGA text mode, keyboard handling
Phase 5: Interrupts & CPU Control
IDT, ISRs, PIC programming
Phase 6: Memory Management
Paging, virtual memory, heap allocator
8
Phase 7: Disk Access & Filesystems
Block devices, FAT, VFS layer
You Are Here
9
Phase 8: Processes & User Mode
Task switching, system calls, user space
10
Phase 9: ELF Loading & Executables
ELF format, program loading
11
Phase 10: Standard Library & Shell
C library, command-line shell
12
Phase 11: 64-Bit Long Mode
x86-64, 64-bit paging, modern architecture
13
Phase 12: Modern Booting with UEFI
UEFI boot services, memory maps
14
Phase 13: Graphics & GUI Systems
Framebuffer, windowing, drawing
15
Phase 14: Advanced Input & Timing
Mouse, high-precision timers
16
Phase 15: Hardware Discovery & Drivers
PCI, device drivers, NVMe
17
Phase 16: Performance & Optimization
Caching, scheduler tuning
18
Phase 17: Stability, Security & Finishing
Debugging, hardening, completion
Your kernel can allocate memory—but when you reboot, everything is gone. Persistent storage is what makes computers useful: saving documents, loading programs, keeping user data between sessions.
THE STORAGE LAYER CAKE
═══════════════════════════════════════════════════════════════
USER PROGRAM
↓ open("/docs/readme.txt", O_RDONLY)
╔═══════════════════════════════════════════════════════╗
║ SYSCALL INTERFACE ║
║ open(), read(), write(), close(), mkdir() ║
╚═══════════════════════════════════════════════════════╝
↓
╔═══════════════════════════════════════════════════════╗
║ VFS (Virtual File System) ║
║ Unified interface: vfs_read(), vfs_write() ║
║ File descriptors, path resolution, mount points ║
╠═══════════════════════════════════════════════════════╣
║ FILESYSTEM DRIVERS ║
║ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ║
║ │ FAT │ │ ext2 │ │ tmpfs │ │ devfs │ ║
║ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ ║
╚══════╪═══════════╪═══════════╪═══════════╪═══════════╝
│ │ │ │
╔══════╪═══════════╪═══════════╪═══════════════════════╗
║ BLOCK CACHE (RAM) ║
║ Caches recently accessed disk sectors ║
╠══════╪═══════════╪═══════════════════════════════════╣
║ BLOCK DEVICE LAYER ║
║ read_block(), write_block() ║
╚══════╪═══════════╪═══════════════════════════════════╝
│ │
╔══════════════════════════════════════════════════════╗
║ DEVICE DRIVERS ║
║ ┌─────────┐ ┌─────────┐ ┌─────────┐ ║
║ │ ATA │ │ AHCI │ │ NVMe │ ║
║ │ (IDE) │ │ (SATA) │ │ (PCIe) │ ║
║ └────┬────┘ └────┬────┘ └────┬────┘ ║
╚═══════╪════════════╪════════════╪════════════════════╝
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ DISK │ │ DISK │ │ SSD │
│ (HDD) │ │ (SATA) │ │ (NVMe) │
└─────────┘ └─────────┘ └─────────┘
Key Insight: A filesystem transforms raw disk sectors into a hierarchy of named files and directories. The VFS layer lets your kernel support multiple filesystem types through a unified interface.
Storage Architecture
Storage devices communicate in blocks (usually 512 bytes). You can't read or write a single byte from disk—you read an entire sector. This is why we need:
- Block device drivers: Talk to hardware (ATA, SATA, NVMe)
- Block cache: Keep frequently-used sectors in RAM
- Filesystems: Organize blocks into files and directories
- VFS: Abstract away filesystem differences
Block Devices
/* block.h - Block device interface */
#define BLOCK_SIZE 512 /* Standard sector size */
typedef struct block_device {
char name[32]; /* "hda", "sda", "nvme0n1" */
uint32_t block_size; /* bytes per block */
uint64_t total_blocks; /* device capacity */
/* Operations */
int (*read)(struct block_device* dev, uint64_t lba,
uint32_t count, void* buffer);
int (*write)(struct block_device* dev, uint64_t lba,
uint32_t count, const void* buffer);
} block_device_t;
/* Example: Read 4 sectors starting at LBA 100 */
uint8_t buffer[4 * BLOCK_SIZE];
ata_device.read(&ata_device, 100, 4, buffer);
LBA vs CHS Addressing
Old hard drives used CHS (Cylinder-Head-Sector) addressing—specify physical location on spinning platters. Modern drives use LBA (Logical Block Addressing)—just a sector number from 0 to N. The drive controller handles the physical mapping.
Historical Context
ATA Disk Driver
ATA/IDE Overview
ATA (AT Attachment), also called IDE, is the classic interface for hard drives. Modern systems use SATA or NVMe, but ATA is simple to program and still supported in emulators—perfect for learning.
ATA CONTROLLER ARCHITECTURE
═══════════════════════════════════════════════════════════════
┌─────────────────────────────────────┐
│ ATA Controller │
│ (Usually on motherboard chipset) │
└──────────────┬──────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ Addresses:
│ PRIMARY │ │ SECONDARY │
│ CHANNEL │ │ CHANNEL │
│ 0x1F0-0x1F7 │ │ 0x170-0x177 │
│ IRQ 14 │ │ IRQ 15 │
└───────┬───────┘ └───────┬───────┘
│ │
┌───────┴───────┐ ┌───────┴───────┐
│ │ │ │
┌──▼──┐ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
│MASTER│ │SLAVE│ │MASTER│ │SLAVE│
│ hda │ │ hdb│ │ hdc │ │ hdd │
└─────┘ └─────┘ └─────┘ └─────┘
Total: Up to 4 drives (2 channels × 2 drives/channel)
ATA register ports:
| Port Offset |
Read |
Write |
| +0 | Data | Data |
| +1 | Error | Features |
| +2 | Sector Count | Sector Count |
| +3 | LBA Low (0-7) | LBA Low |
| +4 | LBA Mid (8-15) | LBA Mid |
| +5 | LBA High (16-23) | LBA High |
| +6 | Drive/Head | Drive/Head (bit 4 = drive select) |
| +7 | Status | Command |
PIO Mode
PIO (Programmed I/O) mode is the simplest way to transfer data: the CPU reads/writes each word directly. It's slow but requires no DMA setup—ideal for bootloaders and simple kernels.
/* ata.h - ATA PIO driver definitions */
#define ATA_PRIMARY_IO 0x1F0
#define ATA_PRIMARY_CTRL 0x3F6
#define ATA_SECONDARY_IO 0x170
#define ATA_SECONDARY_CTRL 0x376
/* ATA Commands */
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO_EXT 0x24 /* 48-bit LBA */
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_WRITE_PIO_EXT 0x34
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_CMD_IDENTIFY 0xEC
/* Status register bits */
#define ATA_SR_BSY 0x80 /* Busy */
#define ATA_SR_DRDY 0x40 /* Drive ready */
#define ATA_SR_DF 0x20 /* Drive fault */
#define ATA_SR_DSC 0x10 /* Drive seek complete */
#define ATA_SR_DRQ 0x08 /* Data request ready */
#define ATA_SR_CORR 0x04 /* Corrected data */
#define ATA_SR_IDX 0x02 /* Index */
#define ATA_SR_ERR 0x01 /* Error */
/* Drive/Head register bits */
#define ATA_DRIVE_MASTER 0x00
#define ATA_DRIVE_SLAVE 0x10
#define ATA_MODE_LBA 0xE0 /* LBA mode + bits 4-7 of LBA */
/* ata.c - ATA PIO read sector (complete implementation) */
#include "io.h"
#include "ata.h"
/**
* Wait for drive to be ready (BSY clear)
*/
static void ata_wait_bsy(uint16_t io_base) {
while (inb(io_base + 7) & ATA_SR_BSY);
}
/**
* Wait for data to be ready (DRQ set)
*/
static void ata_wait_drq(uint16_t io_base) {
while (!(inb(io_base + 7) & ATA_SR_DRQ));
}
/**
* Check for drive errors
* @return 0 if no error, error code otherwise
*/
static int ata_check_error(uint16_t io_base) {
uint8_t status = inb(io_base + 7);
if (status & ATA_SR_ERR) {
return inb(io_base + 1); /* Return error register */
}
if (status & ATA_SR_DF) {
return -1; /* Drive fault */
}
return 0;
}
/**
* Read a single sector using PIO mode
* @param lba Logical block address (28-bit)
* @param buffer 512-byte buffer for data
*/
void ata_read_sector(uint32_t lba, uint8_t* buffer) {
uint16_t io_base = ATA_PRIMARY_IO;
/* Wait for drive not busy */
ata_wait_bsy(io_base);
/* Select drive (master) and set LBA mode with bits 24-27 */
outb(io_base + 6, ATA_MODE_LBA | ATA_DRIVE_MASTER |
((lba >> 24) & 0x0F));
/* Set sector count = 1 */
outb(io_base + 2, 1);
/* Set LBA address (bits 0-23) */
outb(io_base + 3, lba & 0xFF); /* LBA low */
outb(io_base + 4, (lba >> 8) & 0xFF); /* LBA mid */
outb(io_base + 5, (lba >> 16) & 0xFF); /* LBA high */
/* Send READ SECTORS command */
outb(io_base + 7, ATA_CMD_READ_PIO);
/* Wait for data ready */
ata_wait_drq(io_base);
/* Check for errors */
if (ata_check_error(io_base)) {
kprintf("ATA: Read error at LBA %d\n", lba);
return;
}
/* Read 256 words (512 bytes) */
for (int i = 0; i < 256; i++) {
uint16_t data = inw(io_base); /* Read 16 bits */
buffer[i * 2] = data & 0xFF; /* Low byte */
buffer[i * 2 + 1] = (data >> 8); /* High byte */
}
}
Read/Write Operations
For writing, the process is similar but we send data to the drive:
/**
* Write a single sector using PIO mode
* @param lba Logical block address (28-bit)
* @param buffer 512-byte buffer of data to write
*/
void ata_write_sector(uint32_t lba, const uint8_t* buffer) {
uint16_t io_base = ATA_PRIMARY_IO;
/* Wait for drive not busy */
ata_wait_bsy(io_base);
/* Select drive and set LBA mode */
outb(io_base + 6, ATA_MODE_LBA | ATA_DRIVE_MASTER |
((lba >> 24) & 0x0F));
/* Set sector count = 1 */
outb(io_base + 2, 1);
/* Set LBA address */
outb(io_base + 3, lba & 0xFF);
outb(io_base + 4, (lba >> 8) & 0xFF);
outb(io_base + 5, (lba >> 16) & 0xFF);
/* Send WRITE SECTORS command */
outb(io_base + 7, ATA_CMD_WRITE_PIO);
/* Wait for drive ready to receive data */
ata_wait_drq(io_base);
/* Write 256 words (512 bytes) */
for (int i = 0; i < 256; i++) {
uint16_t data = buffer[i * 2] | (buffer[i * 2 + 1] << 8);
outw(io_base, data);
}
/* Flush cache to ensure data is written */
outb(io_base + 7, ATA_CMD_CACHE_FLUSH);
ata_wait_bsy(io_base);
}
/**
* Identify drive - get drive information
* @param buffer 512-byte buffer for IDENTIFY data
* @return 0 on success, -1 if no drive
*/
int ata_identify(uint8_t* buffer) {
uint16_t io_base = ATA_PRIMARY_IO;
/* Select master drive */
outb(io_base + 6, ATA_MODE_LBA | ATA_DRIVE_MASTER);
/* Zero out sector count and LBA registers */
outb(io_base + 2, 0);
outb(io_base + 3, 0);
outb(io_base + 4, 0);
outb(io_base + 5, 0);
/* Send IDENTIFY command */
outb(io_base + 7, ATA_CMD_IDENTIFY);
/* Check if drive exists */
if (inb(io_base + 7) == 0) {
return -1; /* No drive */
}
/* Wait for BSY to clear */
ata_wait_bsy(io_base);
/* Check for ATAPI or SATA by examining LBA mid/high */
if (inb(io_base + 4) != 0 || inb(io_base + 5) != 0) {
return -1; /* Not ATA drive */
}
/* Wait for DRQ or error */
while (1) {
uint8_t status = inb(io_base + 7);
if (status & ATA_SR_ERR) return -1;
if (status & ATA_SR_DRQ) break;
}
/* Read IDENTIFY data */
for (int i = 0; i < 256; i++) {
uint16_t data = inw(io_base);
buffer[i * 2] = data & 0xFF;
buffer[i * 2 + 1] = (data >> 8);
}
return 0;
}
/**
* Initialize ATA driver and detect drives
*/
void ata_init(void) {
uint8_t identify[512];
kprintf("ATA: Detecting drives...\n");
if (ata_identify(identify) == 0) {
/* Extract drive info from IDENTIFY data */
uint32_t sectors = *(uint32_t*)&identify[120]; /* Total LBA28 sectors */
uint32_t size_mb = sectors / 2048; /* 512 bytes × 2048 = 1 MB */
/* Model string is at offset 54, 40 bytes, swapped */
char model[41];
for (int i = 0; i < 40; i += 2) {
model[i] = identify[54 + i + 1];
model[i + 1] = identify[54 + i];
}
model[40] = '\0';
kprintf("ATA: Primary Master: %s (%d MB)\n", model, size_mb);
} else {
kprintf("ATA: No drive detected on Primary Master\n");
}
}
Warning: Real disk drivers should use DMA (Direct Memory Access) for performance—PIO ties up the CPU for every byte. Also implement proper error handling, timeouts, and interrupt-driven I/O for production use.
Partition Tables
Before filesystems, we need to understand how disks are divided. Partition tables split a physical disk into logical sections, each with its own filesystem.
MBR Partitions
The Master Boot Record (MBR) partitioning scheme dates back to IBM PC DOS. It lives in sector 0 and supports up to 4 primary partitions (or 3 primary + 1 extended with logical partitions inside).
MBR STRUCTURE (SECTOR 0)
═══════════════════════════════════════════════════════════════
Offset Size Description
────── ──── ────────────────────────────────
0x000 446 Bootloader code (Stage 1)
0x1BE 16 Partition Entry 1
0x1CE 16 Partition Entry 2
0x1DE 16 Partition Entry 3
0x1EE 16 Partition Entry 4
0x1FE 2 Boot signature (0x55, 0xAA)
────── ────
512 bytes total
PARTITION ENTRY (16 bytes each):
┌────────┬──────────────────────────────────────────┐
│ Offset │ Field │
├────────┼──────────────────────────────────────────┤
│ 0x00 │ Boot flag (0x80 = bootable) │
│ 0x01 │ Starting CHS (3 bytes) - legacy │
│ 0x04 │ Partition type (0x0B = FAT32, etc.) │
│ 0x05 │ Ending CHS (3 bytes) - legacy │
│ 0x08 │ Starting LBA (4 bytes) - USE THIS │
│ 0x0C │ Sector count (4 bytes) │
└────────┴──────────────────────────────────────────┘
/* partition.h - MBR partition handling */
/* Common partition types */
#define PART_TYPE_EMPTY 0x00
#define PART_TYPE_FAT12 0x01
#define PART_TYPE_FAT16_S 0x04 /* <32 MB */
#define PART_TYPE_EXTENDED 0x05
#define PART_TYPE_FAT16_L 0x06 /* >32 MB */
#define PART_TYPE_NTFS 0x07
#define PART_TYPE_FAT32 0x0B
#define PART_TYPE_FAT32_LBA 0x0C
#define PART_TYPE_FAT16_LBA 0x0E
#define PART_TYPE_LINUX 0x83
#define PART_TYPE_LINUX_LVM 0x8E
typedef struct {
uint8_t boot_flag;
uint8_t start_chs[3];
uint8_t type;
uint8_t end_chs[3];
uint32_t start_lba;
uint32_t sector_count;
} __attribute__((packed)) partition_entry_t;
typedef struct {
uint8_t bootcode[446];
partition_entry_t partitions[4];
uint16_t signature;
} __attribute__((packed)) mbr_t;
/**
* Read and parse MBR
*/
int mbr_parse(block_device_t* dev) {
uint8_t sector[512];
if (dev->read(dev, 0, 1, sector) != 0) {
return -1;
}
mbr_t* mbr = (mbr_t*)sector;
/* Verify boot signature */
if (mbr->signature != 0xAA55) {
kprintf("MBR: Invalid signature\n");
return -1;
}
kprintf("MBR: Found partition table\n");
for (int i = 0; i < 4; i++) {
partition_entry_t* p = &mbr->partitions[i];
if (p->type != PART_TYPE_EMPTY) {
kprintf(" Partition %d: type=0x%02X, start=%d, size=%d sectors\n",
i + 1, p->type, p->start_lba, p->sector_count);
}
}
return 0;
}
GPT Partitions
GPT (GUID Partition Table) is the modern replacement for MBR, required for UEFI boot and disks > 2 TB. It uses GUIDs (128-bit identifiers) for partition types.
GPT LAYOUT
═══════════════════════════════════════════════════════════════
LBA 0: Protective MBR (for legacy BIOS compatibility)
LBA 1: Primary GPT Header
LBA 2-33: Partition Entries (128 entries × 128 bytes = 16 KB)
...
(Partition data)
...
LBA -33: Backup Partition Entries
LBA -1: Backup GPT Header
GPT HEADER (92 bytes):
┌─────────────────────────────────────────────────────────────┐
│ Signature: "EFI PART" (8 bytes) │
│ Revision: 0x00010000 (4 bytes) │
│ Header Size: 92 (4 bytes) │
│ CRC32 of header (4 bytes) │
│ Reserved (4 bytes) │
│ Current LBA (8 bytes) │
│ Backup LBA (8 bytes) │
│ First usable LBA (8 bytes) │
│ Last usable LBA (8 bytes) │
│ Disk GUID (16 bytes) │
│ Partition entry start LBA (8 bytes) │
│ Number of partition entries (4 bytes) │
│ Size of partition entry (4 bytes) - usually 128 │
│ CRC32 of partition array (4 bytes) │
└─────────────────────────────────────────────────────────────┘
PARTITION ENTRY (128 bytes):
┌─────────────────────────────────────────────────────────────┐
│ Partition Type GUID (16 bytes) │
│ Unique Partition GUID (16 bytes) │
│ First LBA (8 bytes) │
│ Last LBA (8 bytes) │
│ Attributes (8 bytes) │
│ Partition Name (72 bytes) - UTF-16LE │
└─────────────────────────────────────────────────────────────┘
Common Partition Type GUIDs
| EFI System | C12A7328-F81F-11D2-BA4B-00A0C93EC93B |
| Microsoft Basic Data | EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 |
| Linux Filesystem | 0FC63DAF-8483-4772-8E79-3D69D8477DE4 |
| Linux Swap | 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F |
Reference
FAT Filesystem
FAT Overview
FAT (File Allocation Table) is the most widely-supported filesystem. Designed by Microsoft in 1977, it's simple enough to implement in a weekend yet used everywhere: USB drives, SD cards, UEFI boot partitions.
FAT FILESYSTEM LAYOUT
═══════════════════════════════════════════════════════════════
┌──────────────────────────────────────────────────────────────┐
│ BOOT SECTOR (1 sector) │
│ BPB (BIOS Parameter Block) + Boot code │
├──────────────────────────────────────────────────────────────┤
│ RESERVED SECTORS │
│ (Usually 1 for FAT12/16, 32 for FAT32) │
├──────────────────────────────────────────────────────────────┤
│ FAT #1 (File Allocation Table) │
│ Linked list of clusters - one entry per cluster │
├──────────────────────────────────────────────────────────────┤
│ FAT #2 (Backup copy) │
│ Usually identical to FAT #1 │
├──────────────────────────────────────────────────────────────┤
│ ROOT DIRECTORY (FAT12/16 only) │
│ Fixed size at fixed location │
├──────────────────────────────────────────────────────────────┤
│ DATA AREA │
│ Clusters containing files and directories │
│ (FAT32 root directory is here too) │
└──────────────────────────────────────────────────────────────┘
FAT VARIANTS:
┌─────────┬───────────────┬─────────────┬───────────────────┐
│ Variant │ Cluster Entry │ Max Size │ Use Case │
├─────────┼───────────────┼─────────────┼───────────────────┤
│ FAT12 │ 12 bits │ 16 MB │ Floppy disks │
│ FAT16 │ 16 bits │ 2 GB │ Small drives │
│ FAT32 │ 28 bits* │ 2 TB │ USB, SD cards │
└─────────┴───────────────┴─────────────┴───────────────────┘
* FAT32 uses only 28 bits; top 4 bits reserved
Boot Sector
The BIOS Parameter Block (BPB) in sector 0 describes the filesystem geometry:
/* fat.h - FAT16/32 structures */
/* FAT16 Boot Sector (BIOS Parameter Block) */
typedef struct {
uint8_t jmp[3]; /* Jump instruction to boot code */
uint8_t oem_name[8]; /* OEM identifier */
uint16_t bytes_per_sector; /* Usually 512 */
uint8_t sectors_per_cluster; /* 1, 2, 4, 8, 16, 32, 64, or 128 */
uint16_t reserved_sectors; /* Sectors before first FAT */
uint8_t fat_count; /* Number of FATs (usually 2) */
uint16_t root_entry_count; /* FAT12/16: max root entries */
uint16_t total_sectors_16; /* Total sectors (if < 65536) */
uint8_t media_type; /* Media descriptor (0xF8 = HDD) */
uint16_t fat_size_16; /* Sectors per FAT (FAT12/16) */
uint16_t sectors_per_track; /* For CHS geometry */
uint16_t head_count; /* For CHS geometry */
uint32_t hidden_sectors; /* Sectors before partition */
uint32_t total_sectors_32; /* Total sectors (if >= 65536) */
/* Extended Boot Record (FAT16) */
uint8_t drive_number; /* BIOS drive number */
uint8_t reserved;
uint8_t boot_signature; /* 0x29 if following fields valid */
uint32_t volume_id; /* Volume serial number */
uint8_t volume_label[11]; /* Volume name */
uint8_t fs_type[8]; /* "FAT16 " */
} __attribute__((packed)) fat16_boot_t;
/* FAT32 Extended Boot Record */
typedef struct {
/* First 36 bytes same as FAT16 */
uint8_t jmp[3];
uint8_t 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; /* Must be 0 for FAT32 */
uint16_t total_sectors_16;
uint8_t media_type;
uint16_t fat_size_16; /* Must be 0 for FAT32 */
uint16_t sectors_per_track;
uint16_t head_count;
uint32_t hidden_sectors;
uint32_t total_sectors_32;
/* FAT32 specific */
uint32_t fat_size_32; /* Sectors per FAT (FAT32) */
uint16_t ext_flags;
uint16_t fs_version;
uint32_t root_cluster; /* First cluster of root dir */
uint16_t fs_info; /* FSInfo sector number */
uint16_t backup_boot; /* Backup boot sector */
uint8_t reserved[12];
uint8_t drive_number;
uint8_t reserved2;
uint8_t boot_signature;
uint32_t volume_id;
uint8_t volume_label[11];
uint8_t fs_type[8]; /* "FAT32 " */
} __attribute__((packed)) fat32_boot_t;
FAT Table
The File Allocation Table is essentially a linked list of clusters. Each entry either points to the next cluster in a chain, or marks end-of-file, bad cluster, etc.
FAT TABLE: CLUSTER CHAIN EXAMPLE
═══════════════════════════════════════════════════════════════
Cluster: 2 3 4 5 6 7 8 9 10
Entry: [ 3 ] [ 4 ] [EOF] [ 7 ] [FREE] [ 8 ] [EOF] [FREE] [BAD]
│ │ │ │
└──→──┘ └─────→─────┘
File "README.TXT" (3 clusters): 2 → 3 → 4 → EOF
File "DATA.BIN" (2 clusters): 5 → 7 → 8 → EOF
SPECIAL FAT ENTRY VALUES:
┌──────────────┬───────────────┬───────────────┬────────────────┐
│ Meaning │ FAT12 │ FAT16 │ FAT32 │
├──────────────┼───────────────┼───────────────┼────────────────┤
│ Free cluster │ 0x000 │ 0x0000 │ 0x00000000 │
│ Reserved │ 0x001 │ 0x0001 │ 0x00000001 │
│ Bad cluster │ 0xFF7 │ 0xFFF7 │ 0x0FFFFFF7 │
│ End of chain │ 0xFF8-0xFFF │ 0xFFF8-0xFFFF │ 0x0FFFFFF8+ │
└──────────────┴───────────────┴───────────────┴────────────────┘
/* fat.c - FAT table operations */
/**
* FAT driver context
*/
typedef struct {
block_device_t* device;
fat32_boot_t bpb;
uint32_t fat_start_lba; /* LBA of first FAT */
uint32_t data_start_lba; /* LBA of first data cluster */
uint32_t root_dir_lba; /* LBA of root directory */
uint32_t total_clusters;
uint8_t fat_type; /* 12, 16, or 32 */
} fat_fs_t;
/**
* Read a FAT entry (get next cluster in chain)
*/
uint32_t fat_get_entry(fat_fs_t* fs, uint32_t cluster) {
uint32_t fat_offset;
uint32_t fat_sector;
uint32_t entry_offset;
uint8_t sector[512];
if (fs->fat_type == 32) {
fat_offset = cluster * 4;
} else if (fs->fat_type == 16) {
fat_offset = cluster * 2;
} else { /* FAT12 */
fat_offset = cluster + (cluster / 2); /* cluster * 1.5 */
}
fat_sector = fs->fat_start_lba + (fat_offset / 512);
entry_offset = fat_offset % 512;
/* Read FAT sector */
fs->device->read(fs->device, fat_sector, 1, sector);
if (fs->fat_type == 32) {
return (*(uint32_t*)§or[entry_offset]) & 0x0FFFFFFF;
} else if (fs->fat_type == 16) {
return *(uint16_t*)§or[entry_offset];
} else { /* FAT12 */
uint16_t val = *(uint16_t*)§or[entry_offset];
if (cluster & 1) {
return val >> 4; /* Odd cluster: high 12 bits */
} else {
return val & 0x0FFF; /* Even cluster: low 12 bits */
}
}
}
/**
* Check if cluster marks end of chain
*/
int fat_is_eof(fat_fs_t* fs, uint32_t cluster) {
if (fs->fat_type == 32) return cluster >= 0x0FFFFFF8;
if (fs->fat_type == 16) return cluster >= 0xFFF8;
return cluster >= 0xFF8; /* FAT12 */
}
/**
* Convert cluster number to LBA
*/
uint32_t fat_cluster_to_lba(fat_fs_t* fs, uint32_t cluster) {
return fs->data_start_lba +
(cluster - 2) * fs->bpb.sectors_per_cluster;
}
Directory Entries
Directories are files containing 32-byte entries. Each entry describes a file or subdirectory.
/* FAT Directory Entry (32 bytes) */
typedef struct {
uint8_t name[8]; /* 8.3 filename (space padded) */
uint8_t ext[3]; /* Extension */
uint8_t attr; /* Attributes */
uint8_t reserved; /* Windows NT: lowercase flags */
uint8_t create_time_ms; /* Creation time (ms) */
uint16_t create_time; /* Creation time */
uint16_t create_date; /* Creation date */
uint16_t access_date; /* Last access date */
uint16_t cluster_high; /* High 16 bits of cluster (FAT32) */
uint16_t modify_time; /* Last modification time */
uint16_t modify_date; /* Last modification date */
uint16_t cluster_low; /* Low 16 bits of starting cluster */
uint32_t file_size; /* File size in bytes */
} __attribute__((packed)) fat_dir_entry_t;
/* Attribute flags */
#define FAT_ATTR_READ_ONLY 0x01
#define FAT_ATTR_HIDDEN 0x02
#define FAT_ATTR_SYSTEM 0x04
#define FAT_ATTR_VOLUME_ID 0x08 /* Volume label entry */
#define FAT_ATTR_DIRECTORY 0x10
#define FAT_ATTR_ARCHIVE 0x20
#define FAT_ATTR_LFN 0x0F /* Long filename entry */
/* Special first-byte values */
#define FAT_ENTRY_FREE 0xE5 /* Deleted entry */
#define FAT_ENTRY_END 0x00 /* End of directory */
#define FAT_ENTRY_KANJI 0x05 /* Actually 0xE5 (Kanji) */
/**
* Get starting cluster from directory entry
*/
uint32_t fat_entry_cluster(fat_dir_entry_t* entry) {
return ((uint32_t)entry->cluster_high << 16) | entry->cluster_low;
}
/**
* Parse 8.3 filename from directory entry
*/
void fat_parse_filename(fat_dir_entry_t* entry, char* out) {
int i, j = 0;
/* Copy name (remove trailing spaces) */
for (i = 0; i < 8 && entry->name[i] != ' '; i++) {
out[j++] = entry->name[i];
}
/* Add dot and extension if present */
if (entry->ext[0] != ' ') {
out[j++] = '.';
for (i = 0; i < 3 && entry->ext[i] != ' '; i++) {
out[j++] = entry->ext[i];
}
}
out[j] = '\0';
}
File Operations
/**
* List directory contents
*/
void fat_list_directory(fat_fs_t* fs, uint32_t cluster) {
uint8_t sector[512];
fat_dir_entry_t* entries;
char filename[13];
uint32_t lba = fat_cluster_to_lba(fs, cluster);
/* Read directory cluster */
for (int s = 0; s < fs->bpb.sectors_per_cluster; s++) {
fs->device->read(fs->device, lba + s, 1, sector);
entries = (fat_dir_entry_t*)sector;
for (int i = 0; i < 16; i++) { /* 16 entries per sector */
if (entries[i].name[0] == FAT_ENTRY_END) {
return; /* End of directory */
}
if (entries[i].name[0] == FAT_ENTRY_FREE) {
continue; /* Deleted entry */
}
if (entries[i].attr == FAT_ATTR_LFN) {
continue; /* Long filename (skip for now) */
}
if (entries[i].attr & FAT_ATTR_VOLUME_ID) {
continue; /* Volume label */
}
fat_parse_filename(&entries[i], filename);
if (entries[i].attr & FAT_ATTR_DIRECTORY) {
kprintf(" <DIR> %s\n", filename);
} else {
kprintf(" %8d %s\n", entries[i].file_size, filename);
}
}
}
/* Follow cluster chain for larger directories */
uint32_t next = fat_get_entry(fs, cluster);
if (!fat_is_eof(fs, next)) {
fat_list_directory(fs, next);
}
}
/**
* Find file in directory by name
* @return Starting cluster, or 0 if not found
*/
uint32_t fat_find_file(fat_fs_t* fs, uint32_t dir_cluster,
const char* name) {
uint8_t sector[512];
fat_dir_entry_t* entries;
char filename[13];
uint32_t cluster = dir_cluster;
while (!fat_is_eof(fs, cluster)) {
uint32_t lba = fat_cluster_to_lba(fs, cluster);
for (int s = 0; s < fs->bpb.sectors_per_cluster; s++) {
fs->device->read(fs->device, lba + s, 1, sector);
entries = (fat_dir_entry_t*)sector;
for (int i = 0; i < 16; i++) {
if (entries[i].name[0] == FAT_ENTRY_END) {
return 0; /* Not found */
}
if (entries[i].name[0] == FAT_ENTRY_FREE) {
continue;
}
if (entries[i].attr == FAT_ATTR_LFN) {
continue;
}
fat_parse_filename(&entries[i], filename);
/* Case-insensitive compare */
if (strcasecmp(filename, name) == 0) {
return fat_entry_cluster(&entries[i]);
}
}
}
cluster = fat_get_entry(fs, cluster);
}
return 0; /* Not found */
}
/**
* Read file contents into buffer
*/
int fat_read_file(fat_fs_t* fs, uint32_t cluster,
uint8_t* buffer, uint32_t size) {
uint32_t bytes_read = 0;
uint32_t cluster_size = fs->bpb.sectors_per_cluster * 512;
while (!fat_is_eof(fs, cluster) && bytes_read < size) {
uint32_t lba = fat_cluster_to_lba(fs, cluster);
uint32_t to_read = (size - bytes_read < cluster_size) ?
(size - bytes_read) : cluster_size;
/* Read cluster (may be multiple sectors) */
for (int s = 0; s < fs->bpb.sectors_per_cluster; s++) {
fs->device->read(fs->device, lba + s, 1,
buffer + bytes_read + s * 512);
}
bytes_read += cluster_size;
cluster = fat_get_entry(fs, cluster);
}
return bytes_read;
}
Long Filenames (LFN): FAT supports names longer than 8.3 using special directory entries with attribute 0x0F. These "VFAT" entries store up to 13 UTF-16 characters each, placed before the short name entry. Implementing LFN support is a good exercise!
Virtual File System (VFS)
VFS Design
The Virtual File System provides a common interface for all filesystems. Programs use the same open(), read(), write() calls whether accessing FAT, ext2, or network files.
VFS ABSTRACTION LAYER
═══════════════════════════════════════════════════════════════
APPLICATION
│
│ open("/mnt/usb/file.txt", O_RDONLY)
│
▼
╔═══════════════════════════════════════════════════════════╗
║ VFS LAYER ║
║ ║
║ Path resolution: "/" → root_fs ║
║ "/mnt/usb" → mounted FAT ║
║ ║
║ File descriptors: fd 0 = stdin (tty) ║
║ fd 1 = stdout (tty) ║
║ fd 3 = /mnt/usb/file.txt (FAT) ║
║ ║
║ Unified operations: ║
║ vfs_open(), vfs_read(), vfs_write(), vfs_close() ║
║ vfs_mkdir(), vfs_readdir(), vfs_stat() ║
╚════════════════════════════════════════════════════════════╝
│ │ │ │
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ FAT │ │ ext2 │ │ devfs │ │ procfs│
│driver │ │driver │ │(devices)│ │(/proc)│
└───────┘ └───────┘ └───────┘ └───────┘
/* vfs.h - Virtual File System interface */
#define VFS_NAME_MAX 256
#define VFS_PATH_MAX 4096
/* File types */
#define VFS_FILE 0x01
#define VFS_DIRECTORY 0x02
#define VFS_CHARDEVICE 0x03
#define VFS_BLOCKDEVICE 0x04
#define VFS_PIPE 0x05
#define VFS_SYMLINK 0x06
#define VFS_MOUNTPOINT 0x08
/* Forward declarations */
struct vfs_node;
struct dirent;
/* VFS operation function types */
typedef uint32_t (*read_fn)(struct vfs_node*, uint32_t off,
uint32_t size, uint8_t* buf);
typedef uint32_t (*write_fn)(struct vfs_node*, uint32_t off,
uint32_t size, const uint8_t* buf);
typedef void (*open_fn)(struct vfs_node*);
typedef void (*close_fn)(struct vfs_node*);
typedef struct dirent* (*readdir_fn)(struct vfs_node*, uint32_t index);
typedef struct vfs_node* (*finddir_fn)(struct vfs_node*, const char* name);
/* VFS Node - represents a file or directory in the tree */
typedef struct vfs_node {
char name[VFS_NAME_MAX]; /* Node name */
uint32_t mask; /* Permissions */
uint32_t uid; /* Owning user */
uint32_t gid; /* Owning group */
uint32_t flags; /* Node type flags */
uint32_t inode; /* Filesystem-specific inode */
uint32_t length; /* Size in bytes */
uint32_t impl; /* Implementation-specific data */
/* Operations - set by filesystem driver */
read_fn read;
write_fn write;
open_fn open;
close_fn close;
readdir_fn readdir;
finddir_fn finddir;
struct vfs_node* ptr; /* For mountpoints/symlinks */
} vfs_node_t;
/* Directory entry returned by readdir */
typedef struct dirent {
char name[VFS_NAME_MAX];
uint32_t ino; /* Inode number */
} dirent_t;
/* The root of the filesystem tree */
extern vfs_node_t* vfs_root;
/* VFS API */
vfs_node_t* vfs_open(const char* path, uint32_t flags);
void vfs_close(vfs_node_t* node);
uint32_t vfs_read(vfs_node_t* node, uint32_t off, uint32_t size, uint8_t* buf);
uint32_t vfs_write(vfs_node_t* node, uint32_t off, uint32_t size, const uint8_t* buf);
dirent_t* vfs_readdir(vfs_node_t* node, uint32_t index);
vfs_node_t* vfs_finddir(vfs_node_t* node, const char* name);
VFS Nodes
/* vfs.c - VFS implementation */
#include "vfs.h"
#include <string.h>
vfs_node_t* vfs_root = NULL;
/**
* Read from a file
*/
uint32_t vfs_read(vfs_node_t* node, uint32_t offset,
uint32_t size, uint8_t* buffer) {
if (node && node->read) {
return node->read(node, offset, size, buffer);
}
return 0;
}
/**
* Write to a file
*/
uint32_t vfs_write(vfs_node_t* node, uint32_t offset,
uint32_t size, const uint8_t* buffer) {
if (node && node->write) {
return node->write(node, offset, size, buffer);
}
return 0;
}
/**
* Open a file (increment refcount, etc.)
*/
void vfs_open(vfs_node_t* node) {
if (node && node->open) {
node->open(node);
}
}
/**
* Close a file
*/
void vfs_close(vfs_node_t* node) {
if (node && node->close) {
node->close(node);
}
}
/**
* Read directory entry at index
*/
dirent_t* vfs_readdir(vfs_node_t* node, uint32_t index) {
/* Check this is actually a directory */
if (node && (node->flags & 0x7) == VFS_DIRECTORY && node->readdir) {
return node->readdir(node, index);
}
return NULL;
}
/**
* Find file in directory by name
*/
vfs_node_t* vfs_finddir(vfs_node_t* node, const char* name) {
if (node && (node->flags & 0x7) == VFS_DIRECTORY && node->finddir) {
return node->finddir(node, name);
}
return NULL;
}
/**
* Resolve a path to a VFS node
* e.g., "/home/user/file.txt" → vfs_node_t*
*/
vfs_node_t* vfs_namei(const char* path) {
if (!path || path[0] != '/') {
return NULL; /* Must be absolute path */
}
vfs_node_t* node = vfs_root;
char component[VFS_NAME_MAX];
int i = 1; /* Skip leading slash */
while (path[i]) {
/* Extract path component */
int j = 0;
while (path[i] && path[i] != '/') {
component[j++] = path[i++];
}
component[j] = '\0';
if (j == 0) {
i++; /* Skip consecutive slashes */
continue;
}
/* Handle . and .. */
if (strcmp(component, ".") == 0) {
if (path[i]) i++;
continue;
}
/* TODO: handle ".." by tracking parent */
/* Look up component in current directory */
node = vfs_finddir(node, component);
if (!node) {
return NULL; /* Not found */
}
/* Follow mount points */
if (node->flags & VFS_MOUNTPOINT) {
node = node->ptr;
}
if (path[i]) i++; /* Skip slash */
}
return node;
}
Mounting
Mounting attaches a filesystem to a directory in the tree. The mount point becomes the root of the mounted filesystem.
/* Mount table */
typedef struct mount_point {
char path[VFS_PATH_MAX];
vfs_node_t* node; /* Covering node (mount point) */
vfs_node_t* root; /* Root of mounted filesystem */
struct mount_point* next;
} mount_point_t;
static mount_point_t* mount_table = NULL;
/**
* Mount a filesystem
* @param path Mount point (must exist as directory)
* @param fs_root Root node of filesystem to mount
*/
int vfs_mount(const char* path, vfs_node_t* fs_root) {
/* Find mount point directory */
vfs_node_t* mount_node = vfs_namei(path);
if (!mount_node || !(mount_node->flags & VFS_DIRECTORY)) {
return -1; /* Mount point must be existing directory */
}
/* Create mount entry */
mount_point_t* mp = kmalloc(sizeof(mount_point_t));
strcpy(mp->path, path);
mp->node = mount_node;
mp->root = fs_root;
mp->next = mount_table;
mount_table = mp;
/* Mark node as mount point */
mount_node->flags |= VFS_MOUNTPOINT;
mount_node->ptr = fs_root;
kprintf("VFS: Mounted filesystem at %s\n", path);
return 0;
}
/**
* Unmount a filesystem
*/
int vfs_unmount(const char* path) {
mount_point_t** pp = &mount_table;
while (*pp) {
if (strcmp((*pp)->path, path) == 0) {
mount_point_t* mp = *pp;
/* Clear mount point flag */
mp->node->flags &= ~VFS_MOUNTPOINT;
mp->node->ptr = NULL;
/* Remove from list */
*pp = mp->next;
kfree(mp);
kprintf("VFS: Unmounted %s\n", path);
return 0;
}
pp = &(*pp)->next;
}
return -1; /* Not mounted */
}
Example: Initializing the Filesystem
void fs_init(void) {
/* Initialize ATA driver */
ata_init();
/* Initialize VFS with a simple root */
vfs_root = initrd_init(); /* Initial ramdisk as root */
/* Parse MBR on first ATA drive */
mbr_parse(&ata_device);
/* Mount FAT partition */
fat_fs_t* fat = fat_init(&ata_device, partition_start);
if (fat) {
vfs_mount("/mnt/disk", fat->root);
}
/* Now we can access files! */
vfs_node_t* file = vfs_namei("/mnt/disk/README.TXT");
if (file) {
char buffer[512];
uint32_t bytes = vfs_read(file, 0, 511, buffer);
buffer[bytes] = '\0';
kprintf("File contents:\n%s\n", buffer);
}
}
Integration
What You Can Build
Phase 7 Project: A kernel that can read and write files! Your OS now has persistent storage, can access disk drives, navigate directories, and manage files through a unified VFS interface.
Project: File Browser Shell Commands
Add these commands to your shell:
/* shell_fs.c - Filesystem shell commands */
/**
* ls - List directory contents
*/
void cmd_ls(const char* path) {
if (!path) path = ".";
vfs_node_t* dir = vfs_namei(path);
if (!dir) {
kprintf("ls: cannot access '%s': No such file or directory\n", path);
return;
}
if ((dir->flags & 0x7) != VFS_DIRECTORY) {
kprintf("ls: '%s': Not a directory\n", path);
return;
}
kprintf("Contents of %s:\n", path);
kprintf("%-12s %8s %s\n", "TYPE", "SIZE", "NAME");
kprintf("%-12s %8s %s\n", "----", "----", "----");
uint32_t index = 0;
dirent_t* entry;
while ((entry = vfs_readdir(dir, index++))) {
vfs_node_t* child = vfs_finddir(dir, entry->name);
const char* type = "???";
if (child) {
switch (child->flags & 0x7) {
case VFS_FILE: type = "FILE"; break;
case VFS_DIRECTORY: type = "DIR"; break;
case VFS_CHARDEVICE: type = "CHAR"; break;
case VFS_BLOCKDEVICE:type = "BLOCK"; break;
}
}
kprintf("%-12s %8d %s\n", type,
child ? child->length : 0,
entry->name);
}
}
/**
* cat - Display file contents
*/
void cmd_cat(const char* path) {
if (!path) {
kprintf("Usage: cat <filename>\n");
return;
}
vfs_node_t* file = vfs_namei(path);
if (!file) {
kprintf("cat: '%s': No such file\n", path);
return;
}
if ((file->flags & 0x7) != VFS_FILE) {
kprintf("cat: '%s': Not a regular file\n", path);
return;
}
uint8_t* buffer = kmalloc(file->length + 1);
uint32_t bytes = vfs_read(file, 0, file->length, buffer);
buffer[bytes] = '\0';
kprintf("%s", buffer);
if (bytes > 0 && buffer[bytes-1] != '\n') {
kprintf("\n");
}
kfree(buffer);
}
/**
* hexdump - Display file in hex
*/
void cmd_hexdump(const char* path) {
vfs_node_t* file = vfs_namei(path);
if (!file) {
kprintf("hexdump: '%s': No such file\n", path);
return;
}
uint8_t buffer[256];
uint32_t offset = 0;
uint32_t bytes;
while (offset < file->length && offset < 256) {
bytes = vfs_read(file, offset, 16, buffer);
if (bytes == 0) break;
kprintf("%08X: ", offset);
/* Hex bytes */
for (int i = 0; i < 16; i++) {
if (i < bytes) {
kprintf("%02X ", buffer[i]);
} else {
kprintf(" ");
}
if (i == 7) kprintf(" ");
}
/* ASCII */
kprintf(" |");
for (int i = 0; i < bytes; i++) {
char c = buffer[i];
kprintf("%c", (c >= 32 && c < 127) ? c : '.');
}
kprintf("|\n");
offset += bytes;
}
}
Exercises
Exercise 1: File Write Support
Implement FAT file writing: find free clusters, update the FAT chain, and write directory entries for new files. Add a echo "text" > file shell command.
Intermediate
Exercise 2: Block Cache
Implement a block cache that keeps recently-read sectors in memory. Use a hash table for O(1) lookups and LRU eviction when the cache is full.
Performance
Exercise 3: Long Filename Support
Add VFAT long filename support to the FAT driver. Parse LFN entries (attribute 0x0F) and reconstruct UTF-16 filenames. This lets you access files with names longer than 8.3.
Feature
Exercise 4: Initial Ramdisk (initrd)
Create a simple filesystem in memory (loaded by bootloader). Store essential files like shell and config before real disk is mounted. GRUB's tar-based initrd is a good model.
Boot
Next Steps
With memory and files working, it's time for the big one: processes. In Phase 8, we'll implement task switching, system calls, and user mode so programs can run independently.
PHASE 8 PREVIEW: PROCESSES & USER MODE
═══════════════════════════════════════════════════════════════
Your kernel can now:
✓ Read/write disk sectors (ATA driver)
✓ Navigate FAT filesystem
✓ Access files through VFS abstraction
✓ Mount multiple filesystems
Next, you'll build:
┌─────────────────────────────────────────────────────────┐
│ PROCESS MANAGEMENT │
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ KERNEL SPACE (Ring 0) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Scheduler │ │ Memory │ │ File │ │
│ │ (picks │ │ Manager │ │ System │ │
│ │ next task)│ │ (per-proc) │ │ (VFS) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼────────────────▼────────────────▼──────┐ │
│ │ SYSTEM CALL INTERFACE │ │
│ │ fork(), exec(), read(), write(), exit() │ │
│ └──────────────────────┬────────────────────────┘ │
╠══════════════════════════╪════════════════════════════╣
│ USER SPACE (Ring 3) │ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Process │ │ Process │ │ Process │ │
│ │ PID 1 │ │ PID 2 │ │ PID 3 │ │
│ │ (init) │ │ (shell) │ │ (app) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────┘
You'll implement:
• Process Control Block (PCB) - task state
• Context switching - save/restore registers
• System calls - int 0x80 or syscall instruction
• User mode - Ring 3 with memory protection
• fork() / exec() / exit() / wait()
Key Takeaways from Phase 7:
- Block Devices: Disks are accessed in sectors (512 bytes); you can't read individual bytes directly
- ATA PIO: Simple polling-based I/O; production systems use DMA for performance
- Partition Tables: MBR for legacy (4 partitions, 2TB limit), GPT for modern (128 partitions, huge disks)
- FAT Filesystem: Cluster chains in FAT table, 32-byte directory entries, supports FAT12/16/32
- VFS Abstraction: Unified interface for all filesystems; operations as function pointers
- Mounting: Attaches filesystem to directory, path resolution follows mount points
Continue the Series
Phase 6: Memory Management
Review paging, virtual memory, and heap allocation.
Read Article
Phase 8: Processes & User Mode
Implement task switching, system calls, and run code in user space.
Read Article
Phase 9: ELF Loading & Executables
Parse ELF files and load executable programs into memory.
Read Article