Linux异步io机制 io_uring

news2024/11/19 17:23:27

io_uring作为2019年的后起之秀,为linux异步网络编程新增一把倚天大剑,让我们简单学习一下!

数据结构:

a. sq (submition queue):提交队列,一个存放待执行事件的环形队列

b. cq (completion queue): 完成队列,存放已经完成的事件的环形队列

:这两个队列是用户态和内核态之间共享的内存,使用mmap实现

c. sqe :sq中的一项

struct io_uring_sqe { // sq中的一项

__u64 user_data; // 8 btyes

}

d. cqe:cq中的一项

struct io_uring_cqe { // cq中的一项

​ …

__u64 user_data; // 8 btyes

res; // 任务函数的返回值 eg. recv返回收到数据的长度、accept返回clientfd

}

在这里插入图片描述

io_uring基本工作流程如下:(参照上图)

1.初始化ring,其中包含了sq和cq

2.向提交队列sq里注册(提交)一个事件(任务)sqe

3.将sq中的若干事件(任务)提交给内核处理(submit)

4.内核将处理成功的事件cqe放到完成队列cq里

5.用户取出cq里的完成事件cqe

需要注意的是:

注 : sq里注册的任务sqe一旦交给内核处理,就从sq里被移除了,是一次性的,和epoll不同

注 : cq里的 completion 事件cqe,不主动清除会一直存在

注 : cq队列里都是执行成功的任务,没有执行失败的任务,不用判断recv()返回值 < 0的情况

接下来进行接口分析

io_uring 的三个系统调用liburing 中的封装

  • io_uring_setup (初始化ring)被封装为 io_uring_queue_init_params(entries, &ring, &params);
  • io_uring_register(从sqe向sq里注册一个任务sqe)被封装为 io_uring_prep_recv,io_uring_prep_accept等
  • io_uring_enter(将sq里的任务提交给内核处理)被封装为 io_uring_submit(&ring)

初始化:

struct io_uring_params params; // 用来初始化 ring
memset(&params, 0, sizeof(params)); // 初始化 params

struct io_uring ring; // sq and cq 两个环形队列

// 下面使用了系统调用io_uring_setup : 初始化ring
io_uring_queue_init_params(1024, &ring, &params); // 初始化两个环形队列:submition queue 和 completion queue

注册(添加)任务(事件):

struct io_uring_sqe *sqe = io_uring_get_sqe(ring); // 获取sq中下一个可用的位置sqe

	struct user_data user_data = {  // sqe->user_data
		.fd = sockfd,
		.event = EVENT_ACCEPT,
	};
	
	io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags); // 向sq里加入一个任务sqe
	memcpy(&sqe->user_data, &user_data, sizeof(struct user_data)); // 设置任务sqe的属性

提交事件给内核处理:

// 将sq里的任务提交给内核处理
        io_uring_submit(&ring);

获取完成的事件:

struct io_uring_cqe *cqe; // 指向cqe(cq中的一项)的指针

        // 等待sq里的任务完成,并返回一个结果
        io_uring_wait_cqe(&ring, &cqe); // 这个函数会阻塞当前线程,直到至少一个 cqe 可用为止。
                                        // 一旦有一个 cqe 可用,它就会将其存储在提供的指针cqe中,并返回
        
        // 从完成队列cq获取若干内核处理完成的结果,到cqes数组里,不会阻塞
        struct io_uring_cqe *cqes[128];
        int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);  // 和 epoll_wait() 类似

/*
  io_uring_wait_cqe 和 io_uring_peek_batch_cqe 一个是阻塞获取一个结果,一个是非阻塞获取多个结果
*/

移除cq中的完成事件:

// 处理完结果后,将完成的任务从cq中移除
        io_uring_cq_advance(&ring, nready);

最后附上一个io_uring实现的tcpserver,注释很详细

#include <stdio.h>
#include <liburing.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>


/* 
 * io_uring 的三个系统调用在 liburing 中的封装:
 *
 * io_uring_setup (初始化ring) -->被封装为 io_uring_queue_init_params(entries, &ring, &params); 
 *
 * io_uring_register(从sqe向sq里注册一个任务sqe) -->被封装为 io_uring_prep_recv,io_uring_prep_send,io_uring_prep_accept
 * 
 * io_uring_enter(将sq里的任务提交给内核处理)-->被封装为 io_uring_submit(&ring)
 * 
 * 
 * 
 * 注 : sq里注册的任务sqe一旦交给内核处理,就从sq里被移除了,是一次性的,和epoll不同
 * 注 : cq里的 completion 事件cqe,不主动清除会一直存在
 * 
 * 注 : ##cq队列里都是执行成功的任务##,没有执行失败的任务,不用判断recv()返回值 < 0的情况
 * 
 * 注 : sq和cq都是环形队列,是用户态和内核态的共享内存空间,用mmap实现 (submition queue & completion queue )
 * 
 * 注 : sqe(submition queue entry), cqe(completion queue entry) --> 这两个分别代表sq、cq中的一项
 * 
 */



// 自定义事件类型 event
#define EVENT_ACCEPT   	0
#define EVENT_READ		1
#define EVENT_WRITE		2

/*
 * struct io_uring_cqe { // cq中的一项
 *     ...
 *  
 *     __u64 user_data; // 8 btyes
 * 
 *     res;  // 任务函数的返回值   eg. recv返回收到数据的长度、accept返回clientfd
 *    
 * }
 * 
 * struct io_uring_sqe { // sq中的一项
 *     ...
 * 
 *     __u64 user_data; // 8 btyes
 * }
 * 
 */


// 自定义 user_data 保证 8 btyes
struct user_data {   // sqe & cqe --> user_data
	int fd;
	int event;
};


int init_server(unsigned short port) {	  // create a listener sockfd
 
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);	
	struct sockaddr_in serveraddr;	
	memset(&serveraddr, 0, sizeof(struct sockaddr_in));	
	serveraddr.sin_family = AF_INET;	
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	
	serveraddr.sin_port = htons(port);	

	if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {		
		perror("bind");		
		return -1;	
	}	

	listen(sockfd, 10);
	
	return sockfd;
}



#define ENTRIES_LENGTH		1024
#define BUFFER_LENGTH		1024

int set_event_recv(struct io_uring *ring, int sockfd,
                    void *buf, size_t len, int flags) { // 向sqe中添加一个recv任务

    struct io_uring_sqe *sqe = io_uring_get_sqe(ring); //获取sq中下一个可用的位置sqe

    struct user_data user_data = {
        .fd = sockfd,
        .event = EVENT_READ,
    };

    io_uring_prep_recv(sqe, sockfd, buf, len, flags); // 向sq里加入一个recv任务
    memcpy(&sqe->use_data, &user_data, sizeof(struct user_data)); // 设置recv任务的属性

}


int set_event_send(struct io_uring * ring, int sockfd,
                    void *buf, size_t len, int flags) { // 向sq里添加一个send任务

    struct io_uring_sqe *sqe = io_uring_get_sqe(ring); // 获取sq中下一个可用的位置sqe

    struct user_data user_data = {
        .fd = sockfd,
        .event = EVENT_WRITE,
    };

    io_uring_prep_send(sqe, sockfd, buf, len, flags); // 向sq里加入一个任务sqe
    memcpy(&sqe->user_data, &user_data, sizeof(struct user_data)); // 设置任务sqe的属性

}   


int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,
					socklen_t *addrlen, int flags) {

	struct io_uring_sqe *sqe = io_uring_get_sqe(ring); // 获取sq中下一个可用的位置sqe

	struct user_data user_data = {  // sqe->user_data
		.fd = sockfd,
		.event = EVENT_ACCEPT,
	};
	
	io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags); // 向sq里加入一个任务sqe
	memcpy(&sqe->user_data, &user_data, sizeof(struct user_data)); // 设置任务sqe的属性

}



int main(int argc, char *argv[]) {
    // 服务端监听套接字
    unsigned short port = 9999;
	int sockfd = init_server(port);
    // 客户端套接字地址
    struct sockaddr_in clientaddr;	
	socklen_t len = sizeof(clientaddr);


    struct io_uring_params params; // 用来初始化 ring
    memset(&params, 0, sizeof(params)); // 初始化 params

    struct io_uring ring; // sq and cq 两个环形队列

    // 下面使用了系统调用io_uring_setup : 初始化ring
    io_uring_queue_init_params(1024, &ring, &params); // 初始化两个环形队列:submition queue 和 completion queue
    
    // 向提交队列sq里添加一个accept任务
    set_event_accept(&ring, sockfd, (struct sockaddr *)&clientaddr, &len, 0);

    char buffer[BUFFER_LENGTH] = {0};

    while (1) {
        // 将sq里的任务提交给内核处理
        io_uring_submit(&ring);
        // 存储内核处理完成的结果在 cq(completion queue)里

        struct io_uring_cqe *cqe; // 指向cqe(cq中的一项)的指针

        // 等待sq里的任务完成,并返回一个结果
        io_uring_wait_cqe(&ring, &cqe); // 这个函数会阻塞当前线程,直到至少一个 cqe 可用为止。
                                        // 一旦有一个 cqe 可用,它就会将其存储在提供的指针cqe中,并返回
        
        // 从完成队列cq获取若干内核处理完成的结果,到cqes数组里,不会阻塞
        struct io_uring_cqe *cqes[128];
        int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);  // 和 epoll_wait() 类似

/*
  io_uring_wait_cqe 和 io_uring_peek_batch_cqe 一个是阻塞获取一个结果,一个是非阻塞获取多个结果
*/


        // 查看并处理结果
        int i = 0;
        for (i = 0; i < nready; i++) {

            struct io_uring_cqe *entries = cqes[i]; // 已经完成的事件cqe
            struct user_data result;
            // 获取完成事件的 user_data
            memcpy(&result, &entries->user_data, sizeof(struct user_data));

            if (result.event == EVENT_ACCEPT) { // accept任务完成
                // 重新在sq注册一个accept任务
                set_event_accept(&ring, sockfd, (struct sockaddr *)&clientaddr, &len, 0);

                int connfd = entries->res;// accept任务的执行结果:clientfd
                // 在sq注册一个任务:接收客户端数据
                set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0);

            } else if (result.event == EVENT_READ) { // read任务完成

                int ret = entries->res; // read的返回值

                if (ret == 0) { // 对方发送了fin包通知断开连接
                    close(result.fd);
                } else if (ret > 0) {
                    //  在sq注册一个任务:向客户端send数据
                    set_event_send(&ring, result.fd, buffer, ret, 0);
                } else if (result.event == EVENT_WRITE) { // write任务完成

                    int ret = entries->res; // write的返回值
                    // 在sq注册一个任务:接收客户端数据
                    set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);
                }
            }
        }
        // 处理完结果后,将完成的任务从cq中移除
        io_uring_cq_advance(&ring, nready);
    }

}

推荐学习https://xxetb.xetslk.com/s/p5Ibb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1629321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自定义大整数类(初次版本)

例子&#xff1a; #include "X:\Work\Share\CCode\CPlatform\MathExt\_MathExt_out.h"using namespace lf; using namespace std;int main() {_Integer i1 _t("12345678989764581381214575686787987979234234354364");_Integer i2 _t("1234567898…

SpringCloud02(远程调用Feign,网关Gateway,配置中心Nacos)

目录 一、远程调用Feign【要会用】 1. Feign简介 1 什么是Feign 2 准备基础代码 2. Feign入门【重点】 步骤 实现 测试 3. Feign配置Ribbon 1 说明 2 配置 4. Feign配置日志 1 说明 2 步骤 5. Feign使用优化 1 说明 2 步骤 6. 小结 二、网关Gateway 1. 网关…

基于深度学习神经网络的AI图片上色DDcolor系统源码

第一步&#xff1a;DDcolor介绍 DDColor 是最新的 SOTA 图像上色算法&#xff0c;能够对输入的黑白图像生成自然生动的彩色结果&#xff0c;使用 UNet 结构的骨干网络和图像解码器分别实现图像特征提取和特征图上采样&#xff0c;并利用 Transformer 结构的颜色解码器完成基于视…

xfce4 panel 不能显示QQ,钉钉的状态图标

有一段时间不能显示了&#xff0c;之前刚装完系统的时候很长时间内都是好的&#xff0c;所以刚开始肯定是支持显示这些状态图标的。就是因为不能显示的原因&#xff0c;所以还装了lxQt桌面&#xff0c;这个桌面确实不错。不过还是有时会怀念xfce4&#xff0c;想看看能不能解决这…

面经总结(二)(数据库)

数据库常识&#xff1a; 1、数据库系统包含什么&#xff1f; 包含了数据库、数据库管理系统、数据库管理员和应用程序。 数据库&#xff08;DB)&#xff1a;顾名思义是存放数据的仓库&#xff0c;实现数据的持久化。 数据库管理系统&#xff08;DBMS)&#xff1a;类似于操作系…

利用ollama和open-webui本地部署通义千问Qwen1.5-7B-Chat模型

目录 1 安装ollama 2 安装open-webui 2.1 镜像下载 3 配置ollama的模型转换工具环境 3.1 下载ollama源码 3.2 下载ollama子模块 3.3 创建ollama虚拟环境 3.4 安装依赖 3.5 编译量化工具 7 创建ollama模型 8 运行模型 参考文献&#xff1a; 1 安装ollama curl -fsSL …

(GEE)2000-2020年黄河流域时序渐变图及高程模型计算 JavaScript版

文章目录 一. 选取目标区域二. NDVI实现三. 高程模型DEM实现四. 时序图五. 植被覆盖类型六. 参考文献 首先推荐吴秋生老师团队开源的便捷构建网站&#xff1a;适用于地理空间应用的Streamlight 吴秋生老师团队的工具请自行探索。本文讲解基于GEE云开发平台实现&#xff0c;基于…

关于springboot内置tomcat最大请求数配置的一些问题

前言 springboot内置了tomcat。那么一个springboot web应用&#xff0c;最大的请求链接数是多少呢&#xff1f;很早以前就知道这个是有个配置&#xff0c;需要的时候&#xff0c;百度一下即可。但&#xff0c;事实并非如此&#xff0c;有几个问题我想大多数人还真不知道。比如…

机械图纸管理系统,如何选择适合的机械图纸管理系统?

机械图纸管理系统是一种专门用于管理和跟踪机械制造过程中的图纸、设计文件和相关信息的软件系统。例如&#xff1a;彩虹图纸管理系统&#xff0c;这种系统能够有效地整合和管理工程图纸、零部件清单、技术规范、工艺流程等各种数据&#xff0c;为企业提供全面的图纸管理和协作…

typescript:vscode的settings配置文件配置ts语法提示

typescript&#xff1a;vscode的settings配置文件配置ts语法提示 1 找到vscode左下角的齿轮按钮 2 点击Settings&#xff08;或者快捷键ctrl,&#xff09;&#xff1a; 点击右上角的Open Settings(JSON)按钮打开配置文件&#xff1a; 或者ctrlshiftp&#xff0c;搜索settings&…

嵌入式全栈开发学习笔记---Linux基本命令1

目录 cd加路径 相对路径是什么 绝对路径是什么 cd后面没有路径 cd- ls -l ls -a ls -al ls加路径 Linux的命令是数不清的&#xff0c;就像很多应用软件一样&#xff0c;随时都有可能被开发出来。 但是我们目前阶段只需要掌握基本的命令就可以了。 上一篇博文中我已经…

元宇宙APP搭建重点,会用到哪些三方服务?

元宇宙APP的搭建是一个综合性的项目&#xff0c;涉及到众多关键要素和第三方服务。以下是一些元宇宙APP搭建的重点&#xff0c;以及可能用到的第三方服务&#xff1a; 一、搭建重点 技术框架的选择与搭建&#xff1a;元宇宙APP需要稳定、高效的技术框架来支撑其运行。这包括前…

插入排序,搞起来,一路狂奔,数组插入

一点喽 目录 编程实现&#xff1a;程序功能是在一个有序序列中插入一个数后&#xff0c;该数列依然有序 输入测试数据&#xff1a;2 3 5 7 8 23 34 56 78 90 25 程序运行结果&#xff1a;插入之后的数组为 2 3 5 7 8 23 25 34 56 78 90 第一个就…

如何增强交友、婚恋平台、金融等平台的安全性

运营商二要素核验是一种数字身份验证方法&#xff0c;主要使用用户的手机号码和姓名作为核验要素。这两个要素被认为是最基本的用户身份信息&#xff0c;通过运营商的数据库来核实其真实性。 在实际操作中&#xff0c;用户需要提供手机号码和姓名进行验证。应用系统会调用接口…

unity的特性AttriBute详解

unity的特性AttriBute曾经令我大为头疼。因为不动使用的法则&#xff0c;但是教程都是直接就写&#xff0c;卡住就不能继续学下去。令我每一次看到&#xff0c;直接不敢看了。 今天使用文心一言搜索一番&#xff0c;发现&#xff0c;恐惧都是自己想象的&#xff0c;实际上这个…

Docker之存储配置与管理

一、容器本地配置与Docker存储驱动 每个容器都被自动分配了本地存储&#xff0c;也就是内部存储。容器由一个可写容器层和若干只读镜像层组成&#xff0c;容器的数据就存放在这些层中。 容器本地存储采用的是联合文件系统。这种文件系统将其他文件系统合并到一个联合挂载点&a…

Unity类银河恶魔城学习记录15-5,6 p157 Audio time limiter p158 Area sound

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili​​ AreaSound.cs using System.Collections; using System.Collections.G…

Spring boot + Redis + Spring Cache 实现缓存

学习 Redis 的 value 有 5 种常用的数据结构 Redis 存储的是 key-value 结构的数据。key 是字符串类型&#xff0c;value 有 5 种常用的数据结构&#xff1a; Redis 的图形化工具 Another Redis Desktop Manager Spring Data Redis Redis 的 Java 客户端。 Spring Cache Spr…

Pycharm新建工程时使用Python自带解释器的方法

Pycharm新建工程时使用Python自带解释器的方法 新建Project时最好不要新建Python解释器&#xff0c;实践证明&#xff0c;自己新建的Python解释器容易出现各种意想不到的问题。 那么怎样使用Python安装时自带的解释器呢&#xff1f; 看下面的三张截图大家就清楚了。 我的Pyth…

avl excite python二次开发1--python解释器需用内置解释器aws_cmd

avl excite python二次开发1--python解释器需用内置解释器aws_cmd 1、python解释器问题1.1、用外置python解释器&#xff0c;import WSInterface会失败(WSInterface.pyd)1.2、用内置解释器aws_cmd运行py脚本1.3 用内置解释器aws_python执行脚本三级目录 1、python解释器问题 1…