一、簡(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)體:
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ù)原型
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ù)原型
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。
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)檢查:
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ù)原型
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。
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ù)原型
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)求加入阻塞列表。
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ù)原型
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 代碼
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>
以下兩種方式可以處理異步通知:
- 信號(hào)處理
- 線程回調(diào)
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ù)。
代碼:
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,飽受詬病,是極不推薦使用的.
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 安裝
3.3 結(jié)構(gòu)體
3.3.1 aiocb
aiocb結(jié)構(gòu)體:
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)體
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)體
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)體
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 源碼
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操作的類型,可以初始化為下面的類型之一:
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ù)。
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
代碼:
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。
每個(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.
代碼:
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 命令有
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)核使用
-
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
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論