当前位置:   article > 正文

linux 内核 异步io,linux AIO -- libaio 实现的异步 IO 简介及实现原理

libaio eventfd

1. linux AIO — libaio 实现的异步 IO

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

关于 linux IO 模型及 AIO、POSIX AIO 的简介,请参看上一篇文章

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_eventresult);

io_getevents

等待并获取异步IO请求的事件(也就是异步请求的处理结果)

long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event events, struct timespectimeout);

1.1. iocb 结构

struct iocb主要包含以下字段:

struct iocb

{

/*

* 请求类型

* 如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等

*/

__u16     aio_lio_opcode;

/*

* 要被操作的fd

*/

__u32     aio_fildes;

/*

* 读写操作对应的内存buffer

*/

__u64     aio_buf;

/*

* 需要读写的字节长度

*/

__u64     aio_nbytes;

/*

* 读写操作对应的文件偏移

*/

__s64     aio_offset;

/*

* 请求可携带的私有数据

* 在io_getevents时能够从io_event结果中取得)

*/

__u64     aio_data;

/*

* 可选IOCB_FLAG_RESFD标记

* 表示异步请求处理完成时使用eventfd进行通知

*/

__u32     aio_flags;

/*

* 有IOCB_FLAG_RESFD标记时,接收通知的eventfd

*/

__u32     aio_resfd;

}

1.2. io_event 结构

struct io_event

{

/*

* 对应iocb的aio_data的值

*/

__u64     data;

/*

* 指向对应iocb的指针

*/

__u64     obj;

/*

* 对应IO请求的结果

* >=0: 相当于对应的同步调用的返回值;<0: -errno

*/

__s64     res;

}

2. 异步 IO 上下文

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

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

struct kioctx

{

/*

* 调用者进程对应的内存管理结构

* 代表了调用者的虚拟地址空间

*/

struct mm_struct*     mm;

/*

* 上下文ID,也就是io_context_t句柄的值

* 等于ring_info.mmap_base

*/

unsigned long         user_id;

/*

* 属于同一地址空间的所有kioctx结构通过这个list串连起来

* 链表头是mm->ioctx_list

*/

struct hlist_node     list;

/*

* 等待队列

* io_getevents系统调用可能需要等待

* 调用者就在该等待队列上睡眠

*/

wait_queue_head_t     wait;

/*

* 进行中的请求数目

*/

int                   reqs_active;

/*

* 进行中的请求队列

*/

struct list_head      active_reqs;

/*

* 最大请求数

* 对应io_setup调用的int maxevents参数

*/

unsigned              max_reqs;

/*

* 需要aio线程处理的请求列表

* 某些情况下,IO请求可能交给aio线程来提交

*/

struct list_head      run_list;

/*

* 延迟任务队列

* 当需要aio线程处理请求时,将wq挂入aio线程对应的请求队列

*/

struct delayed_work   wq;

/*

* 存放请求结果io_event结构的ring buffer

*/

struct aio_ring_info  ring_info;

}

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

struct aio_ring_info

{

unsigned long    mmap_base;    // ring buffer 的首地址

unsigned long    mmap_size;    // ring buffer 空间大小

struct page**    ring_pages;    // ring buffer 对应的 page 数组

long            nr_pages;    // 分配空间对应的页面数目

unsigned        nr;            // io_event 的数目

unsigned        tail;        // io_event 的存取游标

}

3. 实现原理

unsigned    id;        // 等于 aio_ring_info 中的 user_id

unsigned    nr;        // 等于 aio_ring_info 中的 nr

unsigned    head;    // io_events 数组队首

unsigned    tail;    // io_events 数组游标

unsigned    magic;    // 用于确定数据结构有没有异常篡改

unsigned    compat_features;

unsigned    incompat_features;

unsigned    header_length;    // aio_ring 结构大小

struct io_event    *io_events;    // io_event buffer 首地址

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

struct kiocb

{

struct kioctx*        ki_ctx;           /* 请求对应的kioctx(上下文结构)*/

struct list_head    ki_run_list;      /* 需要aio线程处理的请求,通过该字段链入ki_ctx->run_list */

struct list_head    ki_list;          /* 链入ki_ctx->active_reqs */

struct file*        ki_filp;          /* 对应的文件指针*/

void __user*        ki_obj.user;      /* 指向用户态的iocb结构*/

__u64                ki_user_data;     /* 等于iocb->aio_data */

loff_t                ki_pos;           /* 等于iocb->aio_offset */

unsigned short        ki_opcode;        /* 等于iocb->aio_lio_opcode */

size_t                ki_nbytes;        /* 等于iocb->aio_nbytes */

char __user *        ki_buf;           /* 等于iocb->aio_buf */

size_t                ki_left;          /* 该请求剩余字节数(初值等于iocb->aio_nbytes)*/

struct eventfd_ctx*    ki_eventfd;       /* 由iocb->aio_resfd对应的eventfd对象*/

ssize_t (*ki_retry)(struct kiocb *);  /*由ki_opcode选择的请求提交函数*/

}

这以后,对应的异步读写请求就通过调用 file->f_op->aio_read 或 file->f_op->aio_write 被提交到了虚拟文件系统,与普通的文件读写请求非常类似,但是提交完后 IO 请求立即返回,而不等待虚拟文件系统完成相应操作。

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

4. 与 POSIX 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 的工作就需要用户进程考虑了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/216267
推荐阅读
相关标签
  

闽ICP备14008679号