liburing库核心API

# 安装 liburing 开发库
sudo apt-get install liburing-dev

# 或从源码编译安装:https://github.com/axboe/liburing

io_uring 是 Linux 内核提供的一套 异步 I/O 框架,它通过 共享内存环形队列(ring buffer) 实现用户程序与内核的高效通信,避免频繁的系统调用和上下文切换。

image


核心 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;
}
posted @ 2018-11-19 23:10  osbreak  阅读(265)  评论(0)    收藏  举报