io_contextttttttttttt

news2024/12/23 6:40:18

创建上下文——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_ring
  void* sys_ctx;
  pthread_mutex_t ring_lock;             // protect tail
  pthread_mutex_t completion_lock;       // protect head

  pthread_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 lock
    pthread_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 ring
    ring = 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读写文件的fd

    union {
        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.coloradmin.cn/o/1893826.html

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

相关文章

智胜未来:AI如何重塑SaaS用户增长战略

在当今这个数字化时代&#xff0c;SaaS&#xff08;软件即服务&#xff09;已成为企业运营的重要支柱&#xff0c;而人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;正以前所未有的方式重塑着SaaS行业的面貌&#xff0c;特别是对其用户增长战略产生了深远影响。…

洛谷P7044 「MCOI-03」括号(栈括号的贡献 组合数经典问题)

题目 思路来源 P7044 「MCOI-03」括号 题解 - 洛谷专栏 题解 统计一对括号的贡献&#xff0c;对于一个左括号i&#xff0c;找到其右第一个匹配的括号的位置j 这样对于不包含这个(i,j)对的区间[l,r]&#xff08;1<l<i&#xff0c;i<r<j&#xff09;来说&#xff…

在docker配置Nginx环境配置

应用于商业模式集中&#xff0c;对于各种API的调用&#xff0c;对于我们想要的功能进行暴露&#xff0c;对于不用的进行拦截进行鉴权。用于后面的付费 开发环境 正式上线模式 一、常用命令 停止&#xff1a;docker stop Nginx重启&#xff1a;docker restart Nginx删除服务&a…

【已解决】ip2region解析ip获取地区位置 在linux部署出现java文件操作报错:java.io.FileNotFoundException

1、依赖 <dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>2.7.0</version></dependency>2.加入ip2region.xdb文件 ip2Region下载地址 3.加入到项目里面去 把 ip2region.xdb 文件放…

Windows环境使用SpringBoot整合Minio平替OSS

目录 配置Minio环境 一、下载minio.exe mc.exe 二、设置用户名和密码 用管理员模式打开cmd 三、启动Minio服务器 四、访问WebUI给的地址 SpringBoot整合Minio 一、配置依赖&#xff0c;application.yml 二、代码部分 FileVO MinioConfig MinioUploadService MinioController 三…

Python (Ansbile)脚本高效批量管理服务器和安全

1、简介 在现代 IT 基础设施中&#xff0c;管理大量服务器是一项复杂而繁琐的任务。特别是在检查服务器的存活状态以及 SSH 登录等任务上&#xff0c;手动操作非常耗时且容易出错。本文将介绍如何使用 Python 脚本实现对多台服务器的批量检查和管理&#xff0c;包括检查服务器…

用dify实现简单的Agent应用(AI信息检索)

这篇文章里&#xff0c;我们来聊聊如何使用字节最新的豆包大模型&#xff0c;在 Dify 上来快速完成一个具备理解需求、自主规划、自主选择工具使用的简单智能体&#xff08;Agent&#xff09;。 准备工作 完整准备过程分为&#xff1a;准备 Docker 环境、启动 Dify 程序、启动…

Spring框架的学习SpringMVC(1)

1.什么是MVC (1)MVC其实就是软件架构的一种设计模式&#xff0c;它将软件的系统分为&#xff0c;&#xff08;视图&#xff0c;模型&#xff0c;控制器&#xff09;三个部分 1.1View(视图) 视图也就是&#xff0c;在浏览器显示的那一个部分&#xff0c;是后端数据的呈现 1.…

02-部署LVS-DR群集

1.LVS-DR工作原理 LVS-DR模式&#xff0c;Director Server作为群集的访问入口&#xff0c;不作为网购使用&#xff0c;节点Director Server 与 Real Server 需要在同一个网络中&#xff0c;返回给客户端的数据不需要经过Director Server 为了响应对整个群集的访问&#xff0c;…

实验一 MATLAB \ Python数字图像处理初步

一、实验目的&#xff1a; 1&#xff0e;熟悉及掌握在MATLAB\Python中能够处理哪些格式图像。 2&#xff0e;熟练掌握在MATLAB\Python中如何读取图像。 3&#xff0e;掌握如何利用MATLAB\Python来获取图像的大小、颜色、高度、宽度等等相关信息。 4&#xff0e;掌握如何在M…

电脑录音怎么录?简单四个方法轻松搞定!

在电脑上录制音频是一项非常实用的技能&#xff0c;适合多种场合的需求。例如&#xff0c;你可能需要录制自己的声音&#xff0c;用于录音广播、演示或视频制作&#xff1b;也可能需要录制电脑中的声音&#xff0c;如音乐、游戏音效或在线直播&#xff1b;或者需要捕捉浏览器中…

2024 年 亚太赛 APMCM (A题)中文赛道国际大学生数学建模挑战赛 | 飞行器外形的优化 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; 完整内容可以在文章末尾领取&#xff01; 第一个问…

ScaleCache: A Scalable Page Cache for Multiple Solid-State Drives——论文泛读

EuroSys 2024 Paper 论文阅读笔记整理 问题 高性能存储设备&#xff0c;如具有GB/s级I/O带宽的NVMe SSD&#xff0c;已被广泛应用于企业服务器中。对于处理大量数据&#xff0c;在RAID配置中使用多个SSD很有吸引力&#xff0c;这可以提高I/O性能、可靠性和容量。尽管多个SSD为…

隐私集合求交(PSI)原理深入浅出

隐私集合求交技术是多方安全计算领域的一个子问题&#xff0c;通常也被称为安全求交、隐私保护集合交集或者隐私交集技术等&#xff0c;其目的是允许持有各自数据集的双方或者多方&#xff0c;执行两方或者多方集合的交集计算&#xff0c;当PSI执行完成&#xff0c;一方或者两方…

SQL Server 2022的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 SQL Server 2022主要由4部分组成&#xff0c;分别是数据库引擎、分析服务、集成服务和报表服务。本节将详细介绍这些内容。 1.2.1 SQL Server 2022的数据库引擎 SQL Server 2022的…

90%的铲屎官必遇到家里猫毛满天飞问题,热门宠物空气净化器分享

作为一名资深猫奴&#xff0c;一到换毛季节家中就会忍受猫毛飞舞、异味四溢的双重困扰&#xff1f;花粉加上宠物的毛发和体味&#xff0c;过敏和不适似乎成了家常便饭。尝试过很多半方法&#xff0c;用过空气净化器去除毛和异味&#xff0c;虽然普通空气净化器可能提供一定程度…

MySQL数据库的备份-恢复-日志

一、备份 1.1数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中&#xff0c;数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 1.2造成数据丢失的原因 程序错误人为操作错误运算错误磁盘故障灾难&#xff08;如火灾、地震&#xff09;和盗窃 1.3数…

Flutter——最详细(Drawer)使用教程

背景 应用左侧或右侧导航面板&#xff1b; 属性作用elevation相当于阴影的大小 import package:flutter/material.dart;class CustomDrawer extends StatelessWidget {const CustomDrawer({Key? key}) : super(key: key);overrideWidget build(BuildContext context) {return…

3个让你爽到爆炸的学习工具

We OCR WeOCR 是一个基于浏览器的文字识别工具&#xff0c;用户可以通过上传图片来识别其中的文本信息。它是一个渐进式网络应用程序&#xff08;PWA&#xff09;&#xff0c;可以在浏览器中离线使用。WeOCR 是开源的&#xff0c;并且基于 Tesseract OCR 引擎开发。用户无需在本…

day04-组织架构

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.组织架构-树组件应用树形组件-用层级结构展示信息&#xff0c;可展开或折叠。 2.组织架构-树组件自定义结构3.组织架构-获取组织架构数据4.组织架构-递归转化树形…