Linux 异步IO(AIO)

摘要:
目录1.select和poll2.BSD异步IO3.POSIX异步IO(AIO)3.1AIO控制块3.2aio_read&aio_write3.3aio_fsync3.4aio_error3.5aio_return3.6aio_suspend3.7aio_cancel3.8lio_listio4.AIO的使用例程4.1同步IO操作4.2异步IO操作(AIO)1.select和pollIO多路转换技
目录

1.select和poll

IO多路转换技术, select, poll的原理是: 通过将待监听文件描述符(fd)加入集合, 然后通过查询其调用返回值, 取得数据有动静的fd数量, 再轮询集合中每个fd, 如果有数据, 就直接读取; 如果没有数据, 就等待下一次查询.
select和poll实现了异步形式通知, 但本质上还是需要主动轮询.

2. BSD异步IO

System V和BSD都有一套各自的异步IO, 原理类似, 这里只介绍BSD异步IO.
BSD异步IO是信号SIGIO, SIGURG的组合: SIGIO 通用异步IO信号; SIGURG 用来通知进程网络连接上的带外数据(data of band, 紧急数据)已经达到.

进程接收SIGIO信号, 需要执行的步骤:

  1. 调用signal/sigaction为SIGIO信号建立处理程序;
  2. 调用fcntl, 命令参数F_SETOWN, 设置进程ID或进程组ID, 用于告诉驱动程序/内核, 指定进程接收SIGIO信号;
  3. 调用fcntl, 命令参数F_SETFL, 设置O_ASYNC文件状态标识, 以便在该fd上可以进行异步IO;

进程接收SIGURG, 只需只需第1,2步. 信号仅对引用支持带外数据的网络连接描述符 产生, 如TCP连接(UDP不支持).

什么是带外数据?
请参考带外数据 | 博客园

BSD异步IO例程
完整源代码, 请参见 async.c
关键步骤代码

void sig_fun() {
    int data = 0;
    int n = read(mousefd, &data, sizeof(data));
    if (n < 0) {
        printf("read mouse error
");
    }
    else {
        printf("%d
", data);
    }
}

struct sigaction sa;
struct sigaction od_sa;

sa.sa_handler = sig_fun;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGIO); // SIGIO添加进信号集
sa.sa_flags = 0;

// 1. 调用sigaction为SIGIO信号建立信号处理程序
sigaction(SIGIO, &sa, &od_sa); // 捕获SIGIO后, 处理信号时, 阻塞信号; 处理完毕后恢复

// 2. 以命令F_SETOWN调用fcntl来设置进程ID, 用于接收对该描述符的信号(SIGIO)
if (fcntl(mousefd, F_SETOWN, getpid()) < 0) {
    perror("fcntl F_SETOWN error");
    exit(1);
}

// 3. 以命令F_SETFL调用fcntl, 设置O_ASYNC文件状态标识, 使得在该描述符上可以进行异步IO
int flag = fcntl(mousefd, F_GETFL);
if (flag < 0) {
    perror("fcntl F_GETFL error");
    exit(1);
}
flag |= O_ASYNC;

ret = fcntl(mousefd, F_SETFL, flag);
if (ret < 0) {
    perror("fcntl F_SETFL error");
    exit(1);
}

while (1) {
    usleep(50);
}

3. POSIX异步IO(AIO)

BSD对不同的设备文件进行异步IO方法不一样, 如终端设备是产生SIGIO信号, 仅支持带外数据的设备才能产生SIGURG信号.
POSIX对不同类型文件进行异步IO提供一套一致的方法, SUSv4中, 这些接口被移到了基本部分中, 所以现在所有的平台都被要求支持这些接口.

3.1 AIO控制块

异步IO接口使用AIO控制块来描述IO操作.
AIO控制块由aiocb结构定义:

#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 */
};

字段说明
aio_fildes 表示已打开的文件描述符, 用于读/写;
aio_offset 读写操作从aio_offset指定的偏移量开始;
aio_buf 用于读写操作转存数据的缓冲区;
aio_nbytes 缓冲区aio_buf的大小;
aio_reqprio 应用程序使用该字段为异步IO请求提示顺序. 值必须介于0和sysconf(_SC_AIO_PRIO_DELTA_MAX)返回值之间. 文件同步操作忽略该字段;
aio_lio_opcode 应当进行的操作类型, 只能用于lio_listio (基于列表的异步IO), 值描述见lio_listio章节;
aio_sigevent 指明IO事件完成后, 如何通知应用程序.
sigevent结构:

union sigval {          /* Data passed with notification */
   int     sival_int;         /* Integer value */
   void   *sival_ptr;         /* Pointer value */
};

struct sigevent {
   int          sigev_notify; /* Notification method */
   int          sigev_signo;  /* Notification signal */
   union sigval sigev_value;  /* Data passed with
                                 notification */
   void       (*sigev_notify_function) (union sigval);
                    /* Function used for thread notification (SIGEV_THREAD) */
   void        *sigev_notify_attributes;
                    /* Attributes for notification thread (SIGEV_THREAD) */
   pid_t        sigev_notify_thread_id;
                    /* ID of thread to signal (SIGEV_THREAD_ID) */
};

sigevent字段说明:
-sigev_notify 通知类型, 其取值只能是这3个之一: SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD.
1)SIGEV_NONE 异步IO请求完成后, 不通知进程;
2)SIGEV_SIGNAL 异步IO请求完成后, 产生由sigev_signo字段指定的信号. 也就是说, 需要应用程序捕捉sigev_signo表示的信号, 并在信号处理程序中完成IO数据操作.
3)SIGEV_THREAD 异步IO请求完成时, 调用sigev_notify_function指定的函数, sigev_value作为唯一参数被传入. 除非sigev_notify_attributes字段被设定为pthread属性结构的地址, 且该结构指定了一个另外的线程属性, 否则该函数将在线程分离状态的一个单独的线程中执行.

3.2 aio_read & aio_write

使用异步IO前, 应先对AIO控制块(struct aiocb对象)进行初始化.

aio_read - 异步读, aio_write - 异步写:

#include <aio.h>

int aio_read(struct aiocb *aiocb);

int aio_write(struct aiocb *aiocb);

描述
将异步IO请求放入等待处理的队列中(函数提出请求, 由OS放入). 函数返回值与实际IO操作结果没有关系. IO操作等待时, 需确保AIO控制块和数据缓冲区保持稳定, 下面对应的内容也必须始终合法, 不能被释放, 也不能被复用, 除非IO操作完成.
aio_read是read的异步模拟, aio_write是write的异步模拟.

read(fd, buf, count);
write(fd, buf, n);

返回值
成功返回0; 失败-1

3.3 aio_fsync

aio_fsync - 异步文件同步:
强制所有(等待队中)等待的异步操作不等待, 而直接写入持久化的存储中(通常指磁盘, emmc等), 可以设置一个AIO控制块并调用aio_fsync.

#include <aio.h>

int aio_fsync(int ap, struct aiocb *aiocb);

描述
aiocb->aio_fildes字段(文件描述符)指定异步写操作被同步的文件.
如果op = O_DSYNC, 那么操作执行像调用fdatasync, 函数立即返回, 但IO操作完成前, 文件数据不会被持久化;
如果op = O_SYNC, 那么操作执行像调用fsync, 函数立即返回, 但IO操作完成前, 文件数据和属性不会被持久化;

sync, fsync, fdatasync, fflush是什么?
参考sync、fsync、fdatasync、fflush函数区别和使用举例 | CSDN

函数名称作用描述
sync将所有修改过的(内核)快缓存区排队进写队列, 然后返回, 并不等待实际写磁盘操作结束
fsync只对由fd指定单一文件起作用, 并且等待磁盘操作结束, 然后返回
fdatasync类似于fsync, 但只影响文件的数据部分, 不像fsync还会同步更新文件的属性
fflush冲刷IO库缓存, 将库缓存内容写入内核缓冲区

3.4 aio_error

aio_error - 获取异步IO操作(异步读、写或同步)的完成状态

#include <aio.h>

int aio_error(const struct aiocb *aiocb);

描述
函数返回异步IO请求的错误状态, aiocb指向AIO控制块, 代表了异步IO请求信息.

返回值
0 异步操作成功, 需要调用aio_return 函数获取操作返回值;
-1 对aio_error调用失败, errno被设置;
EINPROGRESS 异步读、写或同步操作仍在等待;
其他值 相关异步操作失败返回的错误码(errno);

3.5 aio_return

aio_error提到, 返回0时表示异步操作成功, 可以调用aio_return获取操作返回值.
aio_return - 获取异步IO操作返回值

int <aio.h>

int aio_return(const struct aiocb *aiocb);

描述
注意:

  1. 异步操作完成之前, 不要调用aio_return, 其行为是未定义的;
  2. 对每个异步操作调用一次aio_return, 因为一旦调用了, OS就能释放包含了IO操作返回值的记录;

返回值
失败返回-1, errno被设置; 成功时, 返回异步操作结果, 即返回(同步版本)read、write或fsync在被成功调用时可能返回的结果.

3.6 aio_suspend

aio_suspend - 等待异步IO操作完成, 或超时

#include <aio.h>

int aio_suspend(const struct aiocb *const list[], int nent, const strct timespec *timeout);

描述
执行IO操作时, 如果有其他事务处理而不想被IO操作阻塞, 可以使用异步IO. 如果事务执行完毕后, 还有异步操作尚未完成时, 可调用aio_suspend函数阻止进程, 直到操作完成.

参数
list 指向AIO控制块数组的指针
nent 表明数组的元素个数
timeout 超时时间

返回值
3种情况:

  1. 如果被一个信号中断, 返回-1, errno设置为EINTR;
  2. 如果没有任何IO操作完成, 阻塞时间超时, 返回-1, errno设置为EAGAIN;
  3. 如果有任何IO操作完成, 返回0; 如果所有的异步IO操作都已完成, aio_suspend将在不阻塞的情况下直接返回;

3.7 aio_cancel

aio_cancel - 取消未完成的异步IO请求

#include <aio.h>

int aio_cancel(int fd, struct aiocb *aiocb);

描述
如果不想完成还在等待中的异步IO操作时, 可以调用aio_cancel尝试取消. 描述为尝试, 是因为系统无法保证一定能取消正在进行的任何操作.
如果异步IO操作成功取消, 相应AIO控制块调用aio_error将返回错误ECANCELED; 如果操作不能被取消, 那么相应的AIO控制块不会被修改

参数
fd 指定未完成的异步IO操作的文件描述符
aiocb 如果aiocb = NULL, 系统会尝试取消所有该文件上未完成的异步IO操作; 其他情况, 系统将尝试取消aiocb指向的单个AIO控制块描述的单个异步IO操作.

返回值
4个值之一:
AIO_ALLDONE 所有操作在尝试取消前, 已经完成;
AIO_CANCELED 所有要求的操作已被取消;
AIO_NOTCANCELED 至少有一个要求的操作没有被取消;
-1 对aio_cancel调用失败, 设置errno;

3.8 lio_listio

lio_listio - 初始化io请求列表

#include <aio.h>

int lio_listio(int mode, struct aiocb *const aiocb_list[], int nitems, struct sigevent *sevp);

描述
既能以同步方式使用, 也能以异步的方式使用. 函数提交一系列由一个AIO控制块列表描述的IO请求.
每个AIO控制块中, aio_lio_opcode字段指定了该操作是一个读操作(LIO_READ), 写操作(LIO_WRITE), 还是将忽略的空操作(LIO_NOP). 读操作, 会按照对应的AIO控制块被传给aio_read来处理; 写操作, 会被传给aio_write处理.

参数
mode 决定IO释放真的是异步的. 取值说明:

  1. LIO_WAIT 调用块将等到所有操作完成, sevp参数将会被忽略;
  2. LIO_NOWAIT IO请求入队后, 立即返回, 进程在所有IO操作完成后, 按sigev指定的, 被异步通知. 如果不想被通知, sigev可设置为NULL. 被sigev指定的异步通知, 是在每个AIO控制块本身的异步通知之外的.

aiocb_list 指向AIO控制块列表, 指定了要运行的IO操作.

nitems 指定了aiocb_list数组元素格式.

实现限制
实现一般会限制一些参数的实际取值
POSIX.1中异步IO运行时不变量的值

名称描述可接受的最小值
AIO_LISTIO_MAX单个列表IO调用中的最大IO操作数_POSIX_AOI_LISTIO_MAX
AIO_MAX未完成的异步IO操作的最大数目_POSIX_AIO_MAX
AIO_PRIO_DELTA_MAX进程可以减少的异步IO优先级的最大值0

4. AIO的使用例程

以从一个文件读取数据, 然后写到另外一个文件为例.

4.1 同步IO操作

流程
Linux 异步IO(AIO)第1张

4.2 异步IO操作 (AIO)

流程
Linux 异步IO(AIO)第2张

免责声明:文章转载自《Linux 异步IO(AIO)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇循序渐进Linux 2:Linux的常用命令及使用技巧痞子衡嵌入式:ARM Cortex-M内核那些事(5)- 一表搜罗指令集下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux添加用户组和删除用户组

1.添加用户组使用groupadd命令添加用户组:groupadd group_name此操作需由系统管理员进行。2.删除用户组使用groupdel命令删除用户组:groupdel group_name此操作需由系统管理员进行。3.定义组管理员使用gpasswd命令指定组管理员:gpasswd-A user group_name此操作需由系统管理员进行。4...

linux系统通过ssh拉取gitee项目 设置权限

1.创建一个文件夹  空文件  2.git init  3.尝试git remote  add origin +ssh地址  提示没有权限 4.尝试git clone +ssh地址  提示没有权限 5.需要设置公钥 没设置之前  开始设置  中间连点三次空格  多出两个文件id_rsa  id_rsa.pub id_rsa是私钥,id_rs...

嵌入式驱动开发之---Linux ALSA音频驱动(一)

本文的部分内容参考来自DroidPhone的博客(http://blog.csdn.net/droidphone/article/details/6271122),关于ALSA写得很不错的文章,只是少了实例。本文就是结合实例来分析ALSA音频驱动。 开发环境:ubuntu10.04 目标板:linux-2.6.37 (通过命令uname -r 查看lin...

Linux wc文件统计

原文链接 linux下如何统计一个目录下的文件个数以及代码总行数的命令知道指定后缀名的文件总个数命令:find . -name "*.cpp" | wc -l知道一个目录下代码总行数以及单个文件行数:find . -name "*.h" | xargs wc -llinux统计文件夹中文件数目第一种方法:ls -l|grep “^-”|wc -lls -l...

Linux下.NET开发环境构建

.net,C#,Asp.Net VisualStudio跟着微软一步步走来,有成功,有喜悦,有收获,但也有一种莫名的危机感,整理思路,规划下未来的道路:花四层左右的时间继续跟着微软的步伐,在比尔教主的大旗下继续开拓疆土,同时花六成左右的时间将重心转移到Linux,Php ,C/C++,分布式文件系统于存储,高并发、大数据量互联网架构上,看上去很多,当然梦想...

【转】Linux 下从命令行打开pdf文件和html文件的命令

【转】 Linux 下从命令行打开pdf文件和html文件的命令 转自:http://hipercomer.blog.51cto.com/4415661/900926 如果你经常工作在终端下,某个时刻需要查看一些文档的时候(比如pdf或者html文档),是不是经常需要切换到文件系统中打开这些文件。事实上,你只需要敲一个命令就可以打开这个文件了,完全没有必要...