io-uring Rust Documentation

repository·master·Indexed 23 days ago

https://github.com/tokio-rs/io-uring

A low-level Rust userspace interface for the Linux io_uring subsystem, providing high-performance asynchronous I/O capabilities. Version 0.7.13 provides tools for managing SubmissionQueues and CompletionQueues, building opcodes like Read, and configuring ring behavior via a Builder pattern. It includes support for various architectures and provides a Probe mechanism to verify kernel opcode support.

Tokens
6.7K
Snippets
8
Records
40
Agent score
83%

What's inside io-uring

  1. Run tests and benchmarks

    master

    You can execute the built-in tests and benchmarks using the following Cargo commands:

    • To run tests: cargo run --package io-uring-test
    • To run benchmarks: cargo bench --package io-uring-bench
    $ cargo run --package io-uring-test
    $ cargo bench --package io-uring-bench
  2. Process completed I/O operations with CompletionQueue

    master

    The CompletionQueue is used to retrieve and process completed asynchronous I/O operations. It implements Iterator and ExactSizeIterator, allowing you to loop through completed entries.

    To ensure you see the most recent completions produced by the kernel, you must call .sync() on the queue. This synchronizes the local head/tail pointers with the actual ring state.

    Key methods:

    • sync(): Synchronizes the queue with the kernel, making new entries available.
    • is_empty(): Returns true if there are no events to process.
    • len(): Returns the number of pending completions.
    • capacity(): Returns the total number of entries the ring can hold.
    • is_full(): Returns true if the queue is at maximum capacity (which may lead to dropped events if nodrop is not enabled).
  3. Distinguish between Fd and Fixed file descriptors

    master

    The io-uring crate provides two types for representing file descriptors to optimize performance:

    1. Fd(pub RawFd): A standard file descriptor that has not been registered with io_uring.
    2. Fixed(pub u32): A file descriptor that has been registered with the ring using Submitter::register_files or Submitter::register_files_sparse. Using Fixed descriptors can reduce overhead in certain operations.

    When using opcodes that accept a target, you can pass either type.

  4. Check kernel io_uring feature support with `Probe`

    master
    The Probe struct provides information about which io_uring opcodes and features are supported by the current Linux kernel. You can populate a Probe by calling register_probe on a Submitter instance. Use is_supported(opcode) to check if a specific opcode is available for use.
  5. Manage Entry sizes: Entry vs Entry128

    master

    The io-uring crate provides two types of submission queue entries:

    1. Entry: A standard 64-byte SQE.
    2. Entry128: A 128-byte SQE, used when the kernel is configured with IORING_SETUP_SQE128.

    You can convert a standard Entry into an Entry128 using Entry128::from(entry).

  6. Build for unsupported architectures

    master

    The io-uring crate provides prebuilt bindings for x86_64, aarch64, riscv64, loongarch64, and powerpc64. For other architectures, use one of the following methods:

    Option 1: Use bindgen

    Enable the bindgen feature in your Cargo.toml to generate bindings at build time.

    [dependencies]
    io-uring = { version = "0.7", features = ["bindgen"] }

    Option 2: Use custom bindings

    If you have your own sys.rs bindings:

    1. Set the IO_URING_OWN_SYS_BINDING environment variable to the path of your binding file.
    2. Build using the io_uring_use_own_sys cfg flag.
    export IO_URING_OWN_SYS_BINDING=/path/to/your/custom/sys.rs
    cargo build --cfg io_uring_use_own_sys
  7. Perform a file read using io-uring

    master

    To perform asynchronous I/O operations, you must initialize an IoUring instance, build an opcode (such as Read), push it to the submission queue, and then wait for the completion queue entry (CQE).

    Important Safety Note: The developer is responsible for ensuring that the entries pushed into the submission queue remain valid (e.g., the file descriptor and the buffer must not be dropped or moved) until the operation completes.

    Kernel Requirement: The Read opcode requires Linux kernel 5.6 or higher. Using a kernel version lower than 5.6 will cause the operation to fail.

    use io_uring::{opcode, types, IoUring};
    use std::os::unix::io::AsRawFd;
    use std::{fs, io};
    
    fn main() -> io::Result<()> {
        let mut ring = IoUring::new(8)?;
    
        let fd = fs::File::open("README.md")?;
        let mut buf = vec![0; 1024];
    
        let read_e = opcode::Read::new(types::Fd(fd.as_raw_fd()), buf.as_mut_ptr(), buf.len() as _) 
            .build()
            .user_data(0x42);
    
        // Note that the developer needs to ensure
        // that the entry pushed into submission queue is valid (e.g. fd, buffer).
        unsafe {
            ring.submission()
                .push(&read_e)
                .expect("submission queue is full");
        }
    
        ring.submit_and_wait(1)?;
    
        let cqe = ring.completion().next().expect("completion queue is empty");
    
        assert_eq!(cqe.user_data(), 0x42);
        assert!(cqe.result() >= 0, "read error: {}", cqe.result());
    
        Ok()
    }
  8. Handle Submission Queue full errors

    master
    When calling push or push_multiple, if the ring buffer does not have enough space for the requested entries, the method will return Err(PushError). This error indicates that the submission queue is full and you must wait for the kernel to consume entries before attempting to push more.
  9. Perform asynchronous I/O with io_uring

    master

    To perform asynchronous I/O using the io-uring crate, you follow a pattern of creating an IoUring instance, building an opcode (such as Read), pushing that opcode to the submission queue, and then submitting the queue and waiting for completions.

    Important Safety Note: The developer is responsible for ensuring that the resources used in the submission (such as file descriptors and memory buffers) remain valid until the operation completes. The io_uring crate uses unsafe blocks for submission because it cannot guarantee the lifetime of the underlying memory or file descriptors once they are handed off to the kernel.

    use io_uring::{opcode, types, IoUring};
    use std::os::unix::io::AsRawFd;
    use std::{fs, io};
    
    fn main() -> io::Result<()> {
        // Initialize a ring with a capacity of 8 entries
        let mut ring = IoUring::new(8)?;
    
        let fd = fs::File::open("README.md")?;
        let mut buf = vec![0; 1024];
    
        // 1. Build the opcode (e.g., Read)
        // We use types::Fd to wrap the raw file descriptor
        let read_e = opcode::Read::new(types::Fd(fd.as_raw_fd()), buf.as_mut_ptr(), buf.len() as _)
            .build()
            .user_data(0x42);
    
        // 2. Push the entry to the submission queue
        // This requires an unsafe block because the kernel will access the buffer/fd
        unsafe {
            ring.submission()
                .push(&read_e)
                .expect("submission queue is full");
        }
    
        // 3. Submit the entries and wait for at least 1 completion
        ring.submit_and_wait(1)?;
    
        // 4. Retrieve the completion queue entry (CQE)
        let cqe = ring.completion().next().expect("completion queue is empty");
    
        // Verify the user_data and the result (bytes read)
        assert_eq!(cqe.user_data(), 0x42);
        assert!(cqe.result() >= 0, "read error: {}", cqe.result());
    
        Ok()
    }
  10. Use `Probe::is_supported` to verify opcodes

    master
    The is_supported method takes a u8 representing an opcode and returns true if the kernel supports it. This is useful for building feature-detecting applications that can fallback to different IO patterns based on kernel capabilities.
  11. Parse multishot RecvMsg results with RecvMsgOut

    master

    When performing a multishot RecvMsg operation, use RecvMsgOut::parse to extract the name, control data, and payload from the buffer provided to the ring.

    let result = RecvMsgOut::parse(buffer, msghdr)?;
    
    let name = result.name_data();
    let payload = result.payload_data();
    
    if result.is_name_data_truncated() {
        // Handle truncated name
    }