土法炼钢兴趣小组的算法知识备份

liburing 基础 API 详解:从 Hello World 到文件 I/O

目录

liburing 基础 API 详解

在了解了 io_uring 的核心原理后,是时候动手写代码了。虽然我们可以直接使用 io_uring_setup 等系统调用,但官方推荐使用 liburing。它封装了繁琐的内存屏障(Memory Barrier)和环形缓冲区管理,提供了一套更人性化的 API。

1. 核心工作流

使用 liburing 的标准流程可以概括为以下 6 步:

graph TD
    A[1. 初始化 io_uring_queue_init] --> B[2. 获取 SQE io_uring_get_sqe]
    B --> C[3. 填充请求 io_uring_prep_read/write]
    C --> D[4. 提交请求 io_uring_submit]
    D --> E[5. 等待完成 io_uring_wait_cqe]
    E --> F[6. 处理结果 & 标记 io_uring_cqe_seen]
    F -->|更多任务?| B
    F -->|结束| G[7. 销毁 io_uring_queue_exit]

2. 关键 API 解析

2.1 初始化与销毁

#include <liburing.h>

struct io_uring ring;
// 初始化一个队列深度为 32 的 ring
// flags 通常为 0,也可以是 IORING_SETUP_SQPOLL 等
int ret = io_uring_queue_init(32, &ring, 0);

// ... 使用 ring ...

// 程序退出前清理资源
io_uring_queue_exit(&ring);

2.2 提交请求 (Submission)

这是生产者的过程。我们需要从 Submission Queue (SQ) 中获取一个空闲的 Entry (SQE),然后填充它。

// 1. 获取空闲 SQE
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
if (!sqe) {
    // 队列满了,需要先 submit 或者处理一些 completion
}

// 2. 准备操作 (以 Read 为例)
// 相当于 pread(fd, buf, len, offset)
io_uring_prep_read(sqe, fd, buf, len, offset);

// 3. (可选) 绑定用户数据
// cqe 返回时会带回这个 data,用于区分是哪个请求完成了
io_uring_sqe_set_data(sqe, user_data_ptr);

// 4. 提交给内核
io_uring_submit(&ring);

liburing 提供了大量的 io_uring_prep_* 辅助函数,如 io_uring_prep_write, io_uring_prep_accept, io_uring_prep_poll_add 等,覆盖了绝大多数 I/O 操作。

2.3 处理结果 (Completion)

这是消费者的过程。内核处理完请求后,会将结果放入 Completion Queue (CQ)。

struct io_uring_cqe *cqe;

// 等待至少一个事件完成 (阻塞)
int ret = io_uring_wait_cqe(&ring, &cqe);

// 或者:非阻塞尝试获取
// int ret = io_uring_peek_cqe(&ring, &cqe);

if (ret == 0) {
    // 检查操作结果 (res 相当于系统调用的返回值)
    if (cqe->res < 0) {
        fprintf(stderr, "Async operation failed: %s\n", strerror(-cqe->res));
    } else {
        printf("Read %d bytes\n", cqe->res);
    }

    // 获取之前绑定的用户数据
    void *data = io_uring_cqe_get_data(cqe);

    // 重要:标记该 CQE 已处理,内核可以复用该槽位
    io_uring_cqe_seen(&ring, cqe);
}

3. 实战:实现一个简单的 cat 命令

下面我们编写一个完整的程序 01-cat-file.c,它使用 io_uring 读取文件内容并打印到标准输出。

3.1 代码实现

/* examples/io_uring/01-cat-file.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <liburing.h>

#define BLOCK_SZ    4096

int main(int argc, char *argv[]) {
    struct io_uring ring;
    
    if (argc < 2) return 1;

    // 1. 初始化
    io_uring_queue_init(4, &ring, 0);

    int fd = open(argv[1], O_RDONLY);
    struct stat st;
    fstat(fd, &st);
    off_t file_sz = st.st_size;
    
    off_t offset = 0;
    char buff[BLOCK_SZ];

    while (offset < file_sz) {
        // 2. 获取 SQE
        struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
        
        int bytes_to_read = BLOCK_SZ;
        if (offset + bytes_to_read > file_sz) 
            bytes_to_read = file_sz - offset;

        // 3. 准备 Read 请求
        io_uring_prep_read(sqe, fd, buff, bytes_to_read, offset);

        // 4. 提交
        io_uring_submit(&ring);

        // 5. 等待完成
        struct io_uring_cqe *cqe;
        io_uring_wait_cqe(&ring, &cqe);

        // 6. 处理结果
        if (cqe->res > 0) {
            fwrite(buff, 1, cqe->res, stdout);
            offset += cqe->res;
        }
        
        // 7. 标记 Seen
        io_uring_cqe_seen(&ring, cqe);
    }

    close(fd);
    io_uring_queue_exit(&ring);
    return 0;
}

完整代码: 01-cat-file.c

3.2 编译与运行

你需要安装 liburing-dev (Debian/Ubuntu) 或 liburing (Arch/Fedora)。

gcc 01-cat-file.c -o 01-cat-file -luring
./01-cat-file /etc/passwd

4. 总结

通过这个简单的例子,我们看到了 io_uring 的基本骨架。虽然对于同步读取文件来说,这比直接用 read() 麻烦了不少,但在高并发网络编程中,这种 “提交 -> 异步处理 -> 通知” 的模式能带来巨大的性能收益。

下一篇,我们将挑战更复杂的场景:编写一个基于 io_uring 的 TCP Echo Server


上一篇: 02-vs-epoll-performance.md - 性能对比 下一篇: 04-echo-server.md - 实战:TCP Echo Server

返回 io_uring 系列索引


By .