linux下aio异步读写详解与实例

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的结构中常用的成员有

 
  1. struct aiocb

  2. {

  3. //要异步操作的文件描述符

  4. int aio_fildes;

  5. //用于lio操作时选择操作何种异步I/O类型

  6. int aio_lio_opcode;

  7. //异步读或写的缓冲区的缓冲区

  8. volatile void *aio_buf;

  9. //异步读或写的字节数

  10. size_t aio_nbytes;

  11. //异步通知的结构体

  12. struct sigevent aio_sigevent;

  13. }

4异步I/O操作的具体使用

(1)异步读aio_read

aio_read函数请求对一个文件进行读操作,所请求文件对应的文件描述符可以是文件,套接字,甚至管道其原型如下

int aio_read(struct aiocb *paiocb);
  • 1

该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作
需要注意的是,我们得先对aiocb结构体进行必要的初始化
具体实例如下

aio_read

 
  1. #include<stdio.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<assert.h>

  6. #include<unistd.h>

  7. #include<stdlib.h>

  8. #include<errno.h>

  9. #include<string.h>

  10. #include<sys/types.h>

  11. #include<fcntl.h>

  12. #include<aio.h>

  13.  
  14.  
  15. #define BUFFER_SIZE 1024

  16.  
  17. int MAX_LIST = 2;

  18.  
  19. int main(int argc,char **argv)

  20. {

  21. //aio操作所需结构体

  22. struct aiocb rd;

  23.  
  24. int fd,ret,couter;

  25.  
  26. fd = open("test.txt",O_RDONLY);

  27. if(fd < 0)

  28. {

  29. perror("test.txt");

  30. }

  31.  
  32.  
  33.  
  34. //将rd结构体清空

  35. bzero(&rd,sizeof(rd));

  36.  
  37.  
  38. //为rd.aio_buf分配空间

  39. rd.aio_buf = malloc(BUFFER_SIZE + 1);

  40.  
  41. //填充rd结构体

  42. rd.aio_fildes = fd;

  43. rd.aio_nbytes = BUFFER_SIZE;

  44. rd.aio_offset = 0;

  45.  
  46. //进行异步读操作

  47. ret = aio_read(&rd);

  48. if(ret < 0)

  49. {

  50. perror("aio_read");

  51. exit(1);

  52. }

  53.  
  54. couter = 0;

  55. // 循环等待异步读操作结束

  56. while(aio_error(&rd) == EINPROGRESS)

  57. {

  58. printf("第%d次\n",++couter);

  59. }

  60. //获取异步读返回值

  61. ret = aio_return(&rd);

  62.  
  63. printf("\n\n返回值为:%d",ret);

  64.  
  65.  
  66. return 0;

  67. }

  •  

上述实例中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时不需要填充它的偏移量了
具体实例如下

 
  1. #include<stdio.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<assert.h>

  6. #include<unistd.h>

  7. #include<stdlib.h>

  8. #include<errno.h>

  9. #include<string.h>

  10. #include<sys/types.h>

  11. #include<fcntl.h>

  12. #include<aio.h>

  13.  
  14. #define BUFFER_SIZE 1025

  15.  
  16. int main(int argc,char **argv)

  17. {

  18. //定义aio控制块结构体

  19. struct aiocb wr;

  20.  
  21. int ret,fd;

  22.  
  23. char str[20] = {"hello,world"};

  24.  
  25. //置零wr结构体

  26. bzero(&wr,sizeof(wr));

  27.  
  28. fd = open("test.txt",O_WRONLY | O_APPEND);

  29. if(fd < 0)

  30. {

  31. perror("test.txt");

  32. }

  33.  
  34. //为aio.buf申请空间

  35. wr.aio_buf = (char *)malloc(BUFFER_SIZE);

  36. if(wr.aio_buf == NULL)

  37. {

  38. perror("buf");

  39. }

  40.  
  41. wr.aio_buf = str;

  42.  
  43. //填充aiocb结构

  44. wr.aio_fildes = fd;

  45. wr.aio_nbytes = 1024;

  46.  
  47. //异步写操作

  48. ret = aio_write(&wr);

  49. if(ret < 0)

  50. {

  51. perror("aio_write");

  52. }

  53.  
  54. //等待异步写完成

  55. while(aio_error(&wr) == EINPROGRESS)

  56. {

  57. printf("hello,world\n");

  58. }

  59.  
  60. //获得异步写的返回值

  61. ret = aio_return(&wr);

  62. printf("\n\n\n返回值为:%d\n",ret);

  63.  
  64. return 0;

  65. }

具体运行结果请读者自己去试试

(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:

 
  1. #include<stdio.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<assert.h>

  6. #include<unistd.h>

  7. #include<stdlib.h>

  8. #include<errno.h>

  9. #include<string.h>

  10. #include<sys/types.h>

  11. #include<fcntl.h>

  12. #include<aio.h>

  13.  
  14.  
  15. #define BUFFER_SIZE 1024

  16.  
  17. int MAX_LIST = 2;

  18.  
  19. int main(int argc,char **argv)

  20. {

  21. //aio操作所需结构体

  22. struct aiocb rd;

  23.  
  24. int fd,ret,couter;

  25.  
  26. //cblist链表

  27. struct aiocb *aiocb_list[2];

  28.  
  29.  
  30.  
  31. fd = open("test.txt",O_RDONLY);

  32. if(fd < 0)

  33. {

  34. perror("test.txt");

  35. }

  36.  
  37.  
  38.  
  39. //将rd结构体清空

  40. bzero(&rd,sizeof(rd));

  41.  
  42.  
  43. //为rd.aio_buf分配空间

  44. rd.aio_buf = malloc(BUFFER_SIZE + 1);

  45.  
  46. //填充rd结构体

  47. rd.aio_fildes = fd;

  48. rd.aio_nbytes = BUFFER_SIZE;

  49. rd.aio_offset = 0;

  50.  
  51. //将读fd的事件注册

  52. aiocb_list[0] = &rd;

  53.  
  54. //进行异步读操作

  55. ret = aio_read(&rd);

  56. if(ret < 0)

  57. {

  58. perror("aio_read");

  59. exit(1);

  60. }

  61.  
  62. couter = 0;

  63. // 循环等待异步读操作结束

  64. while(aio_error(&rd) == EINPROGRESS)

  65. {

  66. printf("第%d次\n",++couter);

  67. }

  68.  
  69. printf("我要开始等待异步读事件完成\n");

  70. //阻塞等待异步读事件完成

  71. ret = aio_suspend(aiocb_list,MAX_LIST,NULL);

  72.  
  73. //获取异步读返回值

  74. ret = aio_return(&rd);

  75.  
  76. printf("\n\n返回值为:%d\n",ret);

  77.  
  78.  
  79. return 0;

  80. }

(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

 
  1. #include<stdio.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<assert.h>

  6. #include<unistd.h>

  7. #include<stdlib.h>

  8. #include<errno.h>

  9. #include<string.h>

  10. #include<sys/types.h>

  11. #include<fcntl.h>

  12. #include<aio.h>

  13.  
  14. #define BUFFER_SIZE 1025

  15.  
  16. int MAX_LIST = 2;

  17.  
  18.  
  19. int main(int argc,char **argv)

  20. {

  21. struct aiocb *listio[2];

  22. struct aiocb rd,wr;

  23. int fd,ret;

  24.  
  25. //异步读事件

  26. fd = open("test1.txt",O_RDONLY);

  27. if(fd < 0)

  28. {

  29. perror("test1.txt");

  30. }

  31.  
  32. bzero(&rd,sizeof(rd));

  33.  
  34. rd.aio_buf = (char *)malloc(BUFFER_SIZE);

  35. if(rd.aio_buf == NULL)

  36. {

  37. perror("aio_buf");

  38. }

  39.  
  40. rd.aio_fildes = fd;

  41. rd.aio_nbytes = 1024;

  42. rd.aio_offset = 0;

  43. rd.aio_lio_opcode = LIO_READ; ///lio操作类型为异步读

  44.  
  45. //将异步读事件添加到list中

  46. listio[0] = &rd;

  47.  
  48.  
  49. //异步些事件

  50. fd = open("test2.txt",O_WRONLY | O_APPEND);

  51. if(fd < 0)

  52. {

  53. perror("test2.txt");

  54. }

  55.  
  56. bzero(&wr,sizeof(wr));

  57.  
  58. wr.aio_buf = (char *)malloc(BUFFER_SIZE);

  59. if(wr.aio_buf == NULL)

  60. {

  61. perror("aio_buf");

  62. }

  63.  
  64. wr.aio_fildes = fd;

  65. wr.aio_nbytes = 1024;

  66.  
  67. wr.aio_lio_opcode = LIO_WRITE; ///lio操作类型为异步写

  68.  
  69. //将异步写事件添加到list中

  70. listio[1] = &wr;

  71.  
  72. //使用lio_listio发起一系列请求

  73. ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL);

  74.  
  75. //当异步读写都完成时获取他们的返回值

  76.  
  77. ret = aio_return(&rd);

  78. printf("\n读返回值:%d",ret);

  79.  
  80. ret = aio_return(&wr);

  81. printf("\n写返回值:%d",ret);

  82.  
  83.  
  84.  
  85. return 0;

  86. }

  •  

5.I/O完成时进行异步通知

当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用回调函数来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知,关于信号通知有兴趣的同学自己去学习吧

使用回调进行异步通知

该种通知方式使用一个系统回调函数来通知应用程序,要想完成此功能,我们必须在aiocb中设置我们想要进行异步回调的aiocb指针,以用来回调之后表示其自身

实例如下
aio线程回调通知

 
  1. #include<stdio.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<assert.h>

  6. #include<unistd.h>

  7. #include<stdlib.h>

  8. #include<errno.h>

  9. #include<string.h>

  10. #include<sys/types.h>

  11. #include<fcntl.h>

  12. #include<aio.h>

  13. #include<unistd.h>

  14.  
  15. #define BUFFER_SIZE 1025

  16.  
  17.  
  18. void aio_completion_handler(sigval_t sigval)

  19. {

  20. //用来获取读aiocb结构的指针

  21. struct aiocb *prd;

  22. int ret;

  23.  
  24. prd = (struct aiocb *)sigval.sival_ptr;

  25.  
  26. printf("hello\n");

  27.  
  28. //判断请求是否成功

  29. if(aio_error(prd) == 0)

  30. {

  31. //获取返回值

  32. ret = aio_return(prd);

  33. printf("读返回值为:%d\n",ret);

  34. }

  35. }

  36.  
  37. int main(int argc,char **argv)

  38. {

  39. int fd,ret;

  40. struct aiocb rd;

  41.  
  42. fd = open("test.txt",O_RDONLY);

  43. if(fd < 0)

  44. {

  45. perror("test.txt");

  46. }

  47.  
  48.  
  49.  
  50. //填充aiocb的基本内容

  51. bzero(&rd,sizeof(rd));

  52.  
  53. rd.aio_fildes = fd;

  54. rd.aio_buf = (char *)malloc(sizeof(BUFFER_SIZE + 1));

  55. rd.aio_nbytes = BUFFER_SIZE;

  56. rd.aio_offset = 0;

  57.  
  58. //填充aiocb中有关回调通知的结构体sigevent

  59. rd.aio_sigevent.sigev_notify = SIGEV_THREAD;//使用线程回调通知

  60. rd.aio_sigevent.sigev_notify_function = aio_completion_handler;//设置回调函数

  61. rd.aio_sigevent.sigev_notify_attributes = NULL;//使用默认属性

  62. rd.aio_sigevent.sigev_value.sival_ptr = &rd;//在aiocb控制块中加入自己的引用

  63.  
  64. //异步读取文件

  65. ret = aio_read(&rd);

  66. if(ret < 0)

  67. {

  68. perror("aio_read");

  69. }

  70.  
  71. printf("异步读以开始\n");

  72. sleep(1);

  73. printf("异步读结束\n");

  74.  
  75.  
  76.  
  77. return 0;

  78. }

  •  

线程会掉是通过使用aiocb结构体中的aio_sigevent结构体来控制的,
其定义如下

 
  1. struct sigevent

  2. {

  3. sigval_t sigev_value;

  4. int sigev_signo;

  5. int sigev_notify;

  6. union {

  7. int _pad[SIGEV_PAD_SIZE];

  8. int _tid;

  9.  
  10. struct {

  11. void (*_function)(sigval_t);

  12. void *_attribute; /* really pthread_attr_t */

  13. } _sigev_thread;

  14. } _sigev_un;

  15. }

  16.  
  17. #define sigev_notify_function _sigev_un._sigev_thread._function

  18. #define sigev_notify_attributes _sigev_un._sigev_thread._attribute

  19. #define sigev_notify_thread_id _sigev_un._tid

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 中,完成异步读写的一种方法是使用 aio_read 和 aio_write 函数。这些函数使用 Linux 内核提供的异步输入/输出(AIO)接口,可以在进行 I/O 操作时不阻塞进程,从而提高系统的性能和吞吐量。 异步读写文件的一般步骤如下: 1. 打开文件,并初始化异步读取或写入结构体。 2. 调用 aio_read 或 aio_write 函数进行异步读写操作。 3. 使用 aio_error 和 aio_return 函数获取异步读写的结果。 4. 关闭文件。 以下是一个简单的示例,演示如何在 Linux 中完成异步读写: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <aio.h> #define BUFSIZE 1024 int main(int argc, char *argv[]) { int fd; struct aiocb aio; char buffer[BUFSIZE]; int ret; if (argc < 2) { printf("Usage: %s <filename>\n", argv[0]); exit(EXIT_FAILURE); } // 打开文件 if ((fd = open(argv[1], O_RDONLY)) == -1) { perror("open"); exit(EXIT_FAILURE); } // 初始化异步读取结构体 memset(&aio, 0, sizeof(struct aiocb)); aio.aio_fildes = fd; aio.aio_buf = buffer; aio.aio_nbytes = BUFSIZE; aio.aio_offset = 0; // 异步读取文件 if (aio_read(&aio) == -1) { perror("aio_read"); exit(EXIT_FAILURE); } // 等待异步读取完成 while ((ret = aio_error(&aio)) == EINPROGRESS); if (ret != 0) { perror("aio_error"); exit(EXIT_FAILURE); } // 获取异步读取结果 if ((ret = aio_return(&aio)) == -1) { perror("aio_return"); exit(EXIT_FAILURE); } // 关闭文件 close(fd); printf("Read %d bytes from file %s.\n", ret, argv[1]); exit(EXIT_SUCCESS); } ``` 以上代码实现了从一个文件中异步读取数据,并将读取的数据存储到 buffer 中。在代码中,首先打开文件,并使用 aio_read 函数异步读取文件,并将读取的数据存储到 buffer 中。最后,使用 aio_error 和 aio_return 函数获取异步读取的结果,并关闭文件。 需要注意的是,以上代码中只是一个简单的示例,实际使用时还需要对错误进行处理,例如当 aio_read 或 aio_write 函数返回 EINPROGRESS 时,需要等待异步读取或写入完成。此外,还需要实现循环读取和写入文件,直到文件全部读取或写入完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值