0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

linux的異步IO分析

科技綠洲 ? 來(lái)源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-09 16:50 ? 次閱讀

一、簡(jiǎn)介

1.1 POSIX AIO

POSIX AIO是一個(gè)用戶級(jí)實(shí)現(xiàn),它在多個(gè)線程中執(zhí)行正常的阻塞I/O,因此給出了I/O異步的錯(cuò)覺(jué).這樣做的主要原因是:

  • 它適用于任何文件系統(tǒng)
  • 它(基本上)在任何操作系統(tǒng)上工作(請(qǐng)記住,gnu的libc是可移植的)
  • 它適用于啟用了緩沖的文件(即沒(méi)有設(shè)置O_DIRECT標(biāo)志)

主要缺點(diǎn)是你的隊(duì)列深度(即你在實(shí)踐中可以擁有的未完成操作的數(shù)量)受到你選擇的線程數(shù)量的限制,這也意味著一個(gè)磁盤上的慢速操作可能會(huì)阻止一個(gè)操作進(jìn)入不同的磁盤.它還會(huì)影響內(nèi)核和磁盤調(diào)度程序看到的I/O(或多少)。

1.2 libaio

內(nèi)核AIO(即io_submit()et.al.)是異步I/O操作的內(nèi)核支持,其中io請(qǐng)求實(shí)際上在內(nèi)核中排隊(duì),按照您擁有的任何磁盤調(diào)度程序排序,可能是其中一些被轉(zhuǎn)發(fā)(我們希望將實(shí)際的磁盤作為異步操作(使用TCQ或NCQ)。這種方法的主要限制是,并非所有文件系統(tǒng)都能很好地工作,或者根本不能使用異步I/O(并且可能會(huì)回到阻塞語(yǔ)義),因此必須使用O_DIRECT打開文件,而O_DIRECT還帶有許多其他限制。。I/O請(qǐng)求。如果您無(wú)法使用O_DIRECT打開文件,它可能仍然"正常",就像您獲得正確的數(shù)據(jù)一樣,但它可能不是異步完成,而是回到阻止語(yǔ)義。

還要記住,在某些情況下,io_submit()實(shí)際上可以阻塞磁盤。

沒(méi)有aio_*系統(tǒng)調(diào)用(http://linux.die.net/man/2/syscalls)。您在vfs中看到的aio_函數(shù)可能是內(nèi)核aio的一部分。用戶級(jí)aio_*函數(shù)不會(huì)將1:1映射到系統(tǒng)調(diào)用。

二、POSIX AIO(用戶層級(jí))

2.1 異步IO基本API

圖片

2.2 重要結(jié)構(gòu)體

上述API調(diào)用都會(huì)用到 struct aiocb 結(jié)構(gòu)體:

1 struct aiocb {
2 int aio_fildes; //文件描述符
3 off_t aio_offset; //文件偏移量
4 volatile void *aio_buf; //緩沖區(qū)
5 size_t aio_nbytes; //數(shù)據(jù)長(zhǎng)度
6 int aio_reqprio; //請(qǐng)求優(yōu)先級(jí)
7 struct sigevent aio_sigevent; //通知方式
8 int aio_lio_opcode; //要執(zhí)行的操作
9 };

2.3 使用時(shí)注意事項(xiàng)

編譯時(shí)加參數(shù) -lrt

2.4 aio_error

2.4.1 功能

檢查異步請(qǐng)求狀態(tài)

2.4.2 函數(shù)原型

int aio_error(const struct aiocb *aiocbp);

2.4.3 返回值

圖片

2.5 aio_read

2.5.1 功能

異步讀操作,aio_read函數(shù)請(qǐng)求對(duì)一個(gè)文件進(jìn)行讀操作,所請(qǐng)求文件對(duì)應(yīng)的文件描述符可以是文件,套接字,甚至管道。

2.5.2 函數(shù)原型

int aio_error(const struct aiocb *aiocbp);

2.5.3 返回值

該函數(shù)請(qǐng)求對(duì)文件進(jìn)行異步讀操作,若請(qǐng)求失敗返回-1,成功則返回0,并將該請(qǐng)求進(jìn)行排隊(duì),然后就開始對(duì)文件的異步讀操作。需要注意的是,我們得先對(duì)aiocb結(jié)構(gòu)體進(jìn)行必要的初始化。

2.5.4 使用示例

2.5.4.1 代碼

特別提醒在編譯上述程序時(shí)必須在編譯時(shí)再加一個(gè)-lrt,如gcc test.c -o test -lrt。

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13
14
15 #define BUFFER_SIZE 1024
16
17 int MAX_LIST = 2;
18
19 int main(int argc,char **argv)
20 {
21 //aio操作所需結(jié)構(gòu)體
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結(jié)構(gòu)體清空
35 bzero(&rd,sizeof(rd));
36
37
38 //為rd.aio_buf分配空間
39 rd.aio_buf = malloc(BUFFER_SIZE + 1);
40
41 //填充rd結(jié)構(gòu)體
42 rd.aio_fildes = fd;
43 rd.aio_nbytes = BUFFER_SIZE;
44 rd.aio_offset = 0;
45
46 //進(jìn)行異步讀操作
47 ret = aio_read(&rd);
48 if(ret < 0)
49 {
50 perror("aio_read");
51 exit(1);
52 }
53
54 couter = 0;
55 // 循環(huán)等待異步讀操作結(jié)束
56 while(aio_error(&rd) == EINPROGRESS)
57 {
58 printf("第%d次n",++couter);
59 }
60 //獲取異步讀返回值
61 ret = aio_return(&rd);
62
63 printf("nn返回值為:%d",ret);
64
65
66 return 0;
67 }

2.5.4.2 代碼分析

上述實(shí)例中aiocb結(jié)構(gòu)體用來(lái)表示某一次特定的讀寫操作,在異步讀操作時(shí)我們只需要注意4點(diǎn)內(nèi)容

  • 1.確定所要讀的文件描述符,并寫入aiocb結(jié)構(gòu)體中(下面幾條一樣不再贅余)
  • 2.確定讀所需的緩沖區(qū)
  • 3.確定讀的字節(jié)數(shù)
  • 4.確定文件的偏移量

總結(jié)以上注意事項(xiàng):基本上和我們的read函數(shù)所需的條件相似,唯一的區(qū)別就是多一個(gè)文件偏移量。

2.5.4.3 執(zhí)行結(jié)果分析

運(yùn)行結(jié)果如下:

圖片

從上圖看出,循環(huán)檢查了3次異步讀寫的狀態(tài)(不同機(jī)器的檢查次數(shù)可能不一樣,和具體的計(jì)算機(jī)性能有關(guān)系),指定的256字節(jié)才讀取完畢,最后返回讀取的字節(jié)數(shù)256。

如果注釋掉異步讀的狀態(tài)檢查:

1 ...
2
3 //查看異步讀取的狀態(tài),直到讀取請(qǐng)求完成
4 /* for(i = 1;aio_error(&cbp) == EINPROGRESS;i++)
5 {
6 printf("No.%3dn",i);
7 }
8 ret = aio_return(&cbp);
9 printf("return %dn",ret);
10 */
11 ...

此時(shí)的運(yùn)行結(jié)果:

圖片

發(fā)現(xiàn)什么都沒(méi)輸出,這是因?yàn)槌绦蚪Y(jié)束的時(shí)候,異步讀請(qǐng)求還沒(méi)完成,所以buf緩沖區(qū)還沒(méi)有讀進(jìn)去數(shù)據(jù)。

如果將上面代碼中的 sleep 的注釋去掉,讓異步請(qǐng)求發(fā)起后,程序等待1秒后再輸出,就會(huì)發(fā)現(xiàn)成功讀取到了數(shù)據(jù)。

用GDB單步跟蹤上面程序,當(dāng)發(fā)起異步讀請(qǐng)求時(shí):

圖片

看到發(fā)起一個(gè)異步請(qǐng)求時(shí),Linux實(shí)際上是創(chuàng)建了一個(gè)線程去處理,當(dāng)請(qǐng)求完成后結(jié)束線程。

2.6 aio_write

2.6.1 功能

aio_writr用來(lái)請(qǐng)求異步寫操作

2.6.2 函數(shù)原型

int aio_write(struct aiocb *paiocb);

2.6.3 返回值

aio_write和aio_read函數(shù)類似,當(dāng)該函數(shù)返回成功時(shí),說(shuō)明該寫請(qǐng)求以進(jìn)行排隊(duì)(成功0,失敗-1)。

2.6.4 使用示例

2.6.4.1 代碼

特別提醒在編譯上述程序時(shí)必須在編譯時(shí)再加一個(gè)-lrt,如gcc test.c -o test -lrt。

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13
14 #define BUFFER_SIZE 1025
15
16 int main(int argc,char **argv)
17 {
18 //定義aio控制塊結(jié)構(gòu)體
19 struct aiocb wr;
20
21 int ret,fd;
22
23 char str[20] = {"hello,world"};
24
25 //置零wr結(jié)構(gòu)體
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申請(qǐng)空間
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結(jié)構(gòu)
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,worldn");
58 }
59
60 //獲得異步寫的返回值
61 ret = aio_return(&wr);
62 printf("nnn返回值為:%dn",ret);
63
64 return 0;
65 }

2.6.4.2 代碼分析

上面test.txt文件是以追加的方式打開的,所以不需要設(shè)置aio_offset 文件偏移量,不管文件偏移的值是多少都會(huì)在文件末尾寫入??梢匀サ鬙_APPEND, 不以追加方式打開文件,這樣就可以設(shè)置 aio_offset ,指定從何處位置開始寫入文件。

2.6 aio_suspend

2.6.1 功能

aio_suspend函數(shù)可以將當(dāng)前進(jìn)程掛起,直到有向其注冊(cè)的異步事件完成為止。

2.6.2 函數(shù)原型

1 int aio_suspend(const struct aiocb * const aiocb_list[],
2 int nitems, const struct timespec *timeout);

2.6.3 返回值

如果在定時(shí)時(shí)間達(dá)到之前,因?yàn)橥瓿闪薎O請(qǐng)求導(dǎo)致函數(shù)返回,此時(shí)返回值是0;否則,返回值為-1,并且會(huì)設(shè)置errno。

2.6.4 使用實(shí)例

2.6.4.1 代碼

前面2個(gè)例子發(fā)起IO請(qǐng)求都是非阻塞的,即使IO請(qǐng)求未完成,也不影響調(diào)用程序繼續(xù)執(zhí)行后面的語(yǔ)句。但是,我們也可以調(diào)用aio_suspend 來(lái)阻塞一個(gè)或多個(gè)異步IO, 只需要將IO請(qǐng)求加入阻塞列表。

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9
10 #define BUFSIZE 1024
11 #define MAX 2
12
13 //異步讀請(qǐng)求
14 int aio_read_file(struct aiocb *cbp,int fd,int size)
15 {
16 int ret;
17 bzero(cbp,sizeof(struct aiocb));
18
19 cbp->aio_buf = (volatile void*)malloc(size+1);
20 cbp->aio_nbytes = size;
21 cbp->aio_offset = 0;
22 cbp->aio_fildes = fd;
23
24 ret = aio_read(cbp);
25 if(ret < 0)
26 {
27 perror("aio_read errorn");
28 exit(1);
29 }
30 }
31
32 int main()
33 {
34 struct aiocb cbp1,cbp2;
35 int fd1,fd2,ret;
36 int i = 0;
37 //異步阻塞列表
38 struct aiocb* aiocb_list[2];
39
40 fd1 = open("test.txt",O_RDONLY);
41 if(fd1 < 0)
42 {
43 perror("open errorn");
44 }
45 aio_read_file(&cbp1,fd1,BUFSIZE);
46
47 fd2 = open("test.txt",O_RDONLY);
48 if(fd2 < 0)
49 {
50 perror("open errorn");
51 }
52 aio_read_file(&cbp2,fd2,BUFSIZE*4);
53
54 //向列表加入兩個(gè)請(qǐng)求
55 aiocb_list[0] = &cbp1;
56 aiocb_list[1] = &cbp2;
57 //阻塞,直到請(qǐng)求完成才會(huì)繼續(xù)執(zhí)行后面的語(yǔ)句
58 aio_suspend((const struct aiocb* const*)aiocb_list,MAX,NULL);
59 printf("read1:%sn",(char*)cbp1.aio_buf);
60 printf("read2:%sn",(char*)cbp2.aio_buf);
61
62 close(fd1);
63 close(fd2);
64 return 0;
65 }

運(yùn)行結(jié)果

圖片

可以看到只有read2讀到了數(shù)據(jù),因?yàn)橹灰蠭O請(qǐng)求完成阻塞函數(shù)aio_suspend就會(huì)直接就返回用戶線程了,繼續(xù)執(zhí)行下面的語(yǔ)句。

2.7 lio_listio

2.7.1 功能

aio同時(shí)還為我們提供了一個(gè)可以發(fā)起多個(gè)或多種I/O請(qǐng)求的接口lio_listio。這個(gè)函數(shù)效率很高,因?yàn)槲覀冎恍枰淮蜗到y(tǒng)調(diào)用(一次內(nèi)核上下位切換)就可以完成大量的I/O操作。第一個(gè)參數(shù):LIO_WAIT (阻塞) 或 LIO_NOWAIT(非阻塞),第二個(gè)參數(shù):異步IO請(qǐng)求列表

2.7.2 函數(shù)原型

int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);

2.7.3 返回值

如果mode是LIO_NOWAIT,當(dāng)所有IO請(qǐng)求成功入隊(duì)后返回0,否則,返回-1,并設(shè)置errno。如果mode是LIO_WAIT,當(dāng)所有IO請(qǐng)求完成之后返回0,否則,返回-1,并設(shè)置errno。

2.7.4 使用示例

2.7.4.1 代碼

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9
10 #define BUFSIZE 100
11 #define MAX 2
12
13 //異步讀結(jié)構(gòu)體
14 int aio_read_file(struct aiocb *cbp,int fd,int size)
15 {
16 int ret;
17 bzero(cbp,sizeof(struct aiocb));
18
19 cbp->aio_buf = (volatile void*)malloc(size+1);
20 cbp->aio_nbytes = size;
21 cbp->aio_offset = 0;
22 cbp->aio_fildes = fd;
23 cbp->aio_lio_opcode = LIO_READ;
24 }
25
26 int main()
27 {
28 struct aiocb cbp1,cbp2;
29 int fd1,fd2,ret;
30 int i = 0;
31 //異步請(qǐng)求列表
32 struct aiocb* io_list[2];
33
34 fd1 = open("test.txt",O_RDONLY);
35 if(fd1 < 0)
36 {
37 perror("open errorn");
38 }
39 aio_read_file(&cbp1,fd1,BUFSIZE);
40
41 fd2 = open("test.txt",O_RDONLY);
42 if(fd2 < 0)
43 {
44 perror("open errorn");
45 }
46 aio_read_file(&cbp2,fd2,BUFSIZE*4);
47
48 io_list[0] = &cbp1;
49 io_list[1] = &cbp2;
50
51 lio_listio(LIO_WAIT,(struct aiocb* const*)io_list,MAX,NULL);
52 printf("read1:%sn",(char*)cbp1.aio_buf);
53 printf("read2:%sn",(char*)cbp2.aio_buf);
54
55 close(fd1);
56 close(fd2);
57 return 0;
58 }

運(yùn)行結(jié)果:

圖片

2.7 異步IO通知機(jī)制

2.7.1 簡(jiǎn)介

異步與同步的區(qū)別就是我們不需要等待異步操作返回就可以繼續(xù)干其他的事情,當(dāng)異步操作完成時(shí)可以通知我們?nèi)ヌ幚硭?/p>

以下兩種方式可以處理異步通知:

2.7.2 信號(hào)處理

2.7.2.1 示例

在發(fā)起異步請(qǐng)求時(shí),可以指定當(dāng)異步操作完成時(shí)給調(diào)用進(jìn)程發(fā)送什么信號(hào),這樣調(diào)用收到此信號(hào)就會(huì)執(zhí)行相應(yīng)的信號(hào)處理函數(shù)。

代碼:

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10
11 #define BUFSIZE 256
12
13 //信號(hào)處理函數(shù),參數(shù)signo接收的對(duì)應(yīng)的信號(hào)值
14 void aio_handler(int signo)
15 {
16 int ret;
17 printf("異步操作完成,收到通知n");
18 }
19
20 int main()
21 {
22 struct aiocb cbp;
23 int fd,ret;
24 int i = 0;
25
26 fd = open("test.txt",O_RDONLY);
27
28 if(fd < 0)
29 {
30 perror("open errorn");
31 }
32
33 //填充struct aiocb 結(jié)構(gòu)體
34 bzero(&cbp,sizeof(cbp));
35 //指定緩沖區(qū)
36 cbp.aio_buf = (volatile void*)malloc(BUFSIZE+1);
37 //請(qǐng)求讀取的字節(jié)數(shù)
38 cbp.aio_nbytes = BUFSIZE;
39 //文件偏移
40 cbp.aio_offset = 0;
41 //讀取的文件描述符
42 cbp.aio_fildes = fd;
43 //發(fā)起讀請(qǐng)求
44
45 //設(shè)置異步通知方式
46 //用信號(hào)通知
47 cbp.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
48 //發(fā)送異步信號(hào)
49 cbp.aio_sigevent.sigev_signo = SIGIO;
50 //傳入aiocb 結(jié)構(gòu)體
51 cbp.aio_sigevent.sigev_value.sival_ptr = &cbp;
52
53 //安裝信號(hào)
54 signal(SIGIO,aio_handler);
55 //發(fā)起異步讀請(qǐng)求
56 ret = aio_read(&cbp);
57 if(ret < 0)
58 {
59 perror("aio_read errorn");
60 exit(1);
61 }
62 //暫停4秒,保證異步請(qǐng)求完成
63 sleep(4);
64 close(fd);
65 return 0;
66 }

運(yùn)行結(jié)果:

圖片

2.7.2.2 小結(jié)

SIGIO 是系統(tǒng)專門用來(lái)表示異步通知的信號(hào),當(dāng)然也可以發(fā)送其他的信號(hào),比如將上面的SIGIO替換為 SIGRTMIN+1 也是可以的。安裝信號(hào)可以使用signal(),但是signal不可以傳遞參數(shù)進(jìn)去。所以推薦使用sigaction()函數(shù),可以為信號(hào)處理設(shè)置更多的東西。

2.7.3 線程回調(diào)

顧名思義就是當(dāng)調(diào)用進(jìn)程收到異步操作完成的通知時(shí),開線程執(zhí)行設(shè)定好的回調(diào)函數(shù)去處理。無(wú)需專門安裝信號(hào)。在Glibc AIO 的實(shí)現(xiàn)中, 用多線程同步來(lái)模擬 異步IO ,以上述代碼為例,它牽涉了3個(gè)線程, 主線程(23908)新建 一個(gè)線程(23909)來(lái)調(diào)用阻塞的pread函數(shù),當(dāng)pread返回時(shí),又創(chuàng)建了一個(gè)線程(23910)來(lái)執(zhí)行我們預(yù)設(shè)的異步回調(diào)函數(shù), 23909 等待23910結(jié)束返回,然后23909也結(jié)束執(zhí)行。與信號(hào)處理方式相比,如上圖,采用線程回調(diào)的方式可以使得在處理異步通知的時(shí)候不會(huì)阻塞當(dāng)前調(diào)用進(jìn)程。

實(shí)際上,為了避免線程的頻繁創(chuàng)建、銷毀,當(dāng)有多個(gè)請(qǐng)求時(shí),Glibc AIO 會(huì)使用線程池,但以上原理是不會(huì)變的,尤其要注意的是:我們的回調(diào)函數(shù)是在一個(gè)單獨(dú)線程中執(zhí)行的.

Glibc AIO 廣受非議,存在一些難以忍受的缺陷和bug,飽受詬病,是極不推薦使用的.

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10
11 #define BUFSIZE 256
12
13 //回調(diào)函數(shù)
14 void aio_handler(sigval_t sigval)
15 {
16 struct aiocb *cbp;
17 int ret;
18
19 printf("異步操作完成,收到通知n");
20 //獲取aiocb 結(jié)構(gòu)體的信息
21 cbp = (struct aiocb*)sigval.sival_ptr;
22
23 if(aio_error(cbp) == 0)
24 {
25 ret = aio_return(cbp);
26 printf("讀請(qǐng)求返回值:%dn",ret);
27 }
28 while(1)
29 {
30 printf("正在執(zhí)行回調(diào)函數(shù)。。。n");
31 sleep(1);
32 }
33 }
34
35 int main()
36 {
37 struct aiocb cbp;
38 int fd,ret;
39 int i = 0;
40
41 fd = open("test.txt",O_RDONLY);
42
43 if(fd < 0)
44 {
45 perror("open errorn");
46 }
47
48 //填充struct aiocb 結(jié)構(gòu)體
49 bzero(&cbp,sizeof(cbp));
50 //指定緩沖區(qū)
51 cbp.aio_buf = (volatile void*)malloc(BUFSIZE+1);
52 //請(qǐng)求讀取的字節(jié)數(shù)
53 cbp.aio_nbytes = BUFSIZE;
54 //文件偏移
55 cbp.aio_offset = 0;
56 //讀取的文件描述符
57 cbp.aio_fildes = fd;
58 //發(fā)起讀請(qǐng)求
59
60 //設(shè)置異步通知方式
61 //用線程回調(diào)
62 cbp.aio_sigevent.sigev_notify = SIGEV_THREAD;
63 //設(shè)置回調(diào)函數(shù)
64 cbp.aio_sigevent.sigev_notify_function = aio_handler;
65 //傳入aiocb 結(jié)構(gòu)體
66 cbp.aio_sigevent.sigev_value.sival_ptr = &cbp;
67 //設(shè)置屬性為默認(rèn)
68 cbp.aio_sigevent.sigev_notify_attributes = NULL;
69
70 //發(fā)起異步讀請(qǐng)求
71 ret = aio_read(&cbp);
72 if(ret < 0)
73 {
74 perror("aio_read errorn");
75 exit(1);
76 }
77 //調(diào)用進(jìn)程繼續(xù)執(zhí)行
78 while(1)
79 {
80 printf("主線程繼續(xù)執(zhí)行。。。n");
81 sleep(1);
82 }
83 close(fd);
84 return 0;
85 }

運(yùn)行結(jié)果:

圖片

三、libaio

3.1 簡(jiǎn)介

上面介紹的aio其實(shí)是用戶層使用線程模擬的異步io,缺點(diǎn)是占用線程資源而且受可用線程的數(shù)量限制。Linux2.6版本后有了libaio,這完全是內(nèi)核級(jí)別的異步IO,IO請(qǐng)求完全由底層自由調(diào)度(以最佳次序的磁盤調(diào)度方式)。

libaio的缺點(diǎn)是,想要使用該種方式的文件必須支持以O(shè)_DIRECT標(biāo)志打開,然而并不是所有的文件系統(tǒng)都支持。如果你沒(méi)有使用O_DIRECT打開文件,它可能仍然“工作”,但它可能不是異步完成的,而是變?yōu)榱俗枞摹?/p>

3.2 安裝

sudo apt-get install libaio-dev

3.3 結(jié)構(gòu)體

3.3.1 aiocb

aiocb結(jié)構(gòu)體:

1 struct aiocb
2 {
3 //要異步操作的文件描述符
4 int aio_fildes;
5 //用于lio操作時(shí)選擇操作何種異步I/O類型
6 int aio_lio_opcode;
7 //異步讀或?qū)懙木彌_區(qū)的緩沖區(qū)
8 volatile void *aio_buf;
9 //異步讀或?qū)懙淖止?jié)數(shù)
10 size_t aio_nbytes;
11 //異步通知的結(jié)構(gòu)體
12 struct sigevent aio_sigevent;
13 }

3.3.2 timespec

3.3.2.1 源碼

timespec結(jié)構(gòu)體

1 struct timespec {
2 time_t tv_sec; /* seconds */
3 long tv_nsec; /* nanoseconds [0 .. 999999999] */
4 };

3.3.2.2 成員分析

tv_sec:秒數(shù)

tv_nsec:毫秒數(shù)

3.3.3 io_event

3.3.3.1 源碼

io_event結(jié)構(gòu)體

1 struct io_event {
2 void *data;
3 struct iocb *obj;
4 unsigned long res;
5 unsigned long res2;
6 };

3.3.3.2 成員分析

io_event是用來(lái)描述返回結(jié)果的:

obj就是之前提交IO任務(wù)時(shí)的iocb; res和res2來(lái)表示IO任務(wù)完成的狀態(tài)。

3.3.4 io_iocb_common

3.3.4.1 源碼

io_iocb_common結(jié)構(gòu)體

1 struct io_iocb_common { // most import structure, either io and iov are both initialized based on this
2
3 PADDEDptr(void *buf, __pad1); // pointer to buffer for io, pointer to iov for io vector
4
5 PADDEDul(nbytes, __pad2); // number of bytes in one io, or number of ios for io vector
6
7 long long offset; //disk offset
8
9 long long __pad3;
10
11 unsigned flags; // interface for set_eventfd(), flag to use eventfd
12
13 unsigned resfd;// interface for set_eventfd(), set to eventfd
14
15 }; /* result code is the amount read or -'ve errno */

io_iocb_common是也是異步IO庫(kù)里面最重要的數(shù)據(jù)結(jié)構(gòu),上面iocb數(shù)據(jù)結(jié)構(gòu)中的聯(lián)合u,通常就是按照io_iocb_common的格式來(lái)初始化的。

3.3.4.2 成員分析

成員 buf: 在批量IO的模式下,表示一個(gè)iovector 數(shù)組的起始地址;在單一IO的模式下,表示數(shù)據(jù)的起始地址; 成員 nbytes: 在批量IO的模式下,表示一個(gè)iovector 數(shù)組的元素個(gè)數(shù);在單一IO的模式下,表示數(shù)據(jù)的長(zhǎng)度; 成員 offset: 表示磁盤或者文件上IO開始的起始地址(偏移) 成員 _pad3: 填充字段,目前可以忽略 成員 flags: 表示是否實(shí)用eventfd 機(jī)制 成員 resfd: 如果使用eventfd機(jī)制,就設(shè)置成之前初始化的eventfd的值, io_set_eventfd()就是用來(lái)封裝上面兩個(gè)域的。

3.3.5 io_iocb

3.3.5.1 源碼

1 struct iocb {
2 PADDEDptr(void *data, __pad1); /* Return in the io completion event */ /// will passed to its io finished events, channel of callback
3
4 PADDED(unsigned key, __pad2); /* For use in identifying io requests */ /// identifier of which iocb, initialized with iocb address?
5
6 short aio_lio_opcode; // io opration code, R/W SYNC/DSYNC/POOL and so on
7
8 short aio_reqprio; // priority
9
10 int aio_fildes; // aio file descriptor, show which file/device the operation will take on
11
12 union {
13 struct io_iocb_common c; // most important, common initialization interface
14
15 struct io_iocb_vector v;
16
17 struct io_iocb_poll poll;
18
19 struct io_iocb_sockaddr saddr;
20
21 } u;
22
23 };

3.3.5.2 成員分析

iocb是異步IO庫(kù)里面最重要的數(shù)據(jù)結(jié)構(gòu),大部分異步IO函數(shù)都帶著這個(gè)參數(shù)。

成員 data: 當(dāng)前iocb對(duì)應(yīng)的IO完成之后,這個(gè)data的值會(huì)自動(dòng)傳遞到這個(gè)IO生成的event的data這個(gè)域上去

成員 key: 標(biāo)識(shí)哪一個(gè)iocb,通常沒(méi)用

成員 ail_lio_opcode:指示當(dāng)前IO操作的類型,可以初始化為下面的類型之一:

1 typedef enum io_iocb_cmd { // IO Operation type, used to initialize aio_lio_opcode
2
3 IO_CMD_PREAD = 0,
4
5 IO_CMD_PWRITE = 1,
6
7 IO_CMD_FSYNC = 2,
8
9 IO_CMD_FDSYNC = 3,
10
11 IO_CMD_POLL = 5, /* Never implemented in mainline, see io_prep_poll */
12
13 IO_CMD_NOOP = 6,
14
15 IO_CMD_PREADV = 7,
16
17 IO_CMD_PWRITEV = 8,
18
19 } io_iocb_cmd_t;

成員 aio_reqprio: 異步IO請(qǐng)求的優(yōu)先級(jí),可能和io queue相關(guān)

成員 aio_filds: 接受IO的打開文件描述符,注意返回這個(gè)描述符的open()函數(shù)所帶的CREATE/RDWR參數(shù)需要和上面的opcod相匹配,滿足讀寫權(quán)限屬性檢查規(guī)則。

成員 u: 指定IO對(duì)應(yīng)的數(shù)據(jù)起始地址,或者IO向量數(shù)組的起始地址

3.4 libaio系統(tǒng)調(diào)用

linux kernel 提供了5個(gè)系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)異步IO。文中最后介紹的是包裝了這些系統(tǒng)調(diào)用的用戶空間的函數(shù)。

1 int io_setup(unsigned nr_events, aio_context_t *ctxp);
2 int io_destroy(aio_context_t ctx);
3 int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]);
4 int io_cancel(aio_context_t ctx, struct iocb *, struct io_event *result);
5 int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

1、建立IO任務(wù)

int io_setup (int maxevents, io_context_t *ctxp);

io_context_t對(duì)應(yīng)內(nèi)核中一個(gè)結(jié)構(gòu),為異步IO請(qǐng)求提供上下文環(huán)境。注意在setup前必須將io_context_t初始化為0。當(dāng)然,這里也需要open需要操作的文件,注意設(shè)置O_DIRECT標(biāo)志。

2、提交IO任務(wù)

long io_submit (aio_context_t ctx_id, long nr, struct iocb **iocbpp);

提交任務(wù)之前必須先填充iocb結(jié)構(gòu)體,libaio提供的包裝函數(shù)說(shuō)明了需要完成的工作:

3.獲取完成的IO long io_getevents (aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout); 這里最重要的就是提供一個(gè)io_event數(shù)組給內(nèi)核來(lái)copy完成的IO請(qǐng)求到這里,數(shù)組的大小是io_setup時(shí)指定的maxevents。timeout是指等待IO完成的超時(shí)時(shí)間,設(shè)置為NULL表示一直等待所有到IO的完成。 4.銷毀IO任務(wù) int io_destrory (io_context_t ctx)

3.5 異步IO上下文 aio_context_t

代碼:

1 #define _GNU_SOURCE /* syscall() is not POSIX */
2 #include /* for perror() */
3 #include /* for syscall() */
4 #include /* for __NR_* definitions */
5 #include /* for AIO types and constants */
6 inline int io_setup(unsigned nr, aio_context_t *ctxp)
7 {
8 return syscall(__NR_io_setup, nr, ctxp);
9 }
10 inline int io_destroy(aio_context_t ctx)
11 {
12 return syscall(__NR_io_destroy, ctx);
13 }
14 int main()
15 {
16 aio_context_t ctx;
17 int ret;
18 ctx = 0;
19 ret = io_setup(128, &ctx);
20 if (ret < 0) {
21 perror("io_setup error");
22 return -1;
23 }
24 printf("after io_setup ctx:%Ldn",ctx);
25 ret = io_destroy(ctx);
26 if (ret < 0) {
27 perror("io_destroy error");
28 return -1;
29 }
30 printf("after io_destroy ctx:%Ldn",ctx);
31 return 0;
32 }

系統(tǒng)調(diào)用io_setup會(huì)創(chuàng)建一個(gè)所謂的"AIO上下文"(即aio_context,后文也叫‘AIO context’等)結(jié)構(gòu)體到在內(nèi)核中。aio_context是用以內(nèi)核實(shí)現(xiàn)異步AIO的數(shù)據(jù)結(jié)構(gòu)。它其實(shí)是一個(gè)無(wú)符號(hào)整形,位于頭文件 /usr/include/linux/aio_abi.h。

typedef unsigned long aio_context_t;

每個(gè)進(jìn)程都可以有多個(gè)aio_context_t。傳入io_setup的第一個(gè)參數(shù)在這里是128,表示同時(shí)駐留在上下文中的IO請(qǐng)求的個(gè)數(shù);第二個(gè)參數(shù)是一個(gè)指針,內(nèi)核會(huì)填充這個(gè)值。io_destroy的作用是銷毀這個(gè)上下文aio_context_t。上面的例子很簡(jiǎn)單,創(chuàng)建一個(gè)aio_context_t并銷毀。

3.6 提交并查詢IO

流程:

  • 每一個(gè)提交的IO請(qǐng)求用結(jié)構(gòu)體struct iocb來(lái)表示。
  • 首先初始化這個(gè)結(jié)構(gòu)體為全零: memset(&cb, 0, sizeof(cb));
  • 然后初始化文件描述符(cb.aio_fildes = fd)和AIO 命令(cb.aio_lio_opcode = IOCB_CMD_PWRITE)
  • 文件描述符對(duì)應(yīng)上文所打開的文件。本例中是./testfile.

代碼:

1 #define _GNU_SOURCE /* syscall() is not POSIX */
2 #include /* for perror() */
3 #include /* for syscall() */
4 #include /* for __NR_* definitions */
5 #include /* for AIO types and constants */
6 #include /* O_RDWR */
7 #include /* memset() */
8 #include /* uint64_t */
9 inline int io_setup(unsigned nr, aio_context_t *ctxp)
10 {
11 return syscall(__NR_io_setup, nr, ctxp);
12 }
13
14 inline int io_destroy(aio_context_t ctx)
15 {
16 return syscall(__NR_io_destroy, ctx);
17 }
18
19 inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp)
20 {
21 return syscall(__NR_io_submit, ctx, nr, iocbpp);
22 }
23
24 inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr, struct io_event *events, struct timespec *timeout)
25 {
26 return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
27 }
28
29 int main()
30 {
31 aio_context_t ctx;
32 struct iocb cb; struct iocb *cbs[1];
33 char data[4096];
34 struct io_event events[1];
35 int ret;
36 int fd;
37 int i ;
38 for(i=0;i<4096;i++)
39 {
40 data[i]=i%50+60;
41 }
42 fd = open("./testfile", O_RDWR | O_CREAT,S_IRWXU);
43 if (fd < 0)
44 {
45 perror("open error");
46 return -1;
47 }
48
49 ctx = 0;
50 ret = io_setup(128, &ctx);
51 printf("after io_setup ctx:%ld",ctx);
52 if (ret < 0)
53 {
54 perror("io_setup error");
55 return -1;
56 } /* setup I/O control block */
57 memset(&cb, 0, sizeof(cb));
58 cb.aio_fildes = fd;
59 cb.aio_lio_opcode = IOCB_CMD_PWRITE;/* command-specific options */
60 cb.aio_buf = (uint64_t)data;
61 cb.aio_offset = 0;
62 cb.aio_nbytes = 4096;
63 cbs[0] = &cb;
64 ret = io_submit(ctx, 1, cbs);
65 if (ret != 1)
66 {
67 if (ret < 0)
68 perror("io_submit error");
69 else
70 fprintf(stderr, "could not sumbit IOs");
71 return -1;
72 } /* get the reply */
73
74 ret = io_getevents(ctx, 1, 1, events, NULL);
75 printf("%dn", ret);
76 struct iocb * result = (struct iocb *)events[0].obj;
77 printf("reusult:%Ld",result->aio_buf);
78 ret = io_destroy(ctx);
79 if (ret < 0)
80 {
81 perror("io_destroy error");
82 return -1;
83 }
84 return 0;
85 }

3.7 內(nèi)核當(dāng)前支持的AIO 命令

內(nèi)核當(dāng)前支持的AIO 命令有

1 IOCB_CMD_PREAD 讀; 對(duì)應(yīng)系統(tǒng)調(diào)用pread().
2 IOCB_CMD_PWRITE 寫,對(duì)應(yīng)系統(tǒng)調(diào)用pwrite().
3 IOCB_CMD_FSYNC 同步文件數(shù)據(jù)到磁盤,對(duì)應(yīng)系統(tǒng)調(diào)用fsync()
4 IOCB_CMD_FDSYNC 同步文件數(shù)據(jù)到磁盤,對(duì)應(yīng)系統(tǒng)調(diào)用fdatasync()
5 IOCB_CMD_PREADV 讀,對(duì)應(yīng)系統(tǒng)調(diào)用readv()
6 IOCB_CMD_PWRITEV 寫,對(duì)應(yīng)系統(tǒng)調(diào)用writev()
7 IOCB_CMD_NOOP 只是內(nèi)核使用

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • IO
    IO
    +關(guān)注

    關(guān)注

    0

    文章

    434

    瀏覽量

    39051
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11213

    瀏覽量

    208736
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6698

    瀏覽量

    123147
  • 磁盤
    +關(guān)注

    關(guān)注

    1

    文章

    365

    瀏覽量

    25156
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux驅(qū)動(dòng)學(xué)習(xí)筆記:異步IO

    前幾篇介紹了幾種IO模型,今天介紹另一種IO模型——異步IO
    發(fā)表于 06-12 16:24 ?652次閱讀

    Linux設(shè)備驅(qū)動(dòng)中的異步通知與異步I/O

    ;信號(hào)驅(qū)動(dòng)的異步I/O"。Linux信號(hào)Linux系統(tǒng)中,異步通知使用信號(hào)來(lái)實(shí)現(xiàn)。信號(hào)也就是一種軟件中斷。信號(hào)的產(chǎn)生:kill raise alarm用戶按下某些終端鍵;硬件異常;終止
    發(fā)表于 02-21 10:52

    linux下的IO模型詳解

      開門見(jiàn)山,Linux下的如中IO模型:阻塞IO模型,非阻塞IO模型,IO復(fù)用模型,信號(hào)驅(qū)動(dòng)IO
    發(fā)表于 10-09 16:12

    異步IO是什么

    python 異步ioAsync IO is a concurrent programming design that has received dedicated support
    發(fā)表于 09-06 07:26

    Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》第9章、Linux設(shè)備驅(qū)動(dòng)中的異步通知與異步IO

    Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》第9章、Linux設(shè)備驅(qū)動(dòng)中的異步通知與異步IO
    發(fā)表于 10-27 11:33 ?0次下載
    《<b class='flag-5'>Linux</b>設(shè)備驅(qū)動(dòng)開發(fā)詳解》第9章、<b class='flag-5'>Linux</b>設(shè)備驅(qū)動(dòng)中的<b class='flag-5'>異步</b>通知與<b class='flag-5'>異步</b><b class='flag-5'>IO</b>

    Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》第8章、Linux設(shè)備驅(qū)動(dòng)中的阻塞與非阻塞IO

    Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》第8章、Linux設(shè)備驅(qū)動(dòng)中的阻塞與非阻塞IO
    發(fā)表于 10-27 11:35 ?9次下載
    《<b class='flag-5'>Linux</b>設(shè)備驅(qū)動(dòng)開發(fā)詳解》第8章、<b class='flag-5'>Linux</b>設(shè)備驅(qū)動(dòng)中的阻塞與非阻塞<b class='flag-5'>IO</b>

    Python異步IO分析

    異步操作在計(jì)算機(jī)軟硬件體系中是一個(gè)普遍概念,根源在于參與協(xié)作的各實(shí)體處理速度上有明顯差異。
    的頭像 發(fā)表于 02-02 09:04 ?1505次閱讀

    Linux驅(qū)動(dòng)技術(shù)中的異步通知技術(shù)

    異步通知的全稱是"信號(hào)驅(qū)動(dòng)的異步IO",通過(guò)"信號(hào)"的方式,期望獲取的資源可用時(shí),驅(qū)動(dòng)會(huì)主動(dòng)通知指定的應(yīng)用程序,和應(yīng)用層的"信號(hào)"相對(duì)應(yīng),這里使用的是信號(hào)"SIGIO"。
    發(fā)表于 05-12 09:24 ?665次閱讀
    <b class='flag-5'>Linux</b>驅(qū)動(dòng)技術(shù)中的<b class='flag-5'>異步</b>通知技術(shù)

    嵌入式Linux 異步IO機(jī)制

    嵌入式linux中文站向廣大嵌入式linux愛(ài)好者介紹一下嵌入式linux系統(tǒng)的I/O機(jī)制。Linux的I/O機(jī)制經(jīng)歷了一下幾個(gè)階段的演進(jìn):1. 同步阻塞I/O: 用戶
    發(fā)表于 04-02 14:31 ?332次閱讀

    鴻蒙系統(tǒng) IO棧和Linux IO棧對(duì)比分析

    華為的鴻蒙系統(tǒng)開源之后第一個(gè)想看的模塊就是 FS 模塊,想了解一下它的 IO 路徑與 linux 的區(qū)別?,F(xiàn)在鴻蒙開源的倉(cāng)庫(kù)中有兩個(gè)內(nèi)核系統(tǒng),一個(gè)是 liteos_a 系統(tǒng),一個(gè)是 liteos_m
    的頭像 發(fā)表于 10-16 10:45 ?2149次閱讀
    鴻蒙系統(tǒng) <b class='flag-5'>IO</b>棧和<b class='flag-5'>Linux</b> <b class='flag-5'>IO</b>棧對(duì)比<b class='flag-5'>分析</b>

    io_uring 優(yōu)化 nginx,基于通用應(yīng)用 nginx 的實(shí)戰(zhàn)

    引言 io_uring是Linux內(nèi)核在v5.1引入的一套異步IO接口,隨著其迅速發(fā)展,現(xiàn)在的io_uring已經(jīng)遠(yuǎn)遠(yuǎn)超過(guò)了純
    的頭像 發(fā)表于 10-10 16:19 ?3033次閱讀
    <b class='flag-5'>io</b>_uring 優(yōu)化 nginx,基于通用應(yīng)用 nginx 的實(shí)戰(zhàn)

    信號(hào)驅(qū)動(dòng)IO異步IO的區(qū)別

    一. 談信號(hào)驅(qū)動(dòng)IO (對(duì)比異步IO來(lái)看) 信號(hào)驅(qū)動(dòng)IO 對(duì)比 異步 IO進(jìn)行理解 信號(hào)驅(qū)動(dòng)
    的頭像 發(fā)表于 11-08 15:32 ?976次閱讀
    信號(hào)驅(qū)動(dòng)<b class='flag-5'>IO</b>與<b class='flag-5'>異步</b><b class='flag-5'>IO</b>的區(qū)別

    linux異步io框架iouring應(yīng)用

    Linux內(nèi)核5.1支持了新的異步IO框架iouring,由Block IO大神也即Fio作者Jens Axboe開發(fā),意在提供一套公用的網(wǎng)絡(luò)和磁盤
    的頭像 發(fā)表于 11-08 15:39 ?631次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>異步</b><b class='flag-5'>io</b>框架iouring應(yīng)用

    異步IO框架iouring介紹

    前言 Linux內(nèi)核5.1支持了新的異步IO框架iouring,由Block IO大神也即Fio作者Jens Axboe開發(fā),意在提供一套公用的網(wǎng)絡(luò)和磁盤
    的頭像 發(fā)表于 11-09 09:30 ?2117次閱讀
    <b class='flag-5'>異步</b><b class='flag-5'>IO</b>框架iouring介紹

    一文解讀Linux 5種IO模型

    Linux里有五種IO模型:阻塞IO、非阻塞IO、多路復(fù)用IO、信號(hào)驅(qū)動(dòng)式IO
    的頭像 發(fā)表于 11-09 11:12 ?151次閱讀
    一文解讀<b class='flag-5'>Linux</b> 5種<b class='flag-5'>IO</b>模型