linux下aio异步读写详解与实例
1.为什么会有异步I/Oaio异步读写是在linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,那么我们在同一个程序中(不用多线程或多进程)就不能同时操作俩个以上的文件I/O,每次只能对一个文件进行I/O操作,很明显这样效率很低下(因为CPU速度远大于I/O操作的速度,所以
1.为什么会有异步I/O
aio异步读写是在linux内核2.6之后才正式纳入其标准。之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非阻塞式来操作I/O的话,那么我们在同一个程序中(不用多线程或多进程)就不能同时操作俩个以上的文件I/O,每次只能对一个文件进行I/O操作,很明显这样效率很低下(因为CPU速度远大于I/O操作的速度,所以当执行I/O时,CPU其实还可以做更多的事)。因此就诞生了相对高效的异步I/O
2.异步I/O的基本概念
所谓异步I/O即我们在调用I/O操作时(读或写)我们的程序不会阻塞在当前位置,而是在继续往下执行。例如当我们调用异步读API aio_read()时,程序执行此代码之后会接着运行此函数下面的代码,并且与此同时程序也在进行刚才所要读的文件的读取工作,但是具体什么时候读完是不确定的
3.异步aio的基本API
| API函数 | 说明 |
|---|---|
| aio_read | 异步读操作 |
| aio_write | 异步写操作 |
| aio_error | 检查异步请求的状态 |
| aio_return | 获得异步请求完成时的返回值 |
| aio_suspend | 挂起调用进程,直到一个或多个异步请求已完成 |
| aio_cancel | 取消异步请求 |
| lio_list | 发起一系列异步I/O请求 |
上述的每个API都要用aiocb结构体赖进行操作
aiocb的结构中常用的成员有
-
struct aiocb -
{ -
//要异步操作的文件描述符 -
int aio_fildes; -
//用于lio操作时选择操作何种异步I/O类型 -
int aio_lio_opcode; -
//异步读或写的缓冲区的缓冲区 -
volatile void *aio_buf; -
//异步读或写的字节数 -
size_t aio_nbytes; -
//异步通知的结构体 -
struct sigevent aio_sigevent; -
}
4异步I/O操作的具体使用
(1)异步读aio_read
aio_read函数请求对一个文件进行读操作,所请求文件对应的文件描述符可以是文件,套接字,甚至管道其原型如下
int aio_read(struct aiocb *paiocb);
- 1
该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作
需要注意的是,我们得先对aiocb结构体进行必要的初始化
具体实例如下
aio_read
-
#include<stdio.h> -
#include<sys/socket.h> -
#include<netinet/in.h> -
#include<arpa/inet.h> -
#include<assert.h> -
#include<unistd.h> -
#include<stdlib.h> -
#include<errno.h> -
#include<string.h> -
#include<sys/types.h> -
#include<fcntl.h> -
#include<aio.h> -
#define BUFFER_SIZE 1024 -
int MAX_LIST = 2; -
int main(int argc,char **argv) -
{ -
//aio操作所需结构体 -
struct aiocb rd; -
int fd,ret,couter; -
fd = open("test.txt",O_RDONLY); -
if(fd < 0) -
{ -
perror("test.txt"); -
} -
//将rd结构体清空 -
bzero(&rd,sizeof(rd)); -
//为rd.aio_buf分配空间 -
rd.aio_buf = malloc(BUFFER_SIZE + 1); -
//填充rd结构体 -
rd.aio_fildes = fd; -
rd.aio_nbytes = BUFFER_SIZE; -
rd.aio_offset = 0; -
//进行异步读操作 -
ret = aio_read(&rd); -
if(ret < 0) -
{ -
perror("aio_read"); -
exit(1); -
} -
couter = 0; -
// 循环等待异步读操作结束 -
while(aio_error(&rd) == EINPROGRESS) -
{ -
printf("第%d次\n",++couter); -
} -
//获取异步读返回值 -
ret = aio_return(&rd); -
printf("\n\n返回值为:%d",ret); -
return 0; -
}
上述实例中aiocb结构体用来表示某一次特定的读写操作,在异步读操作时我们只需要注意4点内容
1.确定所要读的文件描述符,并写入aiocb结构体中(下面几条一样不再赘余)
2.确定读所需的缓冲区
3.确定读的字节数
4.确定文件的偏移量
总结以上注意事项:基本上和我们的read函数所需的条件相似,唯一的区别就是多一个文件偏移量
值得注意的是上述代码中aio_error是用来获取其参数指定的读写操作的状态的
其原型如下
int aio_error(struct aiocb *aiopcb);
当其状态处于EINPROGRESS则I/O还没完成,当处于ECANCELLED则操作已被取消,发生错误返回-1
而aio_return则是用来返回其参数指定I/O操作的返回值
其原型如下
ssize_t aio_return(struct aiocb *paiocb);
如果操作没完成调用此函数,则会产生错误
特别提醒在编译上述程序时必须在编译时再加一个-lrt
上述代码运行结果如下
(2)异步写aio_write
aio_writr用来请求异步写操作
其函数原型如下
int aio_write(struct aiocb *paiocb);
aio_write和aio_read函数类似,当该函数返回成功时,说明该写请求以进行排队(成功0,失败-1)
其和aio_read调用时的区别是就是我们如果在打开文件是,flags设置了O_APPEND则我们在填充aiocb时不需要填充它的偏移量了
具体实例如下
-
#include<stdio.h> -
#include<sys/socket.h> -
#include<netinet/in.h> -
#include<arpa/inet.h> -
#include<assert.h> -
#include<unistd.h> -
#include<stdlib.h> -
#include<errno.h> -
#include<string.h> -
#include<sys/types.h> -
#include<fcntl.h> -
#include<aio.h> -
#define BUFFER_SIZE 1025 -
int main(int argc,char **argv) -
{ -
//定义aio控制块结构体 -
struct aiocb wr; -
int ret,fd; -
char str[20] = {"hello,world"}; -
//置零wr结构体 -
bzero(&wr,sizeof(wr)); -
fd = open("test.txt",O_WRONLY | O_APPEND); -
if(fd < 0) -
{ -
perror("test.txt"); -
} -
//为aio.buf申请空间 -
wr.aio_buf = (char *)malloc(BUFFER_SIZE); -
if(wr.aio_buf == NULL) -
{ -
perror("buf"); -
} -
wr.aio_buf = str; -
//填充aiocb结构 -
wr.aio_fildes = fd; -
wr.aio_nbytes = 1024; -
//异步写操作 -
ret = aio_write(&wr); -
if(ret < 0) -
{ -
perror("aio_write"); -
} -
//等待异步写完成 -
while(aio_error(&wr) == EINPROGRESS) -
{ -
printf("hello,world\n"); -
} -
//获得异步写的返回值 -
ret = aio_return(&wr); -
printf("\n\n\n返回值为:%d\n",ret); -
return 0; -
}
具体运行结果请读者自己去试试
(3)使用aio_suspend阻塞异步I/O
aio_suspend函数可以时当前进程挂起,知道有向其注册的异步事件完成为止
该函数原型如下
int aio_suspend(const struct aiocb *const cblist[],int n,const struct timespec *timeout);
第一个参数是个保存了aiocb块地址的数组,我们可以向其内添加想要等待阻塞的异步事件,第二个参数为向cblist注册的aiocb个数,第三个参数为等待阻塞的超时事件,NULL为无限等待
具体使用如下
suspend:
-
#include<stdio.h> -
#include<sys/socket.h> -
#include<netinet/in.h> -
#include<arpa/inet.h> -
#include<assert.h> -
#include<unistd.h> -
#include<stdlib.h> -
#include<errno.h> -
#include<string.h> -
#include<sys/types.h> -
#include<fcntl.h> -
#include<aio.h> -
#define BUFFER_SIZE 1024 -
int MAX_LIST = 2; -
int main(int argc,char **argv) -
{ -
//aio操作所需结构体 -
struct aiocb rd; -
int fd,ret,couter; -
//cblist链表 -
struct aiocb *aiocb_list[2]; -
fd = open("test.txt",O_RDONLY); -
if(fd < 0) -
{ -
perror("test.txt"); -
} -
//将rd结构体清空 -
bzero(&rd,sizeof(rd)); -
//为rd.aio_buf分配空间 -
rd.aio_buf = malloc(BUFFER_SIZE + 1); -
//填充rd结构体 -
rd.aio_fildes = fd; -
rd.aio_nbytes = BUFFER_SIZE; -
rd.aio_offset = 0; -
//将读fd的事件注册 -
aiocb_list[0] = &rd; -
//进行异步读操作 -
ret = aio_read(&rd); -
if(ret < 0) -
{ -
perror("aio_read"); -
exit(1); -
} -
couter = 0; -
// 循环等待异步读操作结束 -
while(aio_error(&rd) == EINPROGRESS) -
{ -
printf("第%d次\n",++couter); -
} -
printf("我要开始等待异步读事件完成\n"); -
//阻塞等待异步读事件完成 -
ret = aio_suspend(aiocb_list,MAX_LIST,NULL); -
//获取异步读返回值 -
ret = aio_return(&rd); -
printf("\n\n返回值为:%d\n",ret); -
return 0; -
}
(4)lio_listio函数
aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio
这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作
其函数原型如下
int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);
- 1
第一个参数mode可以有俩个实参,LIO_WAIT和LIO_NOWAIT,前一个会阻塞该调用直到所有I/O都完成为止,后一个则会挂入队列就返回
具体实例如下
lio_listio
-
#include<stdio.h> -
#include<sys/socket.h> -
#include<netinet/in.h> -
#include<arpa/inet.h> -
#include<assert.h> -
#include<unistd.h> -
#include<stdlib.h> -
#include<errno.h> -
#include<string.h> -
#include<sys/types.h> -
#include<fcntl.h> -
#include<aio.h> -
#define BUFFER_SIZE 1025 -
int MAX_LIST = 2; -
int main(int argc,char **argv) -
{ -
struct aiocb *listio[2]; -
struct aiocb rd,wr; -
int fd,ret; -
//异步读事件 -
fd = open("test1.txt",O_RDONLY); -
if(fd < 0) -
{ -
perror("test1.txt"); -
} -
bzero(&rd,sizeof(rd)); -
rd.aio_buf = (char *)malloc(BUFFER_SIZE); -
if(rd.aio_buf == NULL) -
{ -
perror("aio_buf"); -
} -
rd.aio_fildes = fd; -
rd.aio_nbytes = 1024; -
rd.aio_offset = 0; -
rd.aio_lio_opcode = LIO_READ; ///lio操作类型为异步读 -
//将异步读事件添加到list中 -
listio[0] = &rd; -
//异步些事件 -
fd = open("test2.txt",O_WRONLY | O_APPEND); -
if(fd < 0) -
{ -
perror("test2.txt"); -
} -
bzero(&wr,sizeof(wr)); -
wr.aio_buf = (char *)malloc(BUFFER_SIZE); -
if(wr.aio_buf == NULL) -
{ -
perror("aio_buf"); -
} -
wr.aio_fildes = fd; -
wr.aio_nbytes = 1024; -
wr.aio_lio_opcode = LIO_WRITE; ///lio操作类型为异步写 -
//将异步写事件添加到list中 -
listio[1] = ≀ -
//使用lio_listio发起一系列请求 -
ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL); -
//当异步读写都完成时获取他们的返回值 -
ret = aio_return(&rd); -
printf("\n读返回值:%d",ret); -
ret = aio_return(&wr); -
printf("\n写返回值:%d",ret); -
return 0; -
}
5.I/O完成时进行异步通知
当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用回调函数来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知,关于信号通知有兴趣的同学自己去学习吧
使用回调进行异步通知
该种通知方式使用一个系统回调函数来通知应用程序,要想完成此功能,我们必须在aiocb中设置我们想要进行异步回调的aiocb指针,以用来回调之后表示其自身
实例如下
aio线程回调通知
-
#include<stdio.h> -
#include<sys/socket.h> -
#include<netinet/in.h> -
#include<arpa/inet.h> -
#include<assert.h> -
#include<unistd.h> -
#include<stdlib.h> -
#include<errno.h> -
#include<string.h> -
#include<sys/types.h> -
#include<fcntl.h> -
#include<aio.h> -
#include<unistd.h> -
#define BUFFER_SIZE 1025 -
void aio_completion_handler(sigval_t sigval) -
{ -
//用来获取读aiocb结构的指针 -
struct aiocb *prd; -
int ret; -
prd = (struct aiocb *)sigval.sival_ptr; -
printf("hello\n"); -
//判断请求是否成功 -
if(aio_error(prd) == 0) -
{ -
//获取返回值 -
ret = aio_return(prd); -
printf("读返回值为:%d\n",ret); -
} -
} -
int main(int argc,char **argv) -
{ -
int fd,ret; -
struct aiocb rd; -
fd = open("test.txt",O_RDONLY); -
if(fd < 0) -
{ -
perror("test.txt"); -
} -
//填充aiocb的基本内容 -
bzero(&rd,sizeof(rd)); -
rd.aio_fildes = fd; -
rd.aio_buf = (char *)malloc(sizeof(BUFFER_SIZE + 1)); -
rd.aio_nbytes = BUFFER_SIZE; -
rd.aio_offset = 0; -
//填充aiocb中有关回调通知的结构体sigevent -
rd.aio_sigevent.sigev_notify = SIGEV_THREAD;//使用线程回调通知 -
rd.aio_sigevent.sigev_notify_function = aio_completion_handler;//设置回调函数 -
rd.aio_sigevent.sigev_notify_attributes = NULL;//使用默认属性 -
rd.aio_sigevent.sigev_value.sival_ptr = &rd;//在aiocb控制块中加入自己的引用 -
//异步读取文件 -
ret = aio_read(&rd); -
if(ret < 0) -
{ -
perror("aio_read"); -
} -
printf("异步读以开始\n"); -
sleep(1); -
printf("异步读结束\n"); -
return 0; -
}
线程会掉是通过使用aiocb结构体中的aio_sigevent结构体来控制的,
其定义如下
-
struct sigevent -
{ -
sigval_t sigev_value; -
int sigev_signo; -
int sigev_notify; -
union { -
int _pad[SIGEV_PAD_SIZE]; -
int _tid; -
struct { -
void (*_function)(sigval_t); -
void *_attribute; /* really pthread_attr_t */ -
} _sigev_thread; -
} _sigev_un; -
} -
#define sigev_notify_function _sigev_un._sigev_thread._function -
#define sigev_notify_attributes _sigev_un._sigev_thread._attribute -
#define sigev_notify_thread_id _sigev_un._tid
更多推荐


所有评论(0)