• [转]Linux AIO :libaio


    转自 https://www.cnblogs.com/cobbliu/articles/8487161.html

    POSIX AIO 是在用户控件模拟异步 IO 的功能,不需要内核支持,而 linux AIO 则是 linux 内核原声支持的异步 IO 调用,行为更加低级

    关于 linux IO 模型及 AIO、POSIX AIO 的简介,请参看:

    POSIX AIO -- glibc 版本异步 IO 简介

    libaio 实现的异步 IO 主要包含以下接口:

    libaio 实现的异步 IO
    函数 功能 原型
    io_setup 创建一个异步IO上下文(io_context_t是一个句柄) int io_setup(int maxevents, io_context_t *ctxp);
    io_destroy 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) int io_destroy(io_context_t ctx);
    io_submit 提交异步IO请求 long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);
    io_cancel 取消一个异步IO请求 long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);
    io_getevents 等待并获取异步IO请求的事件(也就是异步请求的处理结果) long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

    iocb 结构

    struct iocb主要包含以下字段:

     1 struct iocb
     2 {
     3     /*
     4      * 请求类型
     5      * 如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等
     6      */
     7     __u16     aio_lio_opcode;
     8     /*
     9      * 要被操作的fd
    10      */
    11     __u32     aio_fildes;
    12     /*
    13      * 读写操作对应的内存buffer
    14      */
    15     __u64     aio_buf;
    16     /*
    17      * 需要读写的字节长度
    18      */
    19     __u64     aio_nbytes;
    20     /*
    21      * 读写操作对应的文件偏移
    22      */
    23     __s64     aio_offset;
    24     /*
    25      * 请求可携带的私有数据
    26      * 在io_getevents时能够从io_event结果中取得)
    27      */
    28     __u64     aio_data;
    29     /*
    30      * 可选IOCB_FLAG_RESFD标记
    31      * 表示异步请求处理完成时使用eventfd进行通知
    32      */
    33     __u32     aio_flags;
    34     /*
    35      * 有IOCB_FLAG_RESFD标记时,接收通知的eventfd
    36      */
    37     __u32     aio_resfd;
    38 }
     
    io_event 结构
     1 struct io_event
     2 {
     3     /*
     4      * 对应iocb的aio_data的值
     5      */
     6     __u64     data;
     7     /*
     8      * 指向对应iocb的指针
     9      */
    10     __u64     obj;
    11     /*
    12      * 对应IO请求的结果
    13      * >=0: 相当于对应的同步调用的返回值;<0: -errno
    14      */
    15     __s64     res;
    16 }

    aio_context_t 即 AIO 上下文句柄,该结构体对应内核中的一个 struct kioctx 结构,用来给一组异步 IO 请求提供一个上下文环境,每个进程可以有多个 aio_context_t,io_setup 的第一个参数声明了同时驻留在内核中的异步 IO 上下文数量

    kioctx 结构主要包含以下字段:

     1 struct kioctx
     2 {
     3     /*
     4      * 调用者进程对应的内存管理结构
     5      * 代表了调用者的虚拟地址空间
     6      */
     7     struct mm_struct*     mm;
     8     /*
     9      * 上下文ID,也就是io_context_t句柄的值
    10      * 等于ring_info.mmap_base
    11      */
    12     unsigned long         user_id;
    13     /*
    14      * 属于同一地址空间的所有kioctx结构通过这个list串连起来
    15      * 链表头是mm->ioctx_list
    16      */
    17     struct hlist_node     list;
    18     /*
    19      * 等待队列
    20      * io_getevents系统调用可能需要等待
    21      * 调用者就在该等待队列上睡眠
    22      */
    23     wait_queue_head_t     wait;
    24     /*
    25      * 进行中的请求数目
    26      */
    27     int                   reqs_active;
    28     /*
    29      * 进行中的请求队列
    30      */
    31     struct list_head      active_reqs;
    32     /*
    33      * 最大请求数
    34      * 对应io_setup调用的int maxevents参数
    35      */
    36     unsigned              max_reqs;
    37     /*
    38      * 需要aio线程处理的请求列表
    39      * 某些情况下,IO请求可能交给aio线程来提交
    40      */
    41     struct list_head      run_list;
    42     /*
    43      * 延迟任务队列
    44      * 当需要aio线程处理请求时,将wq挂入aio线程对应的请求队列
    45      */
    46     struct delayed_work   wq;
    47     /*
    48      * 存放请求结果io_event结构的ring buffer
    49      */
    50     struct aio_ring_info  ring_info;
    51 }

    其中,aio_ring_info 结构用于存放请求结果 io_event 结构的 ring buffer,主要包含以下字段:

     
    1 struct aio_ring_info
    2 {
    3     unsigned long   mmap_base;  // ring buffer 的首地址
    4     unsigned long   mmap_size;  // ring buffer 空间大小
    5     struct page**   ring_pages; // ring buffer 对应的 page 数组
    6     long            nr_pages;   // 分配空间对应的页面数目
    7     unsigned        nr;         // io_event 的数目
    8     unsigned        tail;       // io_event 的存取游标
    9 }
     

    aio_ring_info 结构中,nr_page * PAGE_SIZE = mmap_size

    以上数据结构都是在内核地址空间上分配的,是内核专有的,用户程序无法访问和使用

    但是 io_event 结构是内核在用户地址空间上分配的 buffer,用户可以修改,但是首地址、大小等信息都是由内核维护的,用户程序通过 io_getevents 函数修改

    io_setup 函数创建了一个 AIO 上下文,并通过值-结果参数 aio_context_t 类型指针返回其句柄

    io_setup 调用后,内核会通过 mmap 在对应的用户地址空间分配一段内存,由 aio_ring_info 结构中的 mmap_base、mmap_size 描述这个映射对应的位置和大小,由 ring_pages、nr_pages 描述实际分配的物理内存页面信息,异步 IO 完成后,内核会将异步 IO 的结果写入其中

    在 mmap_base 指向的用户地址空间上,会存放着一个 struct aio_ring 结构,用来管理 ring buffer,主要包含以下字段:

    1 unsigned    id;     // 等于 aio_ring_info 中的 user_id
    2 unsigned    nr;     // 等于 aio_ring_info 中的 nr
    3 unsigned    head;   // io_events 数组队首
    4 unsigned    tail;   // io_events 数组游标
    5 unsigned    magic;  // 用于确定数据结构有没有异常篡改
    6 unsigned    compat_features;
    7 unsigned    incompat_features;
    8 unsigned    header_length;  // aio_ring 结构大小
    9 struct io_event *io_events; // io_event buffer 首地址

    这个数据结构存在于用户地址空间中,内核作为生产者,在 buffer 中放入数据,并修改 tail 字段,用户程序作为消费者从 buffer 中取出数据,并修改 head 字段

    每一个请求用户都会创建一个 iocb 结构用于描述这个请求,而对应于用户传递的每一个 iocb 结构,内核都会生成一个与之对应的 kiocb 结构,并只该结构中的 ring_info 中预留一个 io_events 空间,用于保存处理的结果 

     
     1 struct kiocb
     2 {
     3     struct kioctx*      ki_ctx;           /* 请求对应的kioctx(上下文结构)*/
     4     struct list_head    ki_run_list;      /* 需要aio线程处理的请求,通过该字段链入ki_ctx->run_list */
     5     struct list_head    ki_list;          /* 链入ki_ctx->active_reqs */
     6     struct file*        ki_filp;          /* 对应的文件指针*/
     7     void __user*        ki_obj.user;      /* 指向用户态的iocb结构*/
     8     __u64               ki_user_data;     /* 等于iocb->aio_data */
     9     loff_t              ki_pos;           /* 等于iocb->aio_offset */
    10     unsigned short      ki_opcode;        /* 等于iocb->aio_lio_opcode */
    11     size_t              ki_nbytes;        /* 等于iocb->aio_nbytes */
    12     char __user *       ki_buf;           /* 等于iocb->aio_buf */
    13     size_t              ki_left;          /* 该请求剩余字节数(初值等于iocb->aio_nbytes)*/
    14     struct eventfd_ctx* ki_eventfd;       /* 由iocb->aio_resfd对应的eventfd对象*/
    15     ssize_t (*ki_retry)(struct kiocb *);  /*由ki_opcode选择的请求提交函数*/
    16 }
    这以后,对应的异步读写请求就通过调用 file->f_op->aio_read 或 file->f_op->aio_write 被提交到了虚拟文件系统,与普通的文件读写请求非常类似,但是提交完后 IO 请求立即返回,而不等待虚拟文件系统完成相应操作

    对于虚拟文件系统返回 EIOCBRETRY 需要重试的情况,内核会在当前 CPU 的 aio 线程中添加一个任务,让 aio 完成该任务的重新提交

    从上图中的流程就可以看出,linux 版本的 AIO 与 POSIX 版本的 AIO 最大的不同在于 linux 版本的 AIO 实际上利用了 CPU 和 IO 设备异步工作的特性,与同步 IO 相比,很大程度上节约了 CPU 资源的浪费

    而 POSIX AIO 利用了线程与线程之间的异步工作特性,在用户线程中实现 IO 的异步操作

    POSIX AIO 支持非 direct-io,而且实现非常灵活,可配置性很高,可以利用内核提供的page cache来提高效率,而 linux 内核实现的 AIO 就只支持 direct-io,cache 的工作就需要用户进程考虑了

  • 相关阅读:
    一、
    【2019-11-25】美好需要主动去发现
    《软件方法(上)》读书笔记
    【2019-11-24】读书让人美丽
    【2019-11-23】让别人来管理自己
    【2019-11-22】不聪明只有靠笨方法了
    【2019-11-21】要像人一样思考
    【2019-11-20】作为丈夫的反省
    【2019-11-19】基础科学的意义发现
    【2019-11-18】重新审视一下自己的思维
  • 原文地址:https://www.cnblogs.com/yi-mu-xi/p/11238220.html
Copyright © 2020-2023  润新知