【io_uring异步高性能框架】

一、io_uring特点

1. 简介:

	io_uring是最早Linux 5.1引入的,后面不断迭代, 高性能异步 I/O 框架,旨在彻底优化传统 I/O 模型的性能瓶颈。

2. 其核心特点:

  • 共享的环形队列
    用户态与内核态通过共享内存,构建的环形队列(SQ/CQ)用来传递 I/O 请求和完成事件,避免数据多次拷贝。实现零拷贝.

  • 批量处理(Batching)
    支持单次系统调用(io_uring_enter)提交/收割请求和完成事件(SQE/CQE)减少了上下文切换

  • 内核轮询(SQ Polling)
    可选的内核线程主动轮询提交队列(SQ),进一步消除系统调用延迟。

  • 灵活的通知机制
    支持事件驱动(如 epoll)或轮询模式,适应不同场景。

二、工作流程

1. 初始化阶段

  1. 创建 io_uring 实例
    调用 io_uring_queue_init 初始化环形队列(SQ 和 CQ),并设置参数(如队列大小、标志位)。

    struct io_uring ring;
    io_uring_queue_init(ENTRIES, &ring, IORING_SETUP_SQPOLL); // 启用 SQPOLL 模式
    
    • SQ(提交队列):用户态提交 I/O 请求的入口。
    • CQ(完成队列):内核返回 I/O 结果的出口。
      用户态提交I/O请求(SQE)到SQ,内核消费,内核将完成事件(CQE)写入CQ,用户态消费
  2. 注册资源(可选)
    预注册文件描述符、缓冲区等资源,减少内核检查开销:

    // 注册文件描述符
    int files[] = {fd1, fd2};
    io_uring_register_files(&ring, files, 2);
    
    // 注册固定缓冲区
    struct iovec iov = {buffer, buffer_size};
    io_uring_register_buffers(&ring, &iov, 1);
    

2. 提交 I/O 请求

  1. 获取 SQE(提交队列条目)
    从 SQ 中获取一个空闲的 SQE,描述 I/O 操作细节:

    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    
  2. 配置 SQE 参数
    设置操作类型(读、写等)、目标文件描述符、缓冲区地址等:

    io_uring_prep_read(sqe, fd, buf, size, offset); // 准备读操作
    sqe->flags |= IOSQE_IO_LINK;                    // 启用链式请求
    sqe->user_data = (void*)request_id;             // 关联用户数据
    
  3. 批量提交请求
    将 SQ 中的 SQE 批量提交到内核:

    io_uring_submit(&ring); // 触发系统调用(若未启用 SQPOLL)
    
    • SQPOLL 模式:内核线程自动轮询 SQ,无需显式提交。

3. 内核处理阶段

  1. 消费 SQ 请求
    内核从 SQ 中取出 SQE,解析并执行对应的 I/O 操作(如磁盘读取、网络发送)。

  2. 异步执行 I/O

    • 对于文件 I/O,数据可能直接从页缓存复制到用户缓冲区。
    • 对于网络 I/O,数据通过套接字缓冲区传输。
  3. 生成 CQE(完成队列条目)
    I/O 完成后,内核将结果写入 CQ,包含状态码和用户数据(user_data):

    struct io_uring_cqe {
        __u64 user_data; // 对应 SQE 的 user_data
        __s32 res;       // 操作结果(成功字节数或错误码)
        __u32 flags;     // 附加标志
    };
    

4. 处理完成事件

  1. 获取 CQE
    用户态从 CQ 中获取完成的 I/O 事件:

    struct io_uring_cqe *cqe;
    io_uring_wait_cqe(&ring, &cqe); // 阻塞等待至少一个 CQE
    // 或
    io_uring_peek_cqe(&ring, &cqe); // 非阻塞检查
    
  2. 处理结果
    根据 cqe->res 判断操作是否成功:

    if (cqe->res >= 0) {
        // 成功:处理数据(cqe->res 为实际传输字节数)
    } else {
        // 失败:处理错误(错误码为 -cqe->res)
    }
    
  3. 标记 CQE 为已消费
    移动 CQ 尾指针,释放 CQE 空间:

    io_uring_cqe_seen(&ring, cqe);
    

四、示例代码

 unsigned short port = 9999;
 int sockfd = init_server(port);
 
 struct io_uring_params params;
 memset(&params, 0, sizeof(params));

 struct io_uring ring;

 io_uring_queue_init_params(ENTRIES_LEGHT , &ring, &params);
 // struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);

 #if 0
 	 struct sockaddr_in  clientaddr;
 	socklen_t len = sizeof(clientaddr);
 	int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
 #else
 	struct sockaddr_in  clientaddr;
 	socklen_t len = sizeof(clientaddr);
 	set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);
 #endif
 	char buffer[BUFFER_LENGTH] = {0};
 	   while(1)
 	   {
 	       io_uring_submit(&ring);
 	
 	       struct io_uring_cqe *cqe;
 	       // 拿到环形队列的头指针  阻塞等待至少一个CQE
 	       io_uring_wait_cqe(&ring, &cqe);
 	
 	       struct io_uring_cqe *cqes[120];
 	       int nready = io_uring_peek_batch_cqe(&ring, cqes, 120);// 准备好的cqe数量
 	
 	       int i = 0;
 	       for( i = 0; i < nready; i++)
 	       {
 	           // printf("io_uring_peek_batch_cqe\n");
 	           // cqes[i].resulut
 	
 	           struct io_uring_cqe *entries = cqes[i];
 	
 	           struct conn_info result;
 	           memcpy(&result, &entries->user_data, sizeof(struct conn_info));
 	           if(result.event == EVENT_ACCEPT)
 	           {
 	               set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);       
 	               // printf("io_uring_accept\n");
 	               int clientfd = entries->res;
 	               set_event_recv(&ring, clientfd, buffer, BUFFER_LENGTH, 0);// set read
 	               //entries
 	           }
 	           else if(result.event == EVENT_READ) // 走到这里数据已经读到了用户的缓冲区
 	           {
 	               int ret = entries->res;
 	               // printf("io_uring_recv ret: %d :  %s\n", ret, buffer);
 	               if(ret == 0)
 	               {
 	                   close(result.fd);
 	               }else if (ret > 0){
 	                   // 前面已经接收完了,这里设置进行回发
 	                   set_event_send(&ring, result.fd, buffer, ret, 0); // 接受完就回发
 	                   //set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);// set read
 	               }
 	           }else if((result.event == EVENT_WRITE)){  // 这里已经执行完了,数据已经写出去了
 	               int ret = entries->res;
 	               // printf("set_event_send ret: %d :  %s\n", ret, buffer);
 	
 	               // 这里设置又可以接收了
 	               set_event_recv(&ring, result.fd, buffer, ret, 0);
 	
 	           }
 	
 	       }
 	       // 清空cq        
 	       io_uring_cq_advance(&ring, nready);
 	   }

高级功能
链式请求(Chained Requests)
依赖执行:通过 IOSQE_IO_LINK 标志串联多个 SQE,前一个操作完成后再执行下一个。

sqe1->flags |= IOSQE_IO_LINK; // sqe2 在 sqe1 完成后执行
内核轮询(SQPOLL)
零系统调用提交:内核线程主动轮询 SQ,无需调用 io_uring_submit()。

配置:初始化时设置 IORING_SETUP_SQPOLL 标志。

五、性能对比(与传统模型)**

指标io_uringepoll + 非阻塞 I/Olibaio
系统调用次数极低(批量提交/收割)高(每次 I/O 需系统调用)中等(批量提交)
上下文切换极少频繁中等
最大吞吐量百万级 IOPS十万级 IOPS十万级 IOPS
延迟微秒级毫秒级微秒级(但功能受限)

六、适用场景

  • 高频网络服务:Web 服务器、API 网关、实时通信。
  • 存储系统:数据库(MySQL、Redis)、分布式存储引擎。
  • 低延迟应用:金融交易系统、实时音视频处理。
  • 混合负载:同时处理网络和磁盘 I/O 的高并发场景。

七、总结

io_uring 的工作流程围绕 提交队列(SQ)完成队列(CQ) 展开,通过共享内存和异步处理机制实现高性能 I/O。其核心优势在于:

  • 零拷贝:减少数据在用户态与内核态之间的复制。
  • 批量处理:单次系统调用处理多个请求。
  • 内核协作:通过 SQPOLL 和注册资源进一步降低开销。
    掌握其流程后,可结合具体场景(如网络服务、存储引擎)设计高效异步 I/O 模型,充分发挥 Linux 内核的潜力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值