# 安装 liburing 开发库
sudo apt-get install liburing-dev
# 或从源码编译安装:https://github.com/axboe/liburing
io_uring 是 Linux 内核提供的一套 异步 I/O 框架,它通过 共享内存环形队列(ring buffer) 实现用户程序与内核的高效通信,避免频繁的系统调用和上下文切换。

| 核心 API 说明 |
说明 |
| io_uring_queue_init() |
初始化 io_uring 实例 |
| io_uring_get_sqe() |
获取一个提交队列条目(SQE) |
| io_uring_prep_accept() |
准备 accept 操作 |
| io_uring_prep_recv() |
准备 recv 操作 |
| io_uring_prep_send() |
准备 send 操作 |
| io_uring_submit() |
提交 SQE 到内核 |
| io_uring_wait_cqe() |
等待完成事件(CQE) |
| io_uring_cqe_seen() |
标记 CQE 处理完成 |
#include <liburing.h>
/*
@param ntries 必须是 2 的幂次方(如 32, 64, 128, 256, 1024...)
@param *ring 包含指向 SQ 和 CQ 的指针:
ring->sq.ring_ptr → 指向 SQ 的共享内存
ring->cq.ring_ptr → 指向 CQ 的共享内存
用户可以通过这些指针直接读写队列,无需系统调用
@param flags:
0 默认行为,由用户主动提交(调用 io_uring_submit())
IORING_SETUP_SQPOLL 启用 内核轮询线程,自动提交 SQ 中的请求,减少系统调用
IORING_SETUP_SQ_AFF 与 SQPOLL 配合,指定轮询线程的 CPU 亲和性
IORING_SETUP_CQSIZE 允许指定 CQ 的大小(通过 struct io_uring_params)
IORING_SETUP_CLAMP 强制将 entries 限制在内核允许范围内
IORING_SETUP_ATTACH_WQ 附加到已存在的 io_uring 实例
*/
int io_uring_queue_init(unsigned entries, struct io_uring *ring, unsigned flags);
原理:
内核创建两个环形队列:Submission Queue (SQ) 和 Completion Queue (CQ),
这两个队列通过 mmap 映射到用户空间,用户和内核可以直接读写,无需系统调用,
优势:初始化一次,后续 I/O 提交和完成都不再需要系统调用(除非队列满或空)
struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring);
用于从 Submission Queue (SQ) 中获取一个空闲的 Submission Queue Entry (SQE),以便填充 I/O 请求并提交给内核。
注意:如果 SQ 满了,io_uring_get_sqe() 返回 NULL,需要先提交或等待。
void io_uring_prep_accept(struct io_uring_sqe *sqe,
int fd,
struct sockaddr *addr,
socklen_t *addrlen,
int flags);
用于准备一个 accept 类型的 I/O 请求,并将其填充到通过 io_uring_get_sqe 获取的 SQE(Submission Queue Entry)中。
它对应的是传统阻塞 I/O 中的 accept() 系统调用,但在 io_uring 模型中,
它是异步的:提交后立即返回,内核在有新连接到达时完成该操作,并通过 CQE(Completion Queue Entry)通知用户。
将一个用户自定义指针(conn)绑定到 SQE
当 I/O 完成时,内核会把这个指针通过 CQE 返回给用户
这是 实现上下文关联的关键!
例如:accept 完成后,你能知道是哪个监听套接字的连接
用途:避免全局查找,快速定位连接状态
int io_uring_submit(struct io_uring *ring);
成功:返回提交的 I/O 请求数量(≥ 0)
失败:返回负的错误码(如 -EBADF, -EFAULT)
默认模式(无 IORING_SETUP_SQPOLL):
通过 io_uring_get_sqe() 准备 SQE
但这些请求尚未通知内核
调用 io_uring_submit() 时:
触发系统调用 io_uring_enter
内核检查 SQ 中是否有新请求(通过 tail 变化)
如果有,内核开始执行这些 I/O
返回实际提交的请求数
SQPOLL 模式(IORING_SETUP_SQPOLL):
内核启动一个内核线程,持续轮询 SQ
用户提交 SQE 后,无需调用 io_uring_submit()
内核线程会自动检测 tail 变化并执行 I/O
此时 io_uring_submit() 可能不触发系统调用
/*
@param *ring
@param **cqe_ptr
@result 成功0, 失败:错误码
*/
int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);
io_uring 编程模型中的核心等待函数,用于阻塞等待至少一个 I/O 操作完成,并获取其完成结果。
| 函数 |
行为 |
是否阻塞 |
适用场景 |
| io_uring_wait_cqe() |
等待至少一个完成事件 |
阻塞 |
主循环中等待 I/O |
| io_uring_peek_cqe() |
查看是否有完成事件 |
不阻塞 |
非阻塞轮询、结合其他事件源(如 epoll) |
//@param *cqe 通常来自 io_uring_wait_cqe() 或 io_uring_peek_cqe() 的返回结果。
void *io_uring_cqe_get_data(const struct io_uring_cqe *cqe);
返回值:
返回一个 void* 指针,即你之前通过 io_uring_sqe_set_data(sqe, your_ptr) 设置的用户自定义数据。
如果未设置,返回 NULL。
//@param *ring 指向已初始化的 io_uring 实例。
//@param *cqe 指向一个已处理完毕的 CQE
void io_uring_cqe_seen(struct io_uring *ring, struct io_uring_cqe *cqe);
是 liburing 库中一个关键的完成队列管理函数,用于通知内核某个 CQE(Completion Queue Entry)已经被用户处理完毕,从而允许内核回收该槽位,推进完成队列的 head 指针。
它是 io_uring 无锁共享队列机制中不可或缺的一环,确保了 CQ(Completion Queue)的正确推进和资源循环利用。
tcp服务端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
// 引入 liburing 头文件
#include <liburing.h>
#define PORT 4567
#define MAX_EVENTS 1024
#define BUFFER_SIZE 4096
// 错误处理
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
// 每个连接的状态
struct conn_info {
int fd;
char buffer[BUFFER_SIZE];
struct io_uring_sqe *recv_sqe; // 指向当前 recv 的 SQE(用于取消)
};
// 封装 recv 提交
void submit_recv(struct io_uring &ring, struct conn_info *conn) {
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv(sqe, conn->fd, conn->buffer, sizeof(conn->buffer), 0);
io_uring_sqe_set_data(sqe, conn);
conn->recv_sqe = sqe; // 保存指针,用于取消
io_uring_submit(&ring);
}
int main() {
int server_sock;
struct sockaddr_in server_addr;
struct io_uring ring;
struct io_uring_cqe *cqe;
struct io_uring_sqe *sqe;
// 1. 初始化 io_uring 实例
if (io_uring_queue_init(MAX_EVENTS, &ring, 0) < 0) {
ERR_EXIT("io_uring_queue_init");
}
// 2. 创建监听 socket
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock < 0) ERR_EXIT("socket");
// 端口复用
int opt = 1;
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
ERR_EXIT("bind");
// 监听
if (listen(server_sock, SOMAXCONN) < 0)
ERR_EXIT("listen");
printf("TCP Server (io_uring) listening on port %d...\n", PORT);
// 3. 提交第一个 accept 请求
struct conn_info *conn = (struct conn_info*)calloc(1, sizeof(*conn));
conn->fd = server_sock;
sqe = io_uring_get_sqe(&ring);
/*
填充 sqe,准备一个 accept 操作
地址缓冲区:NULL(不关心客户端地址)
长度缓冲区:NULL
*/
io_uring_prep_accept(sqe, server_sock, NULL, NULL, 0);
io_uring_sqe_set_data(sqe, conn);
io_uring_submit(&ring);
// 4. 主循环
while (1) {
// 等待完成事件
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
ERR_EXIT("io_uring_wait_cqe");
}
struct conn_info *current_conn = (struct conn_info*)io_uring_cqe_get_data(cqe);
int client_fd, result = cqe->res;
if (cqe->user_data == (unsigned long)current_conn->recv_sqe) {
// recv 完成
if (result > 0) {
printf("Received %d bytes\n", result);
// 回显
sqe = io_uring_get_sqe(&ring);
io_uring_prep_send(sqe, current_conn->fd, current_conn->buffer, result, 0);
io_uring_sqe_set_data(sqe, current_conn);
io_uring_submit(&ring);
// 继续接收
submit_recv(&ring, current_conn);
} else {
printf("Client disconnected (fd=%d)\n", current_conn->fd);
close(current_conn->fd);
free(current_conn);
}
}
else if (result > 0) {
// accept 完成
client_fd = result;
printf("New client connected: fd=%d\n", client_fd);
// 设置非阻塞(虽然 io_uring 不依赖,但推荐)
fcntl(client_fd, F_SETFL, O_NONBLOCK);
// 为新客户端分配状态
struct conn_info *client_conn = (struct conn_info*)calloc(1, sizeof(*client_conn));
client_conn->fd = client_fd;
// 提交 recv 请求
submit_recv(&ring, client_conn);
// 再提交下一个 accept
struct conn_info *listen_conn = (struct conn_info*)io_uring_cqe_get_data(cqe);
sqe = io_uring_get_sqe(&ring);
io_uring_prep_accept(sqe, server_sock, NULL, NULL, 0);
io_uring_sqe_set_data(sqe, listen_conn);
io_uring_submit(&ring);
}
io_uring_cqe_seen(&ring, cqe);
}
close(server_sock);
io_uring_queue_exit(&ring);
return 0;
}
tcp客户端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <liburing.h>
#define BUFFER_SIZE 4096
#define QUEUE_DEPTH 32
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 4567
#define MESSAGE "Hello from io_uring TCP client!\n"
// 客户端状态机
struct client_context {
int sockfd;
char send_buffer[128];
char recv_buffer[BUFFER_SIZE];
int stage; // 0=connecting, 1=sending, 2=receiving
};
// 错误处理宏
#define ERR_EXIT(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main() {
struct io_uring ring;
struct client_context *ctx;
struct io_uring_cqe *cqe;
struct io_uring_sqe *sqe;
// 1. 初始化 io_uring
if (io_uring_queue_init(QUEUE_DEPTH, &ring, 0) < 0) {
ERR_EXIT("io_uring_queue_init");
}
// 2. 分配客户端上下文
ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
ERR_EXIT("calloc");
}
// 3. 创建 socket
ctx->sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (ctx->sockfd < 0) {
ERR_EXIT("socket");
}
// 4. 准备连接请求
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
fprintf(stderr, "Invalid address: %s\n", SERVER_IP);
exit(1);
}
sqe = io_uring_get_sqe(&ring);
if (!sqe) {
fprintf(stderr, "SQ is full!\n");
goto cleanup;
}
io_uring_prep_connect(sqe, ctx->sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
io_uring_sqe_set_data(sqe, ctx); // 绑定上下文
ctx->stage = 0; // connecting
// 5. 提交连接请求
if (io_uring_submit(&ring) <= 0) {
fprintf(stderr, "Failed to submit connect request\n");
goto cleanup;
}
printf("Connecting to %s:%d...\n", SERVER_IP, SERVER_PORT);
// 6. 等待连接完成
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
fprintf(stderr, "io_uring_wait_cqe failed\n");
goto cleanup;
}
ctx = io_uring_cqe_get_data(cqe);
if (cqe->res < 0) {
fprintf(stderr, "Connect failed: %s\n", strerror(-cqe->res));
goto cleanup;
}
printf("Connected!\n");
io_uring_cqe_seen(&ring, cqe);
// === 连接成功,开始发送数据 ===
strcpy(ctx->send_buffer, MESSAGE);
sqe = io_uring_get_sqe(&ring);
if (!sqe) {
fprintf(stderr, "SQ is full!\n");
goto cleanup;
}
io_uring_prep_send(sqe, ctx->sockfd, ctx->send_buffer, strlen(ctx->send_buffer), 0);
io_uring_sqe_set_data(sqe, ctx);
ctx->stage = 1; // sending
if (io_uring_submit(&ring) <= 0) {
fprintf(stderr, "Failed to submit send request\n");
goto cleanup;
}
printf("Sending: %s", ctx->send_buffer);
// 等待发送完成
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
fprintf(stderr, "Send wait failed\n");
goto cleanup;
}
ctx = io_uring_cqe_get_data(cqe);
if (cqe->res < 0) {
fprintf(stderr, "Send failed: %s\n", strerror(-cqe->res));
goto cleanup;
}
printf("Sent %d bytes\n", cqe->res);
io_uring_cqe_seen(&ring, cqe);
// === 发送完成,开始接收响应 ===
sqe = io_uring_get_sqe(&ring);
if (!sqe) {
fprintf(stderr, "SQ is full!\n");
goto cleanup;
}
io_uring_prep_recv(sqe, ctx->sockfd, ctx->recv_buffer, BUFFER_SIZE - 1, 0);
io_uring_sqe_set_data(sqe, ctx);
ctx->stage = 2; // receiving
if (io_uring_submit(&ring) <= 0) {
fprintf(stderr, "Failed to submit recv request\n");
goto cleanup;
}
printf("Waiting for response...\n");
// 等待接收完成
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
fprintf(stderr, "Recv wait failed\n");
goto cleanup;
}
ctx = io_uring_cqe_get_data(cqe);
if (cqe->res < 0) {
fprintf(stderr, "Recv failed: %s\n", strerror(-cqe->res));
} else if (cqe->res == 0) {
printf("Server closed connection.\n");
} else {
ctx->recv_buffer[cqe->res] = '\0'; // null-terminate
printf("Received response (%d bytes):\n%s", cqe->res, ctx->recv_buffer);
}
io_uring_cqe_seen(&ring, cqe);
cleanup:
if (ctx && ctx->sockfd >= 0) {
close(ctx->sockfd);
}
free(ctx);
io_uring_queue_exit(&ring);
return 0;
}