Skip to content

nadaaladdin/io_uring

Repository files navigation

User-Space io_uring Emulator on SimpleFS

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 async cat-style utility
  • test_ur_write_v2 – a small test of async write/read
  • bench – throughput/latency benchmarks
  • simplefs_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:

Architecture Diagram

  • 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:

Async I/O Lifecycle

This diagram illustrates the flow of an asynchronous read operation in our system:

  1. 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.
  2. 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.
  3. The server processes the read request by calling sfs_read() on the SimpleFS file system.
  4. 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.
  5. 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().


Build

  • make all builds cat_ur, test_ur_write_v2, bench, and simplefs_server with no warnings under -Wall -Wextra -Werror.
  • make test runs all tests.
  • make clean removes all artifacts.
make clean && make all
# test
./test_ur_write_v2

# Async cat
./cat_ur test_write.txt

# Benchmarks
./bench

Usage & Sample Runs

To verify our setup, run the following commands in order:

1) First test (test_ur_write_v2)

./test_ur_write_v2

Sample output:

Write completed: user_data=2, res=27

2) Async cat (cat_ur.c)

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 with ur_submit(), and waits for their completion using ur_wait().
  • Completed read data is retrieved via ur_peek_cqe() and written directly to standard output using write().
  • Once finished, the file is closed asynchronously via ur_close(), and the asynchronous system is cleanly shut down with ur_shutdown().

Usage example:

To display the contents of test_write.txt with cat_ur, run:

./cat_ur test_write.txt

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

3) Benchmark (bench.c)

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(), and sfs_write().
    • write the entire file block by block, then read it.
    • measure and report throughput and latency separately for sequential and random patterns.
  • Host FS (sync):
    • use standard Linux file I/O open(), pread(), and pwrite().
    • provide a baseline for performance on the native fs.
    • run sequential and random patterns.
  • SimpleFS (async):
    • use our libur_fs async API (ur_open(), ur_read(), ur_write(), and ur_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.

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:

./bench

Sample output:

SimpleFS 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

Performance Summary

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

Calculating Throughput Speedup

To compute the throughput speedup for each operation, divide the async throughput by the corresponding SimpleFS sync throughput:

$$ \text{Speedup} = \frac{Throughput_{\text{async}}}{Throughput_{\text{sync}}} $$

Latency Speedup (how many times faster async latency is compared to sync):

$$ \text{Speedup} = \frac{Latency_{\text{sync}}}{Latency_{\text{async}}} $$

Speedup Summary (Async vs SimpleFS 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×

Async I/O Lifecycle

Summary:

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.

About

Implementation of a User-Space io_uring Emulator on Top of the SimpleFS

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages