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);
- 1
當其狀态處于EINPROGRESS則I/O還沒完成,當處于ECANCELLED則操作已被取消,發生錯誤傳回-1
而aio_return則是用來傳回其參數指定I/O操作的傳回值
其原型如下
ssize_t aio_return(struct aiocb *paiocb);
- 1
如果操作沒完成調用此函數,則會産生錯誤
特别提醒在編譯上述程式時必須在編譯時再加一個-lrt
上述代碼運作結果如下

(2)異步寫aio_write
aio_writr用來請求異步寫操作
其函數原型如下
int aio_write(struct aiocb *paiocb);
- 1
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);
- 1
第一個參數是個儲存了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指針,以用來回調之後表示其自身
#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;
}
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