Linux 内核101:异步IO

本文参考了:

简述

POSIX 异步 IO interface(AIO)定义了允许进程创建一个或多个异步的 IO 操作的接口。进程可以在 IO 操作完成之后得到操作系统的通知,手段包括:不通知、信号、实例化thread。

POSIX 标准

注意:这只是 POSIX(The Portable Operating System Interface)定义的接口,并不是实现。Linux 内核对异步 IO 的具体实现在后面。(This is an API, not implementation!

POSIX 1003.1标准定义了异步访问文件的 library function:

系统调用描述
aio_read()异步读取文件
aio_write()异步写入文件
aio_fsync发出对当前所有 outstanding 的异步IO操作进行 flush 的请求 (不会阻塞)
aio_error()获取一个处于 outstanding 状态异步 IO 操作的 error code
aio_return()获取一个已经完成的异步 IO 操作的返回码
aio_cancel()取消一个outstanding的异步 IO 操作
aio_suspend()暂停进程,直到至少有一个outstanding的异步 IO 操作完成

使用异步 IO 其实是很简单的。大致来说分为三步:

  1. 程序先是使用普通的open()系统调用。
  2. 然后创建一个叫做aiocb的 control block填满,比较重要的一些字段如下:
  • aio_fildes: 对应文件的 fd(open()系统调用返回的)
  • aio_buf : 为此文件准备的User mode buffer
  • aio_nbytes : 有多少 bytes 应该被传输
  • aio_offset : read、write 操作应该从哪个 offset 开始(注意这个和同步的 IO 操作是独立)
  • aio_sigevent : 调用者想要什么方式获取 IO 成功的回调通知,包括SIGEV_NONESIGEV_SIGNALSIGEV_THREAD,分别对应上文提到的三个通知方式。
  • aio_lio_opcode : IO 操作类型:read write sync

具体的定义如下:

#include <aiocb.h>

struct aiocb {
    /* The order of these fields is implementation-dependent */

    int             aio_fildes;     /* File descriptor */
    off_t           aio_offset;     /* File offset */
    volatile void  *aio_buf;        /* Location of buffer */
    size_t          aio_nbytes;     /* Length of transfer */
    int             aio_reqprio;    /* Request priority */
    struct sigevent aio_sigevent;   /* Notification method */
    int             aio_lio_opcode; /* Operation to be performed;
                                        lio_listio() only */

    /* Various implementation-internal fields not shown */
};

/* Operation codes for 'aio_lio_opcode': */

enum { LIO_READ, LIO_WRITE, LIO_NOP };
复制代码
  1. 最后把aiocb这个 control block 的地址传给aio_read()或者 aio_write()

这两个函数都会在kernel或library将对应 IO 的数据传输加入传输队列之后立即退出。

之后进程可以用aio_error()检查异步 IO 的进行状态:

aio_error()返回值描述
EINPROGRESS还在传输中
0成功完成
其他错误码操作失败

可以用aio_return()获取成功read或write了多少个 bytes,或者-1表示失败。

Linux 内核支持

在操作系统内核不支持异步 IO 的情况下,异步 IO 也能够实现,实现思路如下:aio_read()aio_write()先克隆当前进程,让子进程执行同步的read()write()系统调用,然后父进程结束aio_read()aio_write(),从而实现非阻塞,主进程可以开始做其他事情。显然,这种没有得到内核支持的异步 IO 操作是比较低效的。

Linux 内核从2.6版本起开始支持下面这些系统调用:

系统调用描述
io_setup()为当前进程创建一个异步IO上下文(asynchronous i/o contex)
io_submit()提交一个或多个异步 IO 操作
io_getevents()获取一些 outstanding 异步 IO 的运行状态
io_cancel()取消一个异步 IO
io_destroy()摧毁当前进程的异步 IO 上下文

异步 IO上下文(AIO context)

用户态的进程要调用io_submit()之前,先得调用io_setup()创建异步 IO 上下文。

基本上来说,一个 AIO context 就是一个用于追踪所有进行中的异步IO操作的数据结构,这个 struct 叫做kioctx。一个应用可能会创建多个 AIO context,一个进程的所有kioctx用一个链表相连:ioctx_list

这个ioctx_list保存在该进程的memory descriptor上:

来源于《Understanding the Linux Kernel, Third Edition》 P355

kioctx中有一个重要的数据结构:AIO ringAIO ring的作用是:kernel 把 outstanding 的 异步 IO 操作完成情况写到这里面,由于AIO ring是位于进程的地址空间的,所以进程可以直接从这个数据接口里面读取异步 IO 的状态,而不用执行相对较慢的系统调用。

提交异步 IO 操作

aio_submit()系统调用包含三个参数:

  • ctx_id : io_setup()返回的 id
  • iocbpp : 一个iocb指针的列表,iocb包含描述一个异步 IO 操作的信息。
  • nr : iocbpp的长度

其中iocb和 POSIX 标准中的aiocb是一样的,同样包含aio_fildes, aio_buf, aio_nbytes, aio_offset, aio_lio_opcode这些字段。

Linux kernel 有一个 service routine : sys_io_submit(),执行下列操作:

  1. 检查iocbpp列表包含的iocb descriptors是不是合法的。
  2. 通过xtx_idioctx_list 中检索出对应的kioctx
  3. 对每一个iocb descriptor,执行下列操作:
  • 通过aio_fildes获取文件 fd。
  • 为该异步 IO 操作新建一个kiocb descriptor。
  • 检查 AIO ring中是否有足够的空间来存储完成结果。
  • 根据IO 类型(aio_lio_opcode字段)设置ki_retry方法。
  • 执行aio_run_iocb()函数,这个函数本质上就是调用上一步的ki_retry方法,开始执行 IO 操作。如果ki_retry方法返回EIOCBRETRY,就表示该异步 IO 操作已经被提交了,但是还没有完成。一段时间后aio_lio_opcode之后会被再次执行,当提示 IO 完成时,就调用aio_complete()将完成状态写入到 AIO ring
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值