Linux - 异步IO(AIO)
1. POSIX异步IO(AIO)
BSD对不同的设备文件进行异步IO方法不一样, 如终端设备是产生SIGIO信号, 仅支持带外数据的设备才能产生SIGURG信号.
POSIX对不同类型文件进行异步IO提供一套一致的方法, SUSv4中, 这些接口被移到了基本部分中, 所以现在所有的平台都被要求支持这些接口.
1.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属性结构的地址, 且该结构指定了一个另外的线程属性, 否则该函数将在线程分离状态的一个单独的线程中执行.
1.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
1.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库缓存, 将库缓存内容写入内核缓冲区 |
1.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);
1.5 aio_return
aio_error提到, 返回0时表示异步操作成功, 可以调用aio_return获取操作返回值.
aio_return - 获取异步IO操作返回值
int <aio.h>
int aio_return(const struct aiocb *aiocb);
描述
注意:
- 异步操作完成之前, 不要调用aio_return, 其行为是未定义的;
- 对每个异步操作调用一次aio_return, 因为一旦调用了, OS就能释放包含了IO操作返回值的记录;
返回值
失败返回-1, errno被设置; 成功时, 返回异步操作结果, 即返回(同步版本)read、write或fsync在被成功调用时可能返回的结果.
1.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, errno设置为EINTR;
- 如果没有任何IO操作完成, 阻塞时间超时, 返回-1, errno设置为EAGAIN;
- 如果有任何IO操作完成, 返回0; 如果所有的异步IO操作都已完成, aio_suspend将在不阻塞的情况下直接返回;
1.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;
1.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释放真的是异步的. 取值说明:
- LIO_WAIT 调用块将等到所有操作完成, sevp参数将会被忽略;
- 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 |
2. AIO编程模型
1. 准备缓冲区 struct aiocb
2.1 异步读
#include <stdio.h>
#include <unistd.h>
#include <aio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 1024
int main()
{
//1 准备缓冲区
struct aiocb cb = {0};
int fd = open("test.txt",O_RDONLY);
if(-1 == fd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
//2 异步操作
cb.aio_fildes = fd;//文件描述符号
cb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(cb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
cb.aio_nbytes = BUFF_SIZE;//数据大小 单位是字节
cb.aio_offset = 0;//文件中的偏移量
//bzero
int r = aio_read(&cb);
if(-1 == r) printf("异步读文件失败:%m\n"),close(fd),exit(-1);
printf("异步读文件成功!\n");
//3. 检查是否操作完毕
int n = 0;
while(0 != aio_error(&cb)) {//aio_error返回值为0 循环结束
n++;
}
//4. 得到数据
r = aio_return(&cb);
if(r > 0){
printf("拿到数据:r:%d n:%d >> %s\n",
r,n,cb.aio_buf);
}
//5. 收尾
free(cb.aio_buf);
close(fd);
return 0;
}
2.2 异步写
#include <stdio.h>
#include <unistd.h>
#include <aio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 1024
int main(){
//1 准备缓冲区
struct aiocb cb = {0};
int fd = open("test2.txt",O_WRONLY | O_APPEND);
if(-1 == fd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
//2 异步操作
cb.aio_fildes = fd;//文件描述符号
cb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(cb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
strcpy(cb.aio_buf,"提前祝大家国庆节快乐,假期别忘记写代码");
cb.aio_nbytes = strlen("提前祝大家国庆节快乐,假期别忘记写代码");//数据大小 单位是字节
cb.aio_offset = 0;//文件中的偏移量
//bzero
int r = aio_write(&cb);
if(-1 == r) printf("异步写文件失败:%m\n"),close(fd),exit(-1);
printf("异步写文件成功!\n");
//3. 检查是否操作完毕
int n = 0;
while(0 != aio_error(&cb)) {//aio_error返回值为0 循环结束
n++;
}
//4. 得到数据
r = aio_return(&cb);
if(r > 0){
printf("拿到数据:r:%d n:%d >> %s\n",
r,n,cb.aio_buf);
}
//5. 收尾
free(cb.aio_buf);
close(fd);
return 0;
}
2.3 lio_listio示例
#include <stdio.h>
#include <unistd.h>
#include <aio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 1024
#define LIO_LISTIO_NUM 2
int main(){
//准备缓冲区
struct aiocb rcb = {0};
struct aiocb wcb = {0};
//准备结构体指针数组
struct aiocb* aiocb_list[LIO_LISTIO_NUM] = {0};
int rfd = open("test.txt",O_RDONLY);
if(-1 == rfd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
rcb.aio_fildes = rfd;//文件描述符号
rcb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(rcb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
rcb.aio_nbytes = BUFF_SIZE;//数据大小 单位是字节
rcb.aio_offset = 0;//文件中的偏移量
//bzero
aiocb_list[0] = &rcb;//结构体地址放到结构体指针数组内
int r = aio_read(&rcb);
if(-1 == r) printf("异步读文件失败:%m\n"),close(rfd),exit(-1);
printf("异步读文件成功!\n");
int wfd = open("test2.txt",O_WRONLY | O_APPEND);
if(-1 == wfd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
//2 异步操作
wcb.aio_fildes = wfd;//文件描述符号
wcb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(wcb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
strcpy(wcb.aio_buf,"提前祝大家国庆节快乐,假期别忘记写代码");
wcb.aio_nbytes = strlen("提前祝大家国庆节快乐,假期别忘记写代码");//数据大小 单位是字节
wcb.aio_offset = 0;//文件中的偏移量
aiocb_list[1] = &wcb;//结构体地址放到结构体指针数组内
//bzero
r = aio_write(&wcb);
if(-1 == r) printf("异步写文件失败:%m\n"),close(wfd),exit(-1);
printf("异步写文件成功!\n");
//4 用 lio_listio来监视多个io操作
//r = lio_listio(LIO_NOWAIT,aiocb_list,2,NULL);//不阻塞直接返回
r = lio_listio(LIO_WAIT,aiocb_list,2,NULL);//阻塞
printf("lio_listio完毕 r:%d\n",r);
//5 得到数据
r = aio_return(&rcb);
if(r > 0){
printf("拿到数据:r:%d >> %s\n",
r,rcb.aio_buf);
}
r = aio_return(&wcb);
if(r > 0){
printf("拿到数据:r:%d >> %s\n",
r,wcb.aio_buf);
}
free(wcb.aio_buf);
free(rcb.aio_buf);
close(rfd);
close(wfd);
return 0;
}
2.4 aio_suspend示例
#include <stdio.h>
#include <unistd.h>
#include <aio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 1024
#define LIO_LISTIO_NUM 2
int main(){
//准备缓冲区
struct aiocb rcb = {0};
struct aiocb wcb = {0};
//准备结构体指针数组
struct aiocb* aiocb_list[LIO_LISTIO_NUM] = {0};
int rfd,r;
#if 1
rfd = open("test.txt",O_RDONLY);
if(-1 == rfd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
rcb.aio_fildes = rfd;//文件描述符号
rcb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(rcb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
rcb.aio_nbytes = BUFF_SIZE;//数据大小 单位是字节
rcb.aio_offset = 0;//文件中的偏移量
//bzero
aiocb_list[0] = &rcb;//结构体地址放到结构体指针数组内
r = aio_read(&rcb);
if(-1 == r) printf("异步读文件失败:%m\n"),close(rfd),exit(-1);
printf("异步读文件成功!\n");
#endif
int wfd = open("test2.txt",O_WRONLY | O_APPEND);
if(-1 == wfd) printf("打开文件失败:%m\n"),exit(-1);
printf("打开文件成功!\n");
//2 异步操作
wcb.aio_fildes = wfd;//文件描述符号
wcb.aio_buf = (void*)malloc(BUFF_SIZE+1);//开内存
memset(wcb.aio_buf,0,BUFF_SIZE+1);//清空 全部置零
strcpy(wcb.aio_buf,"提前祝大家国庆节快乐,假期别忘记写代码");
wcb.aio_nbytes = strlen("提前祝大家国庆节快乐,假期别忘记写代码");//数据大小 单位是字节
wcb.aio_offset = 0;//文件中的偏移量
aiocb_list[0] = &wcb;//结构体地址放到结构体指针数组内
//bzero
r = aio_write(&wcb);
if(-1 == r) printf("异步写文件失败:%m\n"),close(wfd),exit(-1);
printf("异步写文件成功!\n");
//4 用 aio_suspend 阻塞式等待
r = aio_suspend(aiocb_list,2,NULL);
printf("aio_suspend 阻塞式等待 完毕:r:%d\n",r);
//5 得到数据
r = aio_return(&rcb);
if(r > 0){
printf("拿到数据:r:%d >> %s\n",
r,rcb.aio_buf);
}
r = aio_return(&wcb);
if(r > 0){
printf("拿到数据:r:%d >> %s\n",
r,wcb.aio_buf);
}
free(wcb.aio_buf);
free(rcb.aio_buf);
close(rfd);
close(wfd);
return 0;
}