This project implements a toy io_uring-style async I/O library (libur_fs.so) on top of a FAT-like disk emulator (SimpleFS), plus:
cat_ur– an asynccat-style utilitytest_ur_write_v2– a small test of async write/readbench– throughput/latency benchmarkssimplefs_server– the server process for SimpleFS operations
The system uses shared memory ring buffers and eventfd notifications to communicate between the application and the SimpleFS server process.
Objective:
- Emulate
io_uring-style APIs entirely in user space. - Use SimpleFS as the backend file system emulator.
- Implement a minimal set of async operations.
- Benchmark our async library against both sync SimpleFS I/O and native POSIX calls.
- Achieve measurable speedup, especially on sequential reads.
- Building
cat_ur, an async clone of UNIX cat operating on SimpleFS.
Architecture:
The following diagram illustrates the components and communication flow of our io_uring emulator built on top of SimpleFS:
- Application
libur_fs: The user-level program or library that issues asynchronous I/O requests by enqueueing them into the submission ring buffer. - Shared memory rings: Ring buffers mapped into shared memory. The submission ring carries pending I/O requests from the application to the server, while the completion ring carries completed request results back from the server to the application.
- SimpleFS server: A separate process that waits for requests on the submission ring, performs file operations using SimpleFS APIs, and posts results on the completion ring.
Flow Explanation:
Application → libur_fs → Shared Rings + eventfd → simplefs_server → SimpleFS Disk
Asynchronous I/O Lifecycle:
This diagram illustrates the flow of an asynchronous read operation in our system:
- The application calls
ur_read(), which creates a submission queue entry (SQE) describing the read request and enqueues it into the submission ring buffer in shared memory. - After enqueuing the SQE, the application signals the SimpleFS server by writing to an
eventfd. This wakes the server process, which begins processing new requests. - The server processes the read request by calling
sfs_read()on the SimpleFS file system. - Upon finishing the read, the server creates a completion queue entry (CQE) with the result and writes it into the completion ring buffer in shared memory.
- The application retrieves the results by calling
ur_peek_cqe(), which reads the CQE from the completion queue and copies the read data back to the user buffer.
Example:
To read a file asynchronously: call ur_open(), enqueue ur_read(), ur_submit(), ur_wait(), and
retrieve results via ur_peek_cqe().
make allbuildscat_ur,test_ur_write_v2,bench, andsimplefs_serverwith no warnings under-Wall -Wextra -Werror.make testruns all tests.make cleanremoves all artifacts.
make clean && make all
# test
./test_ur_write_v2
# Async cat
./cat_ur test_write.txt
# Benchmarks
./benchTo verify our setup, run the following commands in order:
./test_ur_write_v2Sample output:
Write completed: user_data=2, res=27
cat_ur is an asynchronous version of the UNIX cat command designed to read files from the SimpleFS image rather than the host filesystem. It demonstrates how to use the asynchronous ur_* API to stream a file's contents.
Key Points:
- The program reads files in 4 KiB chunks (
BUFSIZE), a common I/O block size. - Initialize an existing simpleFS image using
mksfs(0);. - Bring up the asynchronous I/O infrastructure via
ur_init(), which sets up shared memory rings and starts the SimpleFS server process. - Open the target file asynchronously using
ur_open(). - In a loop, call
ur_read()to enqueue a read, submits them withur_submit(), and waits for their completion usingur_wait(). - Completed read data is retrieved via
ur_peek_cqe()and written directly to standard output usingwrite(). - Once finished, the file is closed asynchronously via
ur_close(), and the asynchronous system is cleanly shut down withur_shutdown().
Usage example:
To display the contents of test_write.txt with cat_ur, run:
./cat_ur test_write.txtSample output:
Warning: 'test_write.txt' is already open.
Hello from async ur_write!
Note: test_write.txt is stored inside the SimpleFS image (disk.sfs), not on the host filesystem.
This benchmark creates a 32 MiB file named benchfile inside the toy SimpleFS, then measures both sync and async throughput and latency for 4 KiB block operations under sequential and random patterns. Also, it validates the correctness and performance efficiency of the async I/O library built on top of SimpleFS.
Tested scenarios:
We compare three scenarios:
- SimpleFS (sync):
- use
sfs_open(),sfs_read(), andsfs_write(). - write the entire file block by block, then read it.
- measure and report throughput and latency separately for sequential and random patterns.
- use
- Host FS (sync):
- use standard Linux file I/O
open(),pread(), andpwrite(). - provide a baseline for performance on the native fs.
- run sequential and random patterns.
- use standard Linux file I/O
- SimpleFS (async):
- use our
libur_fsasync API (ur_open(),ur_read(),ur_write(), andur_close()). - perform write and read in batches of 1024 operations to improve concurrency.
- after each batch, submits requests and waits for all completions.
- measures throughput and latency as with the synchronous tests. For each scenario, we do both sequential and random I/O patterns.
- use our
Metrics reported:
- Throughput (MiB/s): total data transferred divided by total elapsed time.
- Latency (µs/op): average time per 4 KiB operation (elapsed time/number of blocks).
How to Run the Benchmark:
Build and run the benchmark:
./benchSimpleFS sync
[SYNC] sequential write:
Throughput: 0.04 MiB/s, Latency: 109195.50 µs/op
[SYNC] sequential read:
Throughput: 0.04 MiB/s, Latency: 91566.05 µs/op
[SYNC] random write:
Throughput: 0.05 MiB/s, Latency: 83064.88 µs/op
[SYNC] random read:
Throughput: 0.05 MiB/s, Latency: 82809.53 µs/op
Host-FS sync (pread/pwrite)
[HOST-SYNC] sequential write:
Throughput: 2327.04 MiB/s, Latency: 1.68 µs/op
[HOST-SYNC] random write:
Throughput: 2535.18 MiB/s, Latency: 1.54 µs/op
[HOST-SYNC] sequential read:
Throughput: 5652.16 MiB/s, Latency: 0.69 µs/op
[HOST-SYNC] random read:
Throughput: 6045.08 MiB/s, Latency: 0.65 µs/op
SimpleFS async
Warning: 'benchfile' is already open.
[ASYNC] sequential write:
Throughput: 0.12 MiB/s, Latency: 32189.30 µs/op
[ASYNC] sequential read:
Throughput: 0.12 MiB/s, Latency: 32389.46 µs/op
[ASYNC] random write:
Throughput: 0.12 MiB/s, Latency: 32429.21 µs/op
[ASYNC] random read:
Throughput: 0.12 MiB/s, Latency: 32524.56 µs/op
| Test Category | Operation | Throughput (MiB/s) | Latency (µs/op) |
|---|---|---|---|
| SimpleFS sync | Write (Seq) | 0.04 | 109195.50 |
| Read (Seq) | 0.04 | 91566.05 | |
| Write (Rand) | 0.05 | 83064.88 | |
| Read (Rand) | 0.05 | 82809.53 | |
| Host-FS sync | Write (Seq) | 2327.04 | 1.68 |
| Read (Seq) | 5652.16 | 0.69 | |
| Write (Rand) | 2535.18 | 1.54 | |
| Read (Rand) | 6045.08 | 0.65 | |
| SimpleFS async | Write (Seq) | 0.12 | 32189.30 |
| Read (Seq) | 0.12 | 32389.46 | |
| Write (Rand) | 0.12 | 32429.21 | |
| Read (Rand) | 0.12 | 32524.56 |
To compute the throughput speedup for each operation, divide the async throughput by the corresponding SimpleFS sync throughput:
Latency Speedup (how many times faster async latency is compared to sync):
| Operation | Async Thru. (MiB/s) | Sync Thru. (MiB/s) | Thru. Speedup | Async Latency (µs/op) | Sync Latency (µs/op) | Lat. Speedup |
|---|---|---|---|---|---|---|
| Write (Seq) | 0.12 | 0.04 | 3.00× | 32189.30 | 109195.50 | 3.39× |
| Read (Seq) | 0.12 | 0.04 | 3.00× | 32389.46 | 91566.05 | 2.83× |
| Write (Rnd) | 0.12 | 0.05 | 2.40× | 32429.21 | 83064.88 | 2.56× |
| Read (Rnd) | 0.12 | 0.05 | 2.40× | 32524.56 | 82809.53 | 2.55× |
For example, for sequential write:
Speedup_throughput = 0.12 / 0.04 = 3.00×
Speedup_latency = 109195.50 / 32189.30 ≈ 3.39×
The benchmark demonstrates that our asynchronous SimpleFS implementation achieves significant speedup compared to SimpleFS synchronous I/O, especially in sequential access patterns. It achieves about 2.5 to 3 times better throughput and lower latency, especially for sequential reads and writes. This validates the effectiveness of the async io_uring-style design layered on top of the SimpleFS disk emulator.


