0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

AIO编程的相关知识

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-10 16:17 次阅读

在Direct IO模式下,异步是非常有必要的(因为绕过了pagecache,直接和磁盘交互)。linux Native AIO正是基于这种场景设计的,具体的介绍见:KernelAsynchronousI/O (AIO)SupportforLinux。下面我们就来分析一下AIO编程的相关知识。

阻塞模式下的IO过程如下:

int fd = open(const char *pathname, int flags, mode_t mode);
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
int close(int fd);

因为整个过程会等待read/write的返回,所以不需要任何额外的数据结构。但异步IO的思想是:应用程序不能阻塞在昂贵的系统调用上让CPU睡大觉,而是将IO操作抽象成一个个的任务单元提交给内核,内核完成IO任务后将结果放在应用程序可以取到的地方。这样在底层做I/O的这段时间内,CPU可以去干其他的计算任务。但异步的IO任务批量的提交和完成,必须有自身可描述的结构,最重要的两个就是iocb和io_event。

libaio中的structs

struct iocb {
        void     *data;  /* Return in the io completion event */
        unsigned key;   /*r use in identifying io requests */
        short           aio_lio_opcode;
        short           aio_reqprio;
        int             aio_fildes;
        union {
                struct io_iocb_common           c;
                struct io_iocb_vector           v;
                struct io_iocb_poll             poll;
                struct io_iocb_sockaddr saddr;
        } u;
};
struct io_iocb_common {
        void            *buf;
        unsigned long   nbytes;
        long long       offset;
        unsigned        flags;
        unsigned        resfd;
};

iocb是提交IO任务时用到的,可以完整地描述一个IO请求:

data是留给用来自定义的指针:可以设置为IO完成后的callback函数;

aio_lio_opcode表示操作的类型:IO_CMD_PWRITE | IO_CMD_PREAD;

aio_fildes是要操作的文件:fd;

io_iocb_common中的buf, nbytes, offset分别记录的IO请求的mem buffer,大小和偏移。

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

io_event是用来描述返回结果的:

obj就是之前提交IO任务时的iocb;

res和res2来表示IO任务完成的状态。

libaio提供的API和完成IO的过程

libaio提供的API有:io_setup, io_submit, io_getevents, io_destroy。

  1. 建立IO任务
int io_setup (int maxevents, io_context_t *ctxp);

io_context_t对应内核中一个结构,为异步IO请求提供上下文环境。注意在setup前必须将io_context_t初始化为0。

当然,这里也需要open需要操作的文件,注意设置O_DIRECT标志。

2.提交IO任务

long io_submit (aio_context_t ctx_id, long nr, struct iocb **iocbpp);

提交任务之前必须先填充iocb结构体,libaio提供的包装函数说明了需要完成的工作:

void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
{
        memset(iocb, 0, sizeof(*iocb));
        iocb- >aio_fildes = fd;
        iocb- >aio_lio_opcode = IO_CMD_PREAD;
        iocb- >aio_reqprio = 0;
        iocb- >u.c.buf = buf;
        iocb- >u.c.nbytes = count;
        iocb- >u.c.offset = offset;
}
void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)
{
        memset(iocb, 0, sizeof(*iocb));
        iocb- >aio_fildes = fd;
        iocb- >aio_lio_opcode = IO_CMD_PWRITE;
        iocb- >aio_reqprio = 0;
        iocb- >u.c.buf = buf;
        iocb- >u.c.nbytes = count;
        iocb- >u.c.offset = offset;
}

这里注意读写的buf都必须是按扇区对齐的,可以用posix_memalign来分配。

3.获取完成的IO

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

这里最重要的就是提供一个io_event数组给内核来copy完成的IO请求到这里,数组的大小是io_setup时指定的maxevents。

timeout是指等待IO完成的超时时间,设置为NULL表示一直等待所有到IO的完成。

4.销毁IO任务

int io_destroy (io_context_t ctx);

libaio和epoll的结合

在异步编程中,任何一个环节的阻塞都会导致整个程序的阻塞,所以一定要避免在io_getevents调用时阻塞式的等待。还记得io_iocb_common中的flags和resfd吗?看看libaio是如何提供io_getevents和事件循环的结合:

void io_set_eventfd(struct iocb *iocb, int eventfd)
{
        iocb- >u.c.flags |= (1 < < 0) /* IOCB_FLAG_RESFD */;
        iocb- >u.c.resfd = eventfd;
}

这里的resfd是通过系统调用eventfd生成的。

int eventfd(unsigned int initval, int flags);

eventfd是linux 2.6.22内核之后加进来的syscall,作用是内核用来通知应用程序发生的事件的数量,从而使应用程序不用频繁地去轮询内核是否有时间发生,而是由内核将发生事件的数量写入到该fd,应用程序发现fd可读后,从fd读取该数值,并马上去内核读取。

有了eventfd,就可以很好地将libaio和epoll事件循环结合起来:

  1. 创建一个eventfd
efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
  1. 将eventfd设置到iocb中
io_set_eventfd(iocb, efd);
  1. 交接AIO请求
io_submit(ctx, NUM_EVENTS, iocb);
  1. 创建一个epollfd,并将eventfd加到epoll中
epfd = epoll_create(1);
epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &epevent);
epoll_wait(epfd, &epevent, 1, -1);
  1. 当eventfd可读时,从eventfd读出完成IO请求的数量,并调用io_getevents获取这些IO
read(efd, &finished_aio, sizeof(finished_aio);
r = io_getevents(ctx, 1, NUM_EVENTS, events, &tms);

图片

异步非阻塞IO模型的流程图

一个完整的编程实例

#define _GNU_SOURCE
#define __STDC_FORMAT_MACROS


#include < stdio.h >
#include < errno.h >
#include < libaio.h >
#include < sys/eventfd.h >
#include < sys/epoll.h >
#include < stdlib.h >
#include < sys/types.h >
#include < unistd.h >
#include < stdint.h >
#include < sys/stat.h >
#include < fcntl.h >
#include < inttypes.h >


#define TEST_FILE   "aio_test_file"
#define TEST_FILE_SIZE  (127 * 1024)
#define NUM_EVENTS  128
#define ALIGN_SIZE  512
#define RD_WR_SIZE  1024


struct custom_iocb
{
    struct iocb iocb;
    int nth_request;
};


void aio_callback(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
    struct custom_iocb *iocbp = (struct custom_iocb *)iocb;
    printf("nth_request: %d, request_type: %s, offset: %lld, length: %lu, res: %ld, res2: %ldn", 
            iocbp- >nth_request, (iocb- >aio_lio_opcode == IO_CMD_PREAD) ? "READ" : "WRITE",
            iocb- >u.c.offset, iocb- >u.c.nbytes, res, res2);
}


int main(int argc, char *argv[])
{
    int efd, fd, epfd;
    io_context_t ctx;
    struct timespec tms;
    struct io_event events[NUM_EVENTS];
    struct custom_iocb iocbs[NUM_EVENTS];
    struct iocb *iocbps[NUM_EVENTS];
    struct custom_iocb *iocbp;
    int i, j, r;
    void *buf;
    struct epoll_event epevent;


    efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (efd == -1) {
        perror("eventfd");
        return 2;
    }


    fd = open(TEST_FILE, O_RDWR | O_CREAT | O_DIRECT, 0644);
    if (fd == -1) {
        perror("open");
        return 3;
    }
    ftruncate(fd, TEST_FILE_SIZE);
    
    ctx = 0;
    if (io_setup(8192, &ctx)) {
        perror("io_setup");
        return 4;
    }


    if (posix_memalign(&buf, ALIGN_SIZE, RD_WR_SIZE)) {
        perror("posix_memalign");
        return 5;
    }
    printf("buf: %pn", buf);


    for (i = 0, iocbp = iocbs; i < NUM_EVENTS; ++i, ++iocbp) {
        iocbps[i] = &iocbp- >iocb;
        io_prep_pread(&iocbp- >iocb, fd, buf, RD_WR_SIZE, i * RD_WR_SIZE);
        io_set_eventfd(&iocbp- >iocb, efd);
        io_set_callback(&iocbp- >iocb, aio_callback);
        iocbp- >nth_request = i + 1;
    }


    if (io_submit(ctx, NUM_EVENTS, iocbps) != NUM_EVENTS) {
        perror("io_submit");
        return 6;
    }


    epfd = epoll_create(1);
    if (epfd == -1) {
        perror("epoll_create");
        return 7;
    }


    epevent.events = EPOLLIN | EPOLLET;
    epevent.data.ptr = NULL;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &epevent)) {
        perror("epoll_ctl");
        return 8;
    }


    i = 0;
    while (i < NUM_EVENTS) {
        uint64_t finished_aio;


        if (epoll_wait(epfd, &epevent, 1, -1) != 1) {
            perror("epoll_wait");
            return 9;
        }


        if (read(efd, &finished_aio, sizeof(finished_aio)) != sizeof(finished_aio)) {
            perror("read");
            return 10;
        }


        printf("finished io number: %"PRIu64"n", finished_aio);
    
        while (finished_aio > 0) {
            tms.tv_sec = 0;
            tms.tv_nsec = 0;
            r = io_getevents(ctx, 1, NUM_EVENTS, events, &tms);
            if (r > 0) {
                for (j = 0; j < r; ++j) {
                    ((io_callback_t)(events[j].data))(ctx, events[j].obj, events[j].res, events[j].res2);
                }
                i += r;
                finished_aio -= r;
            }
        }
    }
    
    close(epfd);
    free(buf);
    io_destroy(ctx);
    close(fd);
    close(efd);
    remove(TEST_FILE);


    return 0;
}

说明:

  1. 在centos 6.2 (libaio-devel 0.3.107-10) 上运行通过
  2. struct io_event中的res字段表示读到的字节数或者一个负数错误码。在后一种情况下,-res表示对应的

errno。res2字段为0表示成功,否则失败

  1. iocb在aio请求执行过程中必须是valid的
  2. 在上面的程序中,通过扩展iocb结构来保存额外的信息(nth_request),并使用iocb.data

来保存回调函数的地址。如果回调函数是固定的,那么也可以使用iocb.data来保存额外信息。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10446

    浏览量

    206571
  • 编程
    +关注

    关注

    88

    文章

    3441

    浏览量

    92406
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39900
  • AIO
    AIO
    +关注

    关注

    1

    文章

    61

    浏览量

    9916
收藏 人收藏

    评论

    相关推荐

    请问AIO与GPIO有什么本质的区别?

    AIO与GPIO有什么本质的区别?如果我只想输出,或者输入一个高电平(低电平)。GPIO 与AIO在电路上有什么区别?TI官网上哪份PDF里有介绍?
    发表于 09-29 15:09

    AIO和PC应用的DP源参考原理图

    PI3WVR31310A用于笔记本电脑,AIO和PC应用的DP源参考原理图。 PI3WVR31xxxA开关系列开发用于在笔记本电脑,AIO,PC,开关盒,扩展坞,监视器和HDTV应用中切换DP1.2,DP1.3,HDMI 2.0(PI3WVR31310A)的高速信号时实现
    发表于 07-15 09:27

    介绍UG编程的基本操作及相关加工工艺知识

    UG编程基本操作及工艺介绍分析本章主要介绍UG编程的基本操作及相关加工工艺知识,读者学习完本章后将会对UG编程
    发表于 09-01 06:36

    VisualCppRedist AIO是什么?有何作用

    微信关注“DLGG创客DIY”设为“星标”,重磅干货,第一时间送达。最近发现一个不错的项目——VisualCppRedist AIO,一键安装各种系统运行所需的DLL。项...
    发表于 01-11 08:28

    AIO-3399ProC NPU开发相关资料推荐

    1、AIO-3399ProCNPU开发简介AIO-3399ProC 开发者需要注意:NPU推理阶段会与CPU进行数据通信,单次传输数据量少但频率高,但是与USB3.0相比PCIE不适合小文件
    发表于 07-01 17:38

    AIO-3399ProC无法开机请问如何重新烧录?

    问题描述及复现步骤:误把ROC-RK3399-PC-Pro提供的Android10固件烧录到AIO-3399ProC,导致AIO-3399ProC无法开机,开机画面一直卡在如下图所示,现在无法进入loader模式,请问如何重新烧录?
    发表于 03-13 14:29

    电容与电器器相关知识

    电容与电器器相关知识
    发表于 11-18 16:50 14次下载

    温控器相关知识

    温控器相关知识
    发表于 06-30 13:14 1136次阅读

    消毒柜相关知识

    消毒柜相关知识 为使大家进一步了解消毒柜基本知识,现简单介绍一些消毒柜相关知识。     基本术
    发表于 01-14 16:40 1217次阅读

    protel,pcb,单片机,stm32相关知识

    protel相关知识,pcb相关知识,单片机相关知识,stm32
    发表于 03-10 17:18 0次下载

    什么是多线程编程?多线程编程基础知识

    摘要:多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程,这就要牵涉到多进程。本文主要以多线程编程以及多线程编程相关知识而做出的一些结
    发表于 12-08 16:30 1.2w次阅读

    Linux内核模块编程必须了解哪些知识

    模块编程属于内核编程,因此,除了对内核相关知识有所了解外,还需要了解与模块相关知识
    发表于 08-24 17:15 8次下载
    Linux内核模块<b class='flag-5'>编程</b>必须了解哪些<b class='flag-5'>知识</b>?

    043-ACMICPC相关知识

    043-ACMICPC相关知识(开关电源中高压电容怎么选择)-ACMICPC相关知识;ACMICPC相关
    发表于 07-26 11:56 12次下载
    043-ACMICPC<b class='flag-5'>相关</b><b class='flag-5'>知识</b>

    总降调度相关知识

    总降调度相关知识(开关电源技术与设计潘pdf)-总降调度相关知识                   
    发表于 09-23 16:33 8次下载
    总降调度<b class='flag-5'>相关</b><b class='flag-5'>知识</b>

    母线保护相关知识分享

    母线保护相关知识分享
    的头像 发表于 01-19 10:29 183次阅读
    母线保护<b class='flag-5'>相关</b><b class='flag-5'>知识</b>分享