天天看點

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的結構中常用的成員有

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

上述代碼運作結果如下

linux下aio異步讀寫詳解與執行個體

(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] = &wr;

    //使用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