io_contextttttttttttt

创建上下文——io_context_t

它是一个上下文结构,在内部它包含一个完成队列,在线程之间是可以共享的。

提交请求——iocb

io回调数据结构,和io_submit配合使用。

处理结果

通过io_event处理结果,

struct io_event {void *data;struct iocb *obj;long long res;
};

2222222222222222222222222222222222222222222222222222222222222222222

Linux异步IO:AIO教程(libaio)

迹寒

迹寒

华中科技大学 计算机系统结构硕士

​关注他

10 人赞同了该文章

AIO使用顺序

  1. 打开一个I/O Context以提交或者获取I/O请求;
  2. 创建一个或多个请求对象并设置;
  3. 将这些请求提交到I/O Context,这些请求会被发送到设备;
  4. 以事件的方式获取完成的对象;
  5. 回到2,循环往复。

创建上下文——io_context_t

它是一个上下文结构,在内部它包含一个完成队列,在线程之间是可以共享的。

struct io_context {int32_t ctx_id;uint32_t aio_max_events;uint32_t aio_pendings;// for system io_context_t, the type is long, point to kernel aio_ringvoid* sys_ctx;pthread_mutex_t ring_lock;             // protect tailpthread_mutex_t completion_lock;       // protect headpthread_spinlock_t rlock;pthread_spinlock_t clock;pthread_spinlock_t nrlock;struct io_event** ring = nullptr;uint32_t head;uint32_t tail;io_context(uint32_t maxevents, uint32_t ctxid): ctx_id(ctxid),aio_max_events(maxevents),aio_pendings(0),sys_ctx(nullptr),head(0),tail(0) {// init lockpthread_mutex_init(&ring_lock, nullptr);pthread_mutex_init(&completion_lock, nullptr);pthread_spin_init(&nrlock, 0);pthread_spin_init(&rlock, 0);pthread_spin_init(&clock, 0);// iocb ringring = reinterpret_cast<struct io_event**>(calloc(maxevents, sizeof(struct io_event*)));}~io_context() {pthread_mutex_destroy(&ring_lock);pthread_mutex_destroy(&completion_lock);pthread_spin_destroy(&nrlock);pthread_spin_destroy(&rlock);pthread_spin_destroy(&clock);free(ring);}
};

如果你需要创建一个io_context_t,请使用:

int io_setup(int maxevents, io_context_t *ctxp);

maxevents是最大事件数,也就是IO队列的长度。如果需要析构一个io_context_t:

int io_destroy(io_context_t ctx);

提交请求——iocb

io回调数据结构,和io_submit配合使用。

struct iocb {void *data;short aio_lio_opcode; // 表明一个op是读还是写,分别用IO_CMD_PREAD和IO_CMD_PWRITE表示int aio_fildes; // iocb读写文件的fdunion {struct {void *buf; // 指向读写内存的指针unsigned long nbytes; // 请求的长度long long offset; // 请求的offset} c;} u;
};

初始化iocb需要用到io_prep_pread andio_prep_pwrite

io_prep_pwritev:为异步写建立iocb,也就是回调函数。

inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);

io_prep_write() 是用于设置并行写入的便捷函数。

iocb->aio_fildes = fd 是描述符的文件的第一个 iocb->u.c.nbytes = count 字节从 iocb->u.c.buf = buf 开始的缓冲区写入。 写入从文件中的绝对位置 ioc->u.c.offset = offset 开始。

该函数立即返回。 要安排操作,必须调用函数 io_submit(3)。

io_prep_pread也类似:

inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);

提交请求需要用到io_submit

int io_submit(io_context_t ctx, long nr, struct iocb *ios[]);

注意它提交的是一组iocb,nrios数组的长度。如果一次提交多个iocb,那么返回的iocb的顺序得不到保证。由于 CPU 使用率的降低,大批量提交有时会导致性能提升。有时还可以通过同时保持许多 I/O 的“运行”来提高性能。

如果提交包含太多iocb,以至于内部队列io_context_t在完成时会溢出,io_submit则将返回一个非零数字并设置errnoEAGAIN。务必使用O_DIRECT标志打开文件时使用该标志,并在原始块设备上进行操作。

处理结果

通过io_event处理结果,

struct io_event {void *data;struct iocb *obj;long long res;
};

data就是传入iocb的数据,obj是原始的iocb,res是读或写返回的结果。

通过以下函数得到io_event:

int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

ctx_id是获取的io_context

min_nr是返回的io_event的最小数量。除非这有min_nr个IO完成事件,否则该函数将会阻塞;

nr是IO完成事件的最大数量,即ios数组长度;

eventsio_events数组,

timeout是调用io_getevents可能阻塞直到它返回的最长时间。如果传入NULL,io_getevents则将阻塞,直到min_nr 个事件完成。

返回值表示报告了多少完成,即写入了多少事件。返回值将介于 0 和 之间nr。返回值可能低于min_nr超时时间;如果超时为NULL,则返回值将介于min_nr和之间nr。

  • min_nr = 0(或者,等效地,timeout = 0)。此选项形成了一种非阻塞轮询技术:无论是否有任何完成可用,它总是会立即返回。在每次迭代中作为应用程序主运行循环的一部分min_nr = 0调用时使用它是有意义的。io_getevents
  • min_nr = 1. 此选项会阻塞,直到有一个完成可用。该参数是产生阻塞调用的最小值,因此对于某些用户来说可能是低延迟操作的最佳值。当应用程序注意到eventfd触发了与 iocb 对应的一个(参见下一节epoll),然后应用程序可以调用io_getevents对应io_context_t的,并保证不会发生阻塞。
  • min_nr > 1. 此选项等待多个完成返回,除非超时到期。由于减少了 CPU 使用率,等待多个完成可能会提高吞吐量,这既是由于更少的io_getevents调用,也是因为如果完成队列中由于移除的完成而有更多的空间,那么稍后的io_submit调用可能具有更大的粒度,以及减少的当事件可用时,上下文切换回调用线程的次数。此选项存在增加操作延迟的风险,尤其是在操作率较低时。

即使min_nr = 0或 1,出于性能原因将 nr 设置得更大一点也是有用的:可能已经完成了多个事件,并且可以在不多次调用 的情况下对其进行处理io_getevents。更大的 nr 值库的唯一成本是用户必须分配更大的事件数组并准备好接受它们。

性能考虑

  • io_submit在ext4、缓冲操作、网络访问、管道等期间阻塞。AIO 接口不能很好地表示某些操作。对于缓冲读取、套接字或管道上的操作等完全不受支持的操作,整个操作将在 io_submit 系统调用期间执行,完成后可立即通过 io_getevents 访问。部分支持对文件系统(如 ext4)上文件的 AIO 访问:如果需要读取元数据来查找数据块(即,如果元数据尚未在内存中),则 io_submit 调用将阻塞读取元数据。某些类型的文件放大写入完全不受支持,并在整个操作期间阻塞。
  • CPU 开销。当在高性能设备上执行小操作并针对单个 CPU 的非常高的操作率时,可能会导致 CPU 瓶颈。这可以通过从多个线程提交和获取 AIO 来解决。
  • 当许多 CPU 或请求共享一个 io_context_t 时锁争用。在某些情况下,可以从多个 CPU 访问对应于 io_context_t 的内核数据结构。例如,多个线程可以从同一个 io_context_t 提交和获取事件。一些设备可能对所有完成使用单个中断线。这可能导致锁在内核之间反弹或锁被严重竞争,从而导致更高的 CPU 使用率和潜在的更低吞吐量。一种解决方案是分片成多个 io_context_t 对象,例如通过线程和地址的哈希。
  • 确保足够的并行性。一些设备需要许多并发操作才能达到峰值性能。这意味着要确保同时有几个“在进行中”的操作。在一些高性能存储设备上,当操作量较小时,必须并行提交数十或数百个,才能达到最大吞吐量。对于磁盘驱动器,如果电梯调度程序可以在飞行中同时进行更多操作做出更好的决策,则性能可能会随着更大的并行性而提高,但在许多情况下预计效果会很小。

AIO的替代技术

  • 同步 I/O 线程的线程池。这适用于许多用例,并且可能更容易编程。与 AIO 不同,所有功能都可以通过线程池并行化。一些用户发现线程池不能很好地工作,因为线程在 CPU 和内存带宽使用方面的开销来自上下文切换。对于高性能存储设备上的小随机读取,这是一个特别大的问题。
  • POSIX AIO。另一个异步 I/O 接口是 POSIX AIO。它是作为 glibc 的一部分实现的。但是,glibc 实现在内部使用线程池。Joel Becker 实现了一个基于上述 Linux AIO 机制的 POSIX AIO版本。IBM DeveloperWorks对 POSIX AIO有很好的介绍。
  • epoll。Linux 对使用 epoll 作为异步 I/O 机制的支持有限。对于以缓冲模式(即没有 O_DIRECT)打开的文件的读取,如果文件以 O_NONBLOCK 方式打开,则读取将返回 EAGAIN,直到相关部分在内存中。对缓冲文件的写入通常是立即的,因为它们是通过另一个写回线程写出的。但是,这些机制并没有提供直接 I/O 提供的对 I/O 的控制级别。

示例代码

下面是一些使用 Linux AIO 的示例代码。我在谷歌写的,所以它使用谷歌 glog 日志库和谷歌 gflags 命令行标志库,以及对谷歌 C++ 编码约定的松散解释。使用 gcc 编译时,传递-laio给与 libaio 动态链接。(它不包含在 glibc 中,因此必须明确包含。)

// Code written by Daniel Ehrenberg, released into the public domain#include <fcntl.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <libaio.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>DEFINE_string(path, "/tmp/testfile", "Path to the file to manipulate");
DEFINE_int32(file_size, 1000, "Length of file in 4k blocks");
DEFINE_int32(concurrent_requests, 100, "Number of concurrent requests");
DEFINE_int32(min_nr, 1, "min_nr");
DEFINE_int32(max_nr, 1, "max_nr");// The size of operation that will occur on the device
static const int kPageSize = 4096;class AIORequest {public:int* buffer_;virtual void Complete(int res) = 0;AIORequest() {int ret = posix_memalign(reinterpret_cast<void**>(&buffer_),kPageSize, kPageSize);CHECK_EQ(ret, 0);}virtual ~AIORequest() {free(buffer_);}
};class Adder {public:virtual void Add(int amount) = 0;virtual ~Adder() { };
};class AIOReadRequest : public AIORequest {private:Adder* adder_;public:AIOReadRequest(Adder* adder) : AIORequest(), adder_(adder) { }virtual void Complete(int res) {CHECK_EQ(res, kPageSize) << "Read incomplete or error " << res;int value = buffer_[0];LOG(INFO) << "Read of " << value << " completed";adder_->Add(value);}
};class AIOWriteRequest : public AIORequest {private:int value_;public:AIOWriteRequest(int value) : AIORequest(), value_(value) {buffer_[0] = value;}virtual void Complete(int res) {CHECK_EQ(res, kPageSize) << "Write incomplete or error " << res;LOG(INFO) << "Write of " << value_ << " completed";}
};class AIOAdder : public Adder {public:int fd_;io_context_t ioctx_;int counter_;int reap_counter_;int sum_;int length_;AIOAdder(int length): ioctx_(0), counter_(0), reap_counter_(0), sum_(0), length_(length) { }void Init() {LOG(INFO) << "Opening file";fd_ = open(FLAGS_path.c_str(), O_RDWR | O_DIRECT | O_CREAT, 0644);PCHECK(fd_ >= 0) << "Error opening file";LOG(INFO) << "Allocating enough space for the sum";PCHECK(fallocate(fd_, 0, 0, kPageSize * length_) >= 0) << "Error in fallocate";LOG(INFO) << "Setting up the io context";PCHECK(io_setup(100, &ioctx_) >= 0) << "Error in io_setup";}virtual void Add(int amount) {sum_ += amount;LOG(INFO) << "Adding " << amount << " for a total of " << sum_;}void SubmitWrite() {LOG(INFO) << "Submitting a write to " << counter_;struct iocb iocb;struct iocb* iocbs = &iocb;AIORequest *req = new AIOWriteRequest(counter_);io_prep_pwrite(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize);iocb.data = req;int res = io_submit(ioctx_, 1, &iocbs);CHECK_EQ(res, 1);}void WriteFile() {reap_counter_ = 0;for (counter_ = 0; counter_ < length_; counter_++) {SubmitWrite();Reap();}ReapRemaining();}void SubmitRead() {LOG(INFO) << "Submitting a read from " << counter_;struct iocb iocb;struct iocb* iocbs = &iocb;AIORequest *req = new AIOReadRequest(this);io_prep_pread(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize);iocb.data = req;int res = io_submit(ioctx_, 1, &iocbs);CHECK_EQ(res, 1);}void ReadFile() {reap_counter_ = 0;for (counter_ = 0; counter_ < length_; counter_++) {SubmitRead();Reap();}ReapRemaining();}int DoReap(int min_nr) {LOG(INFO) << "Reaping between " << min_nr << " and "<< FLAGS_max_nr << " io_events";struct io_event* events = new io_event[FLAGS_max_nr];struct timespec timeout;timeout.tv_sec = 0;timeout.tv_nsec = 100000000;int num_events;LOG(INFO) << "Calling io_getevents";num_events = io_getevents(ioctx_, min_nr, FLAGS_max_nr, events,&timeout);LOG(INFO) << "Calling completion function on results";for (int i = 0; i < num_events; i++) {struct io_event event = events[i];AIORequest* req = static_cast<AIORequest*>(event.data);req->Complete(event.res);delete req;}delete events;LOG(INFO) << "Reaped " << num_events << " io_events";reap_counter_ += num_events;return num_events;}void Reap() {if (counter_ >= FLAGS_min_nr) {DoReap(FLAGS_min_nr);}}void ReapRemaining() {while (reap_counter_ < length_) {DoReap(1);}}~AIOAdder() {LOG(INFO) << "Closing AIO context and file";io_destroy(ioctx_);close(fd_);}int Sum() {LOG(INFO) << "Writing consecutive integers to file";WriteFile();LOG(INFO) << "Reading consecutive integers from file";ReadFile();return sum_;}
};int main(int argc, char* argv[]) {google::ParseCommandLineFlags(&argc, &argv, true);AIOAdder adder(FLAGS_file_size);adder.Init();int sum = adder.Sum();int expected = (FLAGS_file_size * (FLAGS_file_size - 1)) / 2;LOG(INFO) << "AIO is complete";CHECK_EQ(sum, expected) << "Expected " << expected << " Got " << sum;printf("Successfully calculated that the sum of integers from 0"" to %d is %d\n", FLAGS_file_size - 1, sum);return 0;
}

参考资料:

https://github.com/littledan/linux-aio

编辑于 2022-06-28 11:06

 

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

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

相关文章

搜狗输入法产品使用说明

搜狗输入法产品使用说明 (qq.com) 看这里&#xff0c;这里记录下。基本都用这个&#xff0c;里面还是有很多小技巧不知道&#xff0c;可以参考下。

【刷题汇总--Fibonacci数列、单词搜索、杨辉三角】

C日常刷题积累 今日刷题汇总 - day0041、Fibonacci数列1.1、题目1.2、思路1.3、程序实现 2、单词搜索2.1、题目2.2、思路2.3、程序实现 3、杨辉三角3.1、题目3.2、思路3.3、程序实现 - 蛮力法3.4、程序实现 - vector3.5、程序实现 - dp 4、题目链接 今日刷题汇总 - day004 1、…

【CUDA】 矩阵乘向量 matVecMul

Matrix - Vector Multiplication 矩阵-向量乘法是线性代数中的基本操作。它用于将一个矩阵与一个向量相乘。乘法的结果是与输入向量大小相同的向量。 矩阵和向量的乘法如图1所示。 图1 基础kernel与共享内存kernel 执行矩阵-向量乘法的基础kernel是使用单个线程执行输出向量…

【SOLID原则前端中的应用】开闭原则(Open/Closed Principle)- vue3示例

开闭原则&#xff08;Open/Closed Principle&#xff09;在Vue 3中的应用 开闭原则&#xff08;Open/Closed Principle&#xff0c;OCP&#xff09;规定&#xff0c;软件实体&#xff08;类、模块、函数等&#xff09;应该对扩展开放&#xff0c;对修改关闭。 也就是说&#xf…

大型网站软件系统架构演进过程

在我们的生活中,通常会使用大型网站系统,比如购物网站淘宝,京东,阿里1688;大型搜索引擎网站百度,社交类的如腾讯旗下的微信,QQ及新浪旗下的微博等,他们通常都有一下特点: 高并发、大流量&#xff1a;这些系统必须能够处理成千上万甚至数百万的并发用户请求&#xff0c;以及持续…

Dubbo内部通信流程

我当时在学习的过程中搭建过demo&#xff0c;具体流程就是&#xff0c;我先定义了一个api接口模块&#xff0c;还定义一个服务提供者模块&#xff0c;然后服务提供方实现该接口&#xff0c;定义该方法具体的实现impl类&#xff0c;服务提供方启动时&#xff0c;将要暴露的服务和…

运维行业的全新视界:一体化监控解决方案

在当今的IT环境中&#xff0c;运维团队面临着前所未有的挑战。随着技术的快速发展&#xff0c;企业需要更加高效、智能的监控工具来确保IT基础设施的稳定运行。本文旨在为运维团队提供一个全面的、集成多种功能的监控解决方案参考。 一、总体架构与要求 首先&#xff0c;一个理…

Python中的模块和包定义以及如何在Python中导入和使用它们

在Python中&#xff0c;模块&#xff08;Module&#xff09;和包&#xff08;Package&#xff09;是组织代码、重用代码的基本单位&#xff0c;它们让Python的编程更加模块化&#xff0c;易于管理和维护。 模块&#xff08;Module&#xff09; 模块是一个包含Python定义和声明…

金融科技的移动时代:探索APP与SaaS系统的结合之道

金融科技&#xff08;FinTech&#xff09;的革新正在重塑金融服务行业的未来。移动应用&#xff08;APP&#xff09;与软件即服务&#xff08;SaaS&#xff09;系统的结合&#xff0c;为金融行业带来了前所未有的灵活性和可扩展性。 一、金融科技的发展趋势 金融科技通过技术…

在线快速制作二维码的技巧,支持生成多种内容二维码

现在用二维码来分享内容是很多场景下会使用的一种方式&#xff0c;常见的展示内容有图片、文件、文本、音频、视频等&#xff0c;都可以生成二维码之后。通过手机扫码来查看内容&#xff0c;有利于内容的快速传播&#xff0c;并且用户获取信息也更加的方便。 下面来教大家使用…

ubuntu rc.local开机自启动

https://blog.csdn.net/qq_48974566/article/details/137212295

Web应用防火墙用在哪些场景?

WAF是Web Application Firewall的缩写&#xff0c;翻译为“Web应用防火墙”是一种网络安全设备或服务&#xff0c;用于保护Web应用程序免受各种网络攻击和漏洞的影响。 WAF特别设计用于识别和阻止特定于Web应用程序的攻击&#xff0c;例如SQL注入、跨站脚本(XSS)、跨站请求伪造…

力扣习题--哈沙德数

一、前言 本系列主要讲解和分析力扣习题&#xff0c;所以的习题均来自于力扣官网题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 二、哈沙德数 1. 哈沙德数 如果一个整数能够被其各个数位上的数字之和整除&#xff0c;则称之为 哈沙德数&#xff08;Harshad number&…

CTF常用sql注入(二)报错注入(普通以及双查询)

0x05 报错注入 适用于页面无正常回显&#xff0c;但是有报错&#xff0c;那么就可以使用报错注入 基础函数 floor() 向下取整函数 返回小于或等于传入参数的最大整数。换句话说&#xff0c;它将数字向下取整到最接近的整数值。 示例&#xff1a; floor(3.7) 返回 3 floor(-2…

5.基于SpringBoot的SSMP整合案例-数据层开发

目录 1.新建项目 2.实体类开发&#xff1a; 2.1在pom.xml中增加Lombok坐标&#xff1a; 2.2添加Book实体类 3.数据层开发&#xff1a; 3.1 配置MyBatisPlus与Druid 3.2创建数据层接口 3.3写测试类 3.4点击运行&#xff1a; 4.数据层快速开发&#xff1a; 4.1配置MyB…

流式数据库 RisingWave「Demo」:直播指标实时分析

直播因其能与观众进行实时互动的独特优势&#xff0c;成为目前最为流行的娱乐形式之一。想要优化直播效果&#xff0c;有许多指标需要跟踪。最常见的指标比如&#xff1a;人流量、评论数量、直播卡顿时长等等。 本教程将分享如何使用 RisingWave 监控直播流量指标。我们为本教…

python访问mongoDB

pip install pymongo1.建立连接 &#xff08;1&#xff09;模块引用 import pymongoClientMongoClient(host10.90.9.101,port27017)(2)访问数据库 dbclient.myDBdbconn.get_database("myDB")2.集合操作 &#xff08;1&#xff09;插入文档 colldb.get_collection(…

docker-compose version is obsolete

如果更新了docker或者docker-desktop 启动时候发现有 version is obsolete警告 删除yml第一行的version字段 上社区原文 Technically you can still define it… But you will get the warning you observed, and it won’t be used, as docker compose v2 (the cli plugin…

关于TaOTUB1的信息总结

关于TaOTUB1的信息总结 文献标题: Reducing expression of TaOTUB1s decreases tiller number in wheat 核心内容: 背景和目的: 小麦的分蘖数量是影响产量的重要农艺性状。OTUB1在水稻中被发现能够调控分蘖数量&#xff0c;而在小麦中的功能尚不明确。本研究旨在通过鉴定和分…

C++视觉开发 四.手势识别

本章记录传统手势识别&#xff0c;在表示0-5六个数值时的识别问题。例如识别剪刀石头布&#xff0c;手势&#xff0c;以及其表示的动作。在识别时将手势中的凹陷区域称为凸缺陷&#xff0c;其个数作为识别的重要依据。 需要注意&#xff0c;在凸缺陷个数为0时&#xff0c;无法…