Use when building Linux servers with liburing, batching SQEs, multi-shot operations, provided buffer rings, registered files and buffers, or zero-copy send. Not for packet I/O: use af-xdp.
Pro scans all 2 files and shows the line behind each finding
Scanned 9/20/2026
npx -y skills add OutlineDriven/outline-driven-development --skill io-uring --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Io Uring?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/outlinedriven-io-uring-outline-driven-development)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: io-uring
description: 'Use when building Linux servers with liburing, batching SQEs, multi-shot operations, provided buffer rings, registered files and buffers, or zero-copy send. Not for packet I/O: use af-xdp.'
---
# io_uring
## Contract
| Field | Bound contract |
|---|---|
| Trigger | Building or tuning Linux async I/O with io_uring: the SQE/CQE model, liburing usage, multi-shot accept or recv, provided buffer rings, fixed files and buffers, zero-copy send, tokio-uring, or the io_uring versus epoll decision. |
| Authority | Read-only. Writes nothing. Chat output only. No remote mutation. |
| Side effect | Returns setup code, ring lifecycle rules, and a comparison verdict. No source files are modified. |
| Done | The liburing example, the multi-shot and buffer-ring patterns, the zero-copy rule, and the epoll comparison verdict are delivered. |
## Inputs
1. Workload shape (required): network or disk, operation mix, and connection count.
2. Kernel (required): the running version, because io_uring features vary by version; base support starts at 5.1.
3. Language (optional): C with liburing, or Rust with tokio-uring.
## Procedure
1. State the queue model. Each SQE describes one operation. Each CQE reports a result and the `user_data` cookie that identifies the operation. `io_uring_submit()` pushes SQEs to the kernel; `io_uring_wait_cqe()` blocks for a completion. Done when: the SQE-to-CQE flow with the cookie rule is stated in the delivered guidance.
2. Show the minimal liburing loop as the reference shape.
```c
#include <liburing.h>
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, (void *)1);
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res >= 0)
handle(cqe->res);
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
```
Build with `-luring`. Done when: the example compiles against liburing and each CQE is consumed with `cqe_seen`.
3. Map the request to its prep function: `io_uring_prep_read`, `prep_write`, `prep_recv`, `prep_send`, `prep_accept`, `prep_connect`, `prep_poll_add`, `prep_timeout`. Batch many SQEs between submits; one submit amortizes the syscall cost over the batch. Done when: every operation in the workload has a prep function and a batching point.
4. Use multi-shot operations for connection-heavy paths. One `io_uring_prep_multishot_accept` SQE keeps producing completions until it reports completion of its own life.
```c
io_uring_prep_multishot_accept(sqe, listen_fd, NULL, NULL, 0);
io_uring_sqe_set_data(sqe, (void *)ACCEPT_COOKIE);
io_uring_wait_cqe(&ring, &cqe);
if (cqe->user_data == ACCEPT_COOKIE && cqe->res >= 0)
handle_client(cqe->res);
int more = cqe->flags & IORING_CQE_F_MORE;
io_uring_cqe_seen(&ring, cqe);
if (!more)
rearm_accept(); /* the multishot SQE ended; submit a new one */
```
Done when: the loop re-arms exactly when `IORING_CQE_F_MORE` is clear.
5. Use provided buffer rings for recv paths. The kernel picks a buffer from the ring, which removes the per-recv allocation.
```c
#define BUF_SIZE 4096
#define BUF_COUNT 64
int ret;
struct io_uring_buf_ring *buf_ring =
io_uring_setup_buf_ring(&ring, BUF_COUNT, BUF_GROUP, 0, &ret);
for (int i = 0; i < BUF_COUNT; i++)
/* mask is ring size - 1; offset walks the slots */
io_uring_buf_ring_add(buf_ring, bufs[i], BUF_SIZE, i, BUF_COUNT - 1, i);
io_uring_buf_ring_advance(buf_ring, BUF_COUNT);
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv_multishot(sqe, sock_fd, NULL, 0, 0);
sqe->buf_group = BUF_GROUP;
```
Return each consumed buffer with `io_uring_buf_ring_add` before the next advance. Done when: every consumed buffer is re-added, and the recv carries the buffer group.
6. Register files and buffers on hot paths. Registered files avoid the per-operation fd table lookup; registered buffers are pinned and suited to `*_fixed` operations.
```c
io_uring_register_files(&ring, fds, 64);
io_uring_prep_read(sqe, 0, buf, size, offset); /* index into the table, not the fd */
struct iovec iov = { .iov_base = buf, .iov_len = size };
io_uring_register_buffers(&ring, &iov, 1);
io_uring_prep_read_fixed(sqe, fd, buf, size, offset, 0);
```
Done when: hot-path operations use fixed variants and the registered tables are updated on fd churn.
7. Use zero-copy send where available. `io_uring_prep_send_zc` needs kernel 6.0 or later and falls back to a normal send when the protocol or device does not support it. A zero-copy send posts a second notification CQE; treat `IORING_CQE_F_MORE` on the first CQE as the signal to wait for it. Done when: the send path handles the notification CQE and the fallback.
8. For Rust, show the tokio-uring shape: the runtime owns the ring and returns buffer ownership with the result.
```rust
use tokio_uring::fs::File;
#[tokio_uring::main]
async fn main() -> std::io::Result<()> {
let file = File::open("test.txt").await?;
let buf = vec![0u8; 4096];
let (res, buf) = file.read_at(buf, 0).await;
println!("read {} bytes", res?);
Ok(())
}
```
Done when: the Rust example compiles with the `tokio-uring` crate and buffer ownership flow is stated.
9. Deliver the epoll comparison as a verdict, not a table of absolutes.
| Aspect | epoll | io_uring |
|---|---|---|
| Syscalls | One per operation plus `epoll_wait` | Batched SQEs, one submit |
| Buffers | Application allocates per op | Provided buffer rings |
| Zero-copy send | Manual | `SEND_ZC` built in |
| Portability | Everywhere | 5.1+, feature-dependent |
Choose io_uring when syscall overhead is measured, not assumed, and the kernel has the needed operations. Stay on epoll for simple services and portability. Probe support at runtime with `io_uring_get_probe()`. Done when: the verdict cites a measured syscall or batching cost.
10. State the security posture. Some distributions restrict io_uring by default because it has been an attack surface.
```bash
sysctl kernel.io_uring_disabled
# 0: all processes may create instances
# 1: CAP_SYS_ADMIN or members of kernel.io_uring_group only
# 2: creation off for everyone
sysctl kernel.io_uring_group # the gid allowed at level 1
```
Existing instances keep working after a policy change. Keep the kernel patched and sandbox untrusted code. Done when: the deployment documents its io_uring policy and kernel version.
## Failure and recovery
| Symptom | Cause | Recovery |
|---|---|---|
| `-EINVAL` on prep | Operation unsupported on this kernel | Check `io_uring_get_probe()`; gate the code path on it. |
| Hang on `wait_cqe` | SQEs never submitted | Call `io_uring_submit` after preparing. |
| Buffer never returned | Consumed buffer not re-added | Re-add with `buf_ring_add` after consuming. |
| `SEND_ZC` result unclear | Notification CQE pending | Wait for the second CQE; `IORING_CQE_F_MORE` marks the pair. |
| Slower than epoll | Tiny batches | Batch more SQEs per submit; consider SQPOLL. |
| `-EPERM` on setup | io_uring restricted by sysctl | Align with the `kernel.io_uring_disabled` policy. |
## Output
The queue-model statement; the minimal liburing example; the prep-function mapping and batching plan; the multi-shot and buffer-ring code; the registration rules; the zero-copy send rule; the tokio-uring example; the epoll verdict with its measured basis; the security posture.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!