Skip to content

add simple virtio-based block device driver and basic support of a FAT file system - #2658

Draft
stlankes wants to merge 4 commits into
hermit-os:mainfrom
stlankes:blk
Draft

add simple virtio-based block device driver and basic support of a FAT file system#2658
stlankes wants to merge 4 commits into
hermit-os:mainfrom
stlankes:blk

Conversation

@stlankes

Copy link
Copy Markdown
Contributor

The device driver is quite simple and supports just one virtqueue. The file system is extended to support FAT. The extension based mainly on Hadris FAT.

I tested the current approach by using rusty_demo, which was build as followed:

HERMIT_LOG_LEVEL_FILTER="hermit::drivers::blk=debug,hermit::fs=debug,smoltcp,info" cargo build --release -Zbuild-std=std,panic_abort --target=aarch64-unknown-hermit -p rusty_demo --features hermit/pci,hermit/virtio-blk,hermit/loader,hermit/pci-ids,fs 

Afterwards, I tested the current approach by following command:

qemu-system-aarch64 -display none -serial stdio \
  -machine virt,gic-version=3 \
  -cpu host  \
  -smp 1 -m 1024M \
  -global virtio-mmio.force-legacy=off \
  -kernel hermit-loader-aarch64 \
  -device guest-loader,addr=0x48000000,initrd=target/aarch64-unknown-hermit/release/rusty_demo  -drive file=disk.img,format=raw,if=none,id=disk0 \
-device virtio-blk-pci,drive=disk0,disable-legacy=on

@mkroening

mkroening commented Aug 17, 2026

Copy link
Copy Markdown
Member

A student of ours has already done this. We need to compare before we merge either of the two.

@stlankes

Copy link
Copy Markdown
Contributor Author

I know, I was interested. It isn't necessary to merge the PR. I was more interested in Hadris FAT.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark Results

Details
Benchmark Current: f2a8a3a Previous: 2e23902 Performance Ratio
startup_benchmark Build Time 93.55 s 80.34 s 1.16
startup_benchmark File Size 0.76 MB 0.80 MB 0.95
Startup Time - 1 core 0.76 s (±0.02 s) 0.75 s (±0.02 s) 1.02
Startup Time - 2 cores 0.80 s (±0.02 s) 0.74 s (±0.02 s) 1.08
Startup Time - 4 cores 0.80 s (±0.02 s) 0.74 s (±0.02 s) 1.08
multithreaded_benchmark Build Time 92.85 s 82.11 s 1.13
multithreaded_benchmark File Size 0.85 MB 0.86 MB 0.99
Multithreaded Pi Efficiency - 2 Threads 66.03 % (±6.62 %) 85.89 % (±6.61 %) 0.77
Multithreaded Pi Efficiency - 4 Threads 40.82 % (±3.11 %) 43.43 % (±2.56 %) 0.94
Multithreaded Pi Efficiency - 8 Threads 19.96 % (±1.72 %) 25.76 % (±1.53 %) 0.77
micro_benchmarks Build Time 218.63 s 80.40 s 2.72
micro_benchmarks File Size 0.85 MB 0.86 MB 0.99
Scheduling time - 1 thread 172.43 ticks (±39.09 ticks) 62.65 ticks (±4.06 ticks) 2.75
Scheduling time - 2 threads 100.24 ticks (±22.31 ticks) 34.08 ticks (±4.10 ticks) 2.94
Micro - Time for syscall (getpid) 11.79 ticks (±5.15 ticks) 3.45 ticks (±0.58 ticks) 3.42
Memcpy speed - (built_in) block size 4096 57608.05 MByte/s (±40444.01 MByte/s) 82448.38 MByte/s (±56997.13 MByte/s) 0.70
Memcpy speed - (built_in) block size 1048576 13448.30 MByte/s (±10951.22 MByte/s) 30585.98 MByte/s (±24707.84 MByte/s) 0.44
Memcpy speed - (built_in) block size 16777216 12736.98 MByte/s (±10818.14 MByte/s) 26340.06 MByte/s (±21720.96 MByte/s) 0.48
Memset speed - (built_in) block size 4096 57606.12 MByte/s (±40442.70 MByte/s) 82292.76 MByte/s (±56891.50 MByte/s) 0.70
Memset speed - (built_in) block size 1048576 13712.23 MByte/s (±11084.64 MByte/s) 31323.85 MByte/s (±25145.86 MByte/s) 0.44
Memset speed - (built_in) block size 16777216 13173.98 MByte/s (±11101.46 MByte/s) 27104.68 MByte/s (±22209.94 MByte/s) 0.49
Memcpy speed - (rust) block size 4096 55421.26 MByte/s (±39233.24 MByte/s) 74097.96 MByte/s (±51811.44 MByte/s) 0.75
Memcpy speed - (rust) block size 1048576 14413.72 MByte/s (±11996.45 MByte/s) 30361.60 MByte/s (±24602.37 MByte/s) 0.47
Memcpy speed - (rust) block size 16777216 11387.02 MByte/s (±9310.49 MByte/s) 27625.34 MByte/s (±22806.88 MByte/s) 0.41
Memset speed - (rust) block size 4096 55929.55 MByte/s (±39588.73 MByte/s) 74373.47 MByte/s (±51976.48 MByte/s) 0.75
Memset speed - (rust) block size 1048576 14715.29 MByte/s (±12152.07 MByte/s) 31110.89 MByte/s (±25033.24 MByte/s) 0.47
Memset speed - (rust) block size 16777216 11671.35 MByte/s (±9471.20 MByte/s) 28386.93 MByte/s (±23265.03 MByte/s) 0.41
alloc_benchmarks Build Time 216.51 s 74.76 s 2.90
alloc_benchmarks File Size 0.84 MB 0.87 MB 0.96
Allocations - Allocation success 91.35 % 91.31 % 1.00
Allocations - Deallocation success 100.00 % 100.00 % 1
Allocations - Pre-fail Allocations 61.55 % 61.44 % 1.00
Allocations - Average Allocation time 22586.28 Ticks (±1498.31 Ticks) 5860.58 Ticks (±98.43 Ticks) 3.85
Allocations - Average Allocation time (no fail) 23316.08 Ticks (±1963.72 Ticks) 6554.81 Ticks (±92.86 Ticks) 3.56
Allocations - Average Deallocation time 5138.90 Ticks (±1933.46 Ticks) 1805.01 Ticks (±250.35 Ticks) 2.85
mutex_benchmark Build Time 209.22 s 79.82 s 2.62
mutex_benchmark File Size 0.85 MB 0.86 MB 0.99
Mutex Stress Test Average Time per Iteration - 1 Threads 35.00 ns (±6.02 ns) 12.10 ns (±0.41 ns) 2.89
Mutex Stress Test Average Time per Iteration - 2 Threads 32.34 ns (±9.38 ns) 40.26 ns (±1.68 ns) 0.80

This comment was automatically generated by workflow using github-action-benchmark.

@stlankes
stlankes marked this pull request as draft August 18, 2026 08:44
The driver operates the single request virtqueue and completes requests by
polling the used ring through Virtq::dispatch_blocking, so a completion
never needs an interrupt.
FatStream turns the sector-granular block driver into the byte-granular
async Read/Write/Seek that hadris-fat expects, backed by a 16-sector LRU
cache.

VfatDirectory resolves paths component by component, because hadris-fat
addresses everything by directory entry. File handles keep only a path
and an offset.
Before these changes VirtioBlkDriver::flush answered Errno::Nosys
when the device had not negotiated F::FLUSH, and that
would now fail every sync on such a device.

Now, the driver response Ok, if the flush feature isn't available.
The FAT volume writes through a sector cache, so a returned write() or
unlink() is not yet on disk, and nothing forced it out: applications had no
way to ask, and an orderly exit left whatever was still dirty in memory. On
FAT that surfaces as lost cluster chains, because a directory entry and the
FAT sectors of its chain reference each other while the cache evicts them
independently.

sys_fsync is backed by a new ObjectInterface::fsync that defaults to a no-op
for objects without backing storage. Both vfat handles route it to a
volume-wide sync, including the directory handle. syscalls::shutdown runs
fs::sync() before anything else.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants