m³OS docs GitHub

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).

Complete

Learning goal

Understand how virtio devices are discovered, initialized, and driven through virtqueues over the PCI bus.

Category

infrastructure

Components touched

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 image to produce a two-partition disk.img: an ESP holding the UEFI boot files and an empty FAT32 data partition. Use mtools or manual sector writes to initialize both FAT32 volumes.
  • Add the QEMU -drive argument in cargo xtask run so 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_MAX as 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_sectors and write_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_server with 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_mount syscall: on first call with fstype="vfat", trigger blk_server initialization, run MBR parsing, and register the FAT32 backend with vfs_server at the requested mount point.
  • Wire init to call mount("/dev/blk0p1", "/data", "vfat", 0, "") after all servers are running.
  • Add a shell built-in mount that 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
  • fsck integration or filesystem repair
  • block device multiplexing (more than one physical disk)
  • encrypted volumes (dm-crypt equivalent)
  • mmap of 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,

kernel/src/fs/ext2.rs See file

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

kernel/src/blk/virtio_blk.rs See file

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