BangOS Virtual File System (VFS) & ext2 Filesystem Engine
This document details the software architecture, on-disk geometry math, POSIX system call layer, and didactic implementation of the Second Extended Filesystem (ext2) and Virtual File System (VFS) in BangOS.
1. Virtual File System (VFS) Architecture
The BangOS VFS provides a unified, hierarchical abstraction over disparate storage mechanisms, including in-memory TarFS ramdisks and on-disk ext2 volumes.
flowchart TD
Userland["Userland POSIX Application (/bin/disktool)"] -->|"open, read, write, stat, close"| Syscall["Syscall Dispatch Layer"]
Syscall -->|"fd_table[fd] -> file_desc_t"| VFS["Virtual File System (VFS Core)"]
VFS -->|"vfs_lookup / vfs_mount"| Mounts["Mount Table (/ & /mnt/ext2)"]
Mounts -->|"vfs_ops_t: tarfs_ops"| TarFS["In-Memory Ramdisk (TarFS)"]
Mounts -->|"vfs_ops_t: ext2_ops"| ext2["ext2 Filesystem Driver"]
ext2 -->|"block_dev_t read/write_blocks"| BlockDev["Block Device Abstraction"]
BlockDev -->|"ATA PIO inw/outw"| ATADriver["ATA / IDE Storage Driver"]
1.1 Process File Descriptor Table
Each process_t retains a private file descriptor table fd_table[VFS_MAX_FD]:
- FD 0: Standard Input (
stdin-> UART/Keyboard) - FD 1: Standard Output (
stdout-> UART serial console) - FD 2: Standard Error (
stderr-> UART serial console) - FD 3..31: Open files managed by
file_desc_t, tracking current seek offset and access mode flags (O_RDONLY,O_WRONLY,O_CREAT,O_APPEND).
1.2 Path Resolution (vfs_lookup)
Given a path such as "/mnt/ext2/docs/architecture.txt":
vfs_find_mount()selects the longest prefix match ("/mnt/ext2"), yielding relative subpath"docs/architecture.txt".- The VFS starts from
mount->root_node(Root Inode 2). - Sequentially tokenizes components (
"docs", then"architecture.txt"), invokingcurr->ops->finddir()on each directory node until target file node is resolved.
2. Second Extended Filesystem (ext2) On-Disk Geometry
ext2 organizes storage into fixed-size Blocks (1024, 2048, or 4096 bytes) grouped into Block Groups.
+---------------+-------------------+-------------------+-------------------+
| Boot Sector | Block Group 0 | Block Group 1 | Block Group N |
| (Bytes 0-1023)| | | |
+---------------+-------------------+-------------------+-------------------+
2.1 Block Group Layout (for 1024-byte Block Size)
| Block Index | LBA Offset | Contents |
|---|---|---|
| Block 0 | LBA 0-1 | Legacy x86 Boot Record / MBR partition table |
| Block 1 | LBA 2-3 | Superblock (1024 bytes, Magic 0xEF53) |
| Block 2 | LBA 4-5 | Block Group Descriptor Table (BGD) |
| Block 3 | LBA 6-7 | Block Usage Bitmap (1 bit per data block) |
| Block 4 | LBA 8-9 | Inode Usage Bitmap (1 bit per inode) |
| Block 5..N | LBA 10.. | Inode Table (Array of 128-byte ext2_inode_t) |
| Block N+1.. | ... | File and Directory Data Blocks |
2.2 Superblock Structure (ext2_super_block_t)
Located at offset 1024 bytes (LBA 2):
typedef struct ext2_super_block {
uint32_t s_inodes_count; // Total inodes count
uint32_t s_blocks_count; // Total blocks count
uint32_t s_r_blocks_count; // Reserved blocks count
uint32_t s_free_blocks_count; // Free blocks count
uint32_t s_free_inodes_count; // Free inodes count
uint32_t s_first_data_block; // 1 for 1KB blocks, 0 for >1KB
uint32_t s_log_block_size; // Block size = 1024 << s_log_block_size
...
uint16_t s_magic; // Magic signature: 0xEF53
uint16_t s_state; // 1 = Cleanly unmounted
uint16_t s_errors; // Error behavior
uint16_t s_minor_rev_level; // Minor revision level
...
uint16_t s_inode_size; // Size of on-disk inode structure (e.g., 128 bytes)
} ext2_super_block_t;
3. Inode Architecture and Block Addressing Math
Every file, directory, or symlink is described by an Inode (ext2_inode_t, 128 bytes).
3.1 Inode Block Pointers (i_block[15])
i_block[0..11] : Direct Block Pointers (Pointers to blocks 0 through 11)
i_block[12] : Single Indirect Pointer (Points to block containing array of uint32_t block pointers)
i_block[13] : Double Indirect Pointer (Points to block containing array of indirect block pointers)
i_block[14] : Triple Indirect Pointer
flowchart TD
Inode["ext2_inode_t"] --> Direct["i_block 0..11"]
Direct --> DBlk["Data Blocks 0..11"]
Inode --> SInd["i_block 12 (Single Indirect)"]
SInd --> SIndBlk["Indirect Block Table"]
SIndBlk --> DBlk2["Data Blocks 12..267"]
Inode --> DInd["i_block 13 (Double Indirect)"]
DInd --> DIndBlk["Double Indirect Table"]
DIndBlk --> SIndBlk2["Indirect Tables"]
SIndBlk2 --> DBlk3["Data Blocks 268.."]
3.2 File Offset to Physical Block Translation
Given file logical block index $B$:
- If $B < 12$: $$\text{Physical Block} = \text{inode.i_block}[B]$$
- If $12 \le B < 12 + \frac{\text{BlockSize}}{4}$: $$B' = B - 12$$ $$\text{Table} = \text{ReadBlock}(\text{inode.i_block}[12])$$ $$\text{Physical Block} = \text{Table}[B']$$
- If $B \ge 12 + \frac{\text{BlockSize}}{4}$:
Traverse double indirect pointer table in
i_block[13].
4. Directory Structure & File Allocation
Directory entries use variable-length records:
typedef struct ext2_dir_entry_2 {
uint32_t inode; // Inode number (0 = unused entry)
uint16_t rec_len; // Directory entry length in bytes
uint8_t name_len; // File name length (1-255)
uint8_t file_type; // 1 = Regular File, 2 = Directory
char name[255]; // File name string (not null-terminated on disk)
} __attribute__((packed)) ext2_dir_entry_2_t;
4.1 Creating Files (O_CREAT)
- Inode Allocation (
ext2_alloc_inode): Scans Inode Bitmap for first clear bit, marks bit as used, and decrements free counters. - Inode Initialization: Sets file permissions (
0644), type (EXT2_S_IFREG), size (0), and links (1). - Directory Record Split: Finds the last directory record in parent directory with excess padding in
rec_len, reduces itsrec_lento actual rounded name size, and appends the new entry record in the remaining space.
5. POSIX System Call Reference
BangOS Ring 3 applications interact with storage using standard POSIX syscall numbers dispatched via x86_64 syscall:
| Syscall | RAX | Arguments | Description |
|---|---|---|---|
read |
0 | rdi: fd, rsi: buf, rdx: count |
Read data from open file descriptor |
write |
1 | rdi: fd, rsi: buf, rdx: count |
Write data to file descriptor |
open |
2 | rdi: path, rsi: flags, rdx: mode |
Open or create file node in VFS |
close |
3 | rdi: fd |
Close open file descriptor |
stat |
4 | rdi: path, rsi: statbuf |
Retrieve metadata for path |
fstat |
5 | rdi: fd, rsi: statbuf |
Retrieve metadata for open file descriptor |
lseek |
8 | rdi: fd, rsi: offset, rdx: whence |
Reposition file read/write offset |
getdents64 |
217 | rdi: fd, rsi: dirp, rdx: count |
Read directory entries |
openat |
257 | rdi: dirfd, rsi: path, rdx: flags, r10: mode |
Relative/absolute open |
newfstatat |
262 | rdi: dirfd, rsi: path, rdx: statbuf, r10: flags |
Relative/absolute stat |
6. FAT32 Architecture & Future Roadmap
To extend BangOS with FAT32 support:
- FAT32 Geometry: Parse Volume Boot Record (VBR) at LBA 0 (Bytes Per Sector, Sectors Per Cluster, Reserved Sector Count, Number of FATs).
- Cluster Chaining: Read File Allocation Table (
FAT1/FAT2) where each 32-bit entry points to the next cluster in the chain (0x0FFFFFF8= End of Cluster Chain). - VFS Mount: Register
fat32_mount_device(dev, "/mnt/fat32", &root)providingvfs_ops_tfor FAT32 8.3 and LFN directory lookups.