# How to Use the io_uring Event Mechanism on Modern Linux Kernels with libfiber

> Learn to use the io_uring event mechanism on Linux kernels with libfiber for high-performance asynchronous I/O. Enable io_uring at compile time and runtime for efficient operations.

- Repository: [iQIYI/libfiber](https://github.com/iqiyi/libfiber)
- Tags: how-to-guide
- Published: 2026-03-04

---

**libfiber abstracts the Linux kernel's io_uring subsystem behind a unified event interface that can be enabled at compile time with `HAS_IO_URING=yes` and activated at runtime using `event_set(FIBER_EVENT_IO_URING)` to achieve high-performance asynchronous I/O.**

The **iqiyi/libfiber** library provides a portable coroutine framework that leverages modern Linux kernel features to minimize I/O overhead. By implementing an **io_uring event mechanism**, libfiber allows applications to batch submissions and reduce syscall overhead without abandoning the familiar fiber-based programming model. This guide explains how to compile, configure, and code against libfiber's io_uring backend using the actual source implementation.

## Compiling libfiber with io_uring Support

Before utilizing the io_uring event mechanism, you must ensure the library is built with the appropriate flag. The build system controlled by `c/Makefile` checks for `HAS_IO_URING`.

```bash
make HAS_IO_URING=yes

```

When enabled, the Makefile appends `-DHAS_IO_URING` to `CFLAGS`, exposing the io_uring-specific code paths in [`c/src/event/event_io_uring.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/event/event_io_uring.c). This compile-time flag is essential because it conditionally includes the `<liburing.h>` headers and the `EVENT_URING` structure definition.

## Selecting the io_uring Backend at Runtime

With the library compiled for io_uring support, choosing the backend requires calling `event_set()` before creating any event loops. This function stores the desired mode in the thread-local variable `__event_mode` defined in [`c/src/event.h`](https://github.com/iqiyi/libfiber/blob/main/c/src/event.h).

```c
#include "fiber/libfiber.h"

int main(void) {
    /* Select io_uring as the underlying event mechanism */
    event_set(FIBER_EVENT_IO_URING);
    
    /* Initialize the event loop with 1024 ring entries */
    EVENT *ev = event_create(1024);
    
    /* Application logic using fiber_create(), fiber_wait_read(), etc. */
    
    event_free(ev);
    return 0;
}

```

The `event_create()` function in [`c/src/event.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/event.c) inspects `__event_mode` and dispatches to `event_io_uring_create()` when the value equals `FIBER_EVENT_IO_URING`. This design ensures that the same application code can run on older kernels by simply changing the `event_set()` parameter or omitting it entirely to use the default epoll backend.

## Architecture of the io_uring Event Mechanism

Understanding how libfiber maps high-level fiber operations to io_uring syscalls helps optimize application performance. The implementation centers on three phases: initialization, submission, and completion handling.

### Ring Initialization and Structure

The function `event_io_uring_create()` in [`c/src/event/event_io_uring.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/event/event_io_uring.c) allocates an `EVENT_URING` structure and initializes the kernel ring buffer via `io_uring_queue_init_params()`. This setup records the submission queue entry (SQE) size and wires generic `EVENT` callbacks—including `event_wait`, `add_read`, and `add_write`—to io_uring-specific implementations.

After initialization, the code checks for `IORING_FEAT_FAST_POLL` to verify that the kernel supports busy-wait polling for sockets. If this feature is unavailable, the library degrades gracefully rather than failing.

### Submission Strategy and Linked Timeouts

All I/O operations route through helper macros `TRY_SUBMMIT` and `SUBMMIT`, which batch SQEs and invoke `io_uring_submit()` only when the ring buffer fills or an explicit flush occurs. This zero-copy approach reuses the liburing SQE buffer directly.

When a fiber calls `fiber_wait_read()` or `fiber_wait_write()` (implemented in [`c/src/fiber_io.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/fiber_io.c)), the backend eventually executes `event_uring_add_read()` or `event_uring_add_write()`. These functions prepare operations using `io_uring_prep_readv()`, `io_uring_prep_poll_add()`, or similar liburing helpers. For timeouts, libfiber constructs a linked timeout SQE using `io_uring_prep_link_timeout()`, ensuring the timeout cancels automatically if the I/O completes first.

### Completion Handling and Fiber Resumption

The event loop blocks in `event_uring_wait()`, which calls `submit_and_wait()` to park the thread until at least one completion queue entry (CQE) arrives. The `peek_more` function then drains additional CQEs to minimize context switches.

Each CQE is converted to a high-level `FILE_EVENT` via the `IO_URING_CTX` payload and dispatched to `handle_read()` or `handle_write()`. These callbacks resume the suspended fiber by invoking `fe->r_proc` or `fe->w_proc`, effectively bridging the gap between kernel-level asynchronous notifications and user-level cooperative multitasking.

## Practical Examples

### Building an io_uring-Enabled Echo Server

The following implementation demonstrates how standard libfiber APIs automatically utilize io_uring operations when the backend is active. Note that `fiber_wait_read()` and `accept()` trigger `io_uring_prep_poll_add` and `io_uring_prep_accept` respectively behind the scenes.

```c
/* echo_server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "fiber/libfiber.h"

#define LISTEN_PORT 12345
#define BUF_SIZE    4096

static void client_handler(ACL_FIBER *fiber, void *ctx)
{
    int fd = *(int *)ctx;
    char buf[BUF_SIZE];
    ssize_t n;

    for (;;) {
        /* Triggers io_uring_prep_poll_add + read operation */
        if (fiber_wait_read(fd) < 0) break;

        n = read(fd, buf, sizeof(buf));
        if (n <= 0) break;

        /* Triggers io_uring_prep_poll_add + write operation */
        if (fiber_wait_write(fd) < 0) break;
        write(fd, buf, (size_t)n);
    }
    close(fd);
}

int main(void)
{
    /* 1. Select io_uring backend */
    event_set(FIBER_EVENT_IO_URING);
    
    /* 2. Create event loop with 2048-entry ring */
    EVENT *ev = event_create(2048);
    if (!ev) {
        perror("event_create");
        return 1;
    }

    /* 3. Standard socket setup */
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(LISTEN_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(listen_fd, 128);

    for (;;) {
        /* Uses io_uring_prep_accept when backend is io_uring */
        int client_fd = accept(listen_fd, NULL, NULL);
        if (client_fd < 0) continue;
        
        fiber_create(client_handler, &client_fd);
    }

    event_free(ev);
    return 0;
}

```

### Accessing Raw liburing Operations

For operations not wrapped by libfiber's high-level API, retrieve the underlying `struct io_uring` pointer using `event_handle()`:

```c
#include <liburing.h>
#include "fiber/libfiber.h"

void custom_statx(EVENT *ev)
{
    /* Cast the handle to the io_uring structure */
    struct io_uring *ring = (struct io_uring *)event_handle(ev);
    
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
    struct statx stx;
    
    io_uring_prep_statx(sqe, AT_FDCWD, "/etc/passwd", 0,
                        STATX_BASIC_STATS, &stx);
    io_uring_sqe_set_data(sqe, NULL);
    io_uring_submit(ring);
    
    /* Completion integrates with normal libfiber event loop */
}

```

This accesses the internal ring initialized in [`event_io_uring.c`](https://github.com/iqiyi/libfiber/blob/main/event_io_uring.c) while maintaining compatibility with the library's completion handling.

## Summary

- **Compile** libfiber with `HAS_IO_URING=yes` to enable the io_uring code paths in [`c/src/event/event_io_uring.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/event/event_io_uring.c).
- **Select** the backend by calling `event_set(FIBER_EVENT_IO_URING)` before `event_create()`, which stores the mode in `__event_mode` and dispatches to `event_io_uring_create()`.
- **Submit** operations through batched SQEs using the `TRY_SUBMMIT` macro, with automatic fallback to epoll on older kernels.
- **Handle** completions via `event_uring_wait()`, which drains CQEs and resumes fibers through `handle_read()` and `handle_write()` callbacks.

## Frequently Asked Questions

### What Linux kernel version is required for libfiber's io_uring support?

The io_uring event mechanism requires Linux kernel 5.1 or newer for basic operation, with full feature support (including `IORING_FEAT_FAST_POLL`) available from 5.5 onwards. If the kernel lacks io_uring syscalls, libfiber automatically falls back to epoll or kqueue, allowing the same binary to run on older systems without modification.

### How does libfiber handle timeouts with io_uring?

When a fiber specifies a timeout (e.g., `fiber_wait_read()` with a deadline), libfiber creates a linked timeout SQE using `io_uring_prep_link_timeout()`. This ensures the timeout operation cancels automatically if the primary I/O completes first, preventing resource leaks and unnecessary wakeups.

### Can I use raw liburing calls alongside libfiber's high-level APIs?

Yes. The `event_handle()` function returns the address of the internal `struct io_uring` ring initialized in `event_io_uring_create()`. You can submit custom SQEs using standard liburing functions, and completions will integrate with libfiber's event loop, though you must ensure your custom completions do not conflict with the library's internal `IO_URING_CTX` payload management.

### Where does the actual io_uring submission happen in the source code?

The submission logic resides in [`c/src/event/event_io_uring.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/event/event_io_uring.c). The `SUBMMIT` macro calls `io_uring_submit()` when the SQE ring fills or when explicit flushing occurs. Individual operations like `event_uring_add_read()` prepare SQEs using `io_uring_prep_readv()` or `io_uring_prep_poll_add()`, depending on whether a direct read or a poll-then-read strategy is optimal.