Phase 24
Persistent Storage
Give m3OS a persistent block device so that files written during one boot session survive into the next. A virtio-blk driver (the simplest PCI virtio device in QEMU) handles raw sector I/O; a partition parser finds the FAT32 data partition; the existing `fat_server` gains a write path that flushes directly to disk. After this phase, writing a file to `/data` and rebooting leaves the file visible on the next boot (and on the host via `losetup` or similar).
Learning goal
Understand how virtio devices are discovered, initialized, and driven through virtqueues over the PCI bus.
Category
infrastructure
Components touched
Builds on
Unlocked by this phase
Why this phase matters
Give m3OS a persistent block device so that files written during one boot session survive into the next. A virtio-blk driver (the simplest PCI virtio device in QEMU) handles raw sector I/O; a partition parser finds the FAT32 data partition; the existing fat_server gains a write path that flushes directly to disk. After this phase, writing a file to /data and rebooting leaves the file visible on the next boot (and on the host via losetup or similar).
How it works in m3OS
Phase 24 gives m3OS persistent block storage via a virtio-blk disk. The current storage model uses an ext2 filesystem mounted at / as the root filesystem. Init mounts the ext2 volume at boot (mount /dev/blk0 / ext2), making all paths — /etc, /var, /home — persistent across reboots (backed by the QEMU disk image disk.img).
The original Phase 24 implementation used a 64 MB FAT32 partition mounted at /data. The FAT32 path routing (/data/... → FAT32 volume) remains as a legacy fallback, but all new code and service configuration targets the ext2 root.
What persists where:
Checking storage health: df / shows ext2 capacity and usage. stat <path> confirms whether a file is on the ext2 volume. If init reports ”/ mount failed”, the ext2 partition may be corrupted — recreate the disk with cargo xtask clean followed by cargo xtask run.
How it builds on earlier phases
This phase builds on Socket API and turns that foundation into Persistent Storage.
Implementation outline
- Update
cargo xtask imageto produce a two-partitiondisk.img: an ESP holding the UEFI boot files and an empty FAT32 data partition. Usemtoolsor manual sector writes to initialize both FAT32 volumes. - Add the QEMU
-driveargument incargo xtask runso QEMU exposes the virtio-blk device at PCI address 0:2.0 (or wherever the bus scan lands it). - Implement PCI device lookup in
blk_server: scan the kernel’s cached PCI device list for VID=0x1AF4, DID=0x1001; map the I/O BAR. - Initialize the virtio-blk device: reset, acknowledge, set DRIVER status, negotiate features (only
BLK_F_SIZE_MAX/BLK_F_SEG_MAXas needed), set DRIVER_OK. - Set up the request virtqueue: allocate a descriptor ring, available ring, and used ring in contiguous physical memory; write the queue addresses into the PCI BAR registers.
- Implement
read_sectorsandwrite_sectors: build a three-descriptor chain (header + data buffer + status byte), kick the queue, spin-poll the used ring for completion, check the status byte. - Implement MBR partition parsing in
blk_server: read sector 0, scan the four partition entries, return the LBA offset and sector count for the FAT32 data partition. - Extend
fat_serverwith a write path: cluster allocation from the free FAT entries, chain extension, directory entry creation, and FAT table flush after each mutation. - Add the
sys_mountsyscall: on first call withfstype="vfat", triggerblk_serverinitialization, run MBR parsing, and register the FAT32 backend withvfs_serverat the requested mount point. - Wire
initto callmount("/dev/blk0p1", "/data", "vfat", 0, "")after all servers are running. - Add a shell built-in
mountthat prints the active mount table to verify.
Task progress snapshot
No checklist tasks were discovered for this phase yet.
Documentation targets
- Documentation deliverables have not been listed yet.
Deferred follow-on work
- NVMe, AHCI/SATA, or USB mass storage drivers
- virtio 1.0 / modern interface (packed virtqueues, capability-based config)
- page cache and write-back buffering
- GPT partition parsing (MBR only required)
- journaling, copy-on-write, or any crash-consistency guarantee beyond FAT32’s inherent fragility
fsckintegration or filesystem repair- block device multiplexing (more than one physical disk)
- encrypted volumes (dm-crypt equivalent)
mmapof file-backed pages- file permissions, ownership, or access control
Source materials
Important code
Code spotlight cards
Code spotlight
ext2 volume driver (kernel/src/fs/ext2.rs) — superblock, block groups,
ext2 volume driver (kernel/src/fs/ext2.rs) — superblock, block groups,
Code spotlight
virtio-blk driver (kernel/src/blk/virtio_blk.rs) — sector-level I/O via
virtio-blk driver (kernel/src/blk/virtio_blk.rs) — sector-level I/O via
Key files
- kernel/src/fs/ext2.rs — ext2 volume driver (kernel/src/fs/ext2.rs) — superblock, block groups,
- kernel/src/blk/virtio_blk.rs — virtio-blk driver (kernel/src/blk/virtio_blk.rs) — sector-level I/O via
How real OSes differ
- Production block I/O stacks are built around asynchronous submission queues (Linux `io_uring`, NVMe submission/completion queues, Windows I/O completion ports) so that a thread is never stalled waiting for a single sector.
- The page cache decouples the filesystem from disk latency: writes land in RAM first and are flushed in batches by writeback threads.
- FAT32 itself is rarely used for root filesystems; production systems use journaling filesystems (ext4, XFS, APFS, NTFS) or log-structured designs (F2FS, btrfs) that survive power loss without corruption.
- The virtio 1.0 split-driver model (and its modern PCIe variant with packed virtqueues) replaces the legacy BAR-based interface used here.
- This phase deliberately keeps every layer synchronous and stateless to make the control flow auditable in a single read-through.
Success criteria
- `cargo xtask image` produces a `disk.img` with two valid FAT32 partitions that `fsck.fat` (run on the host) reports as clean.
- QEMU boots without regression; `blk_server` logs its PCI address and the detected data partition LBA at boot.
- A file written to `/data/hello.txt` inside the shell is visible on the next boot without re-initialization.
- The same file is readable from the host after shutdown via `losetup` + `mount` or `mtools mcopy`.
- `mkdir`, `unlink`, and `rename` all work inside `/data` and survive a reboot.
- Writing a file larger than one FAT32 cluster (4 KB) allocates a correct cluster chain; the file reads back without corruption.
- The UEFI ESP boot path is unaffected; the OS boots from the same `disk.img` that holds the data partition.
Related phases
Keep exploring the roadmap
Phase 23
CompleteSocket API
Expose the kernel's existing TCP/IP stack to userspace via standard Linux socket syscalls. At the end of this phase a userspace program can open a TCP connection, send an HTTP/1.0 request, and receive the response — entirely from ring-3 code, with no kernel modifications needed for each new network program. The `ping` shell builtin moves out of the kernel and into a standalone userspace ELF.
Phase 25
CompleteSMP
Boot all available CPU cores, give each its own Local APIC timer, and run the scheduler across all cores simultaneously. Two processes should be able to execute truly in parallel.