Linux 原生異步 IO 原理與使用(Native AIO)
什么是異步 IO?
異步 IO:當(dāng)應(yīng)用程序發(fā)起一個(gè) IO 操作后,調(diào)用者不能立刻得到結(jié)果,而是在內(nèi)核完成 IO 操作后,通過(guò)信號(hào)或回調(diào)來(lái)通知調(diào)用者。
異步 IO 與同步 IO 的區(qū)別如 圖1 所示:

從上圖可知,同步 IO 必須等待內(nèi)核把 IO 操作處理完成后才返回。而異步 IO 不必等待 IO 操作完成,而是向內(nèi)核發(fā)起一個(gè) IO 操作就立刻返回,當(dāng)內(nèi)核完成 IO 操作后,會(huì)通過(guò)信號(hào)的方式通知應(yīng)用程序。
Linux 原生 AIO 原理
Linux Native AIO 是 Linux 支持的原生 AIO,為什么要加原生這個(gè)詞呢?因?yàn)長(zhǎng)inux存在很多第三方的異步 IO 庫(kù),如 libeio 和 glibc AIO。所以為了加以區(qū)別,Linux 的內(nèi)核提供的異步 IO 就稱為原生異步 IO。
很多第三方的異步 IO 庫(kù)都不是真正的異步 IO,而是使用多線程來(lái)模擬異步 IO,如 libeio 就是使用多線程來(lái)模擬異步 IO 的。
本文主要介紹 Linux 原生 AIO 的原理和使用,所以不會(huì)對(duì)其他第三方的異步 IO 庫(kù)進(jìn)行分析,下面我們先來(lái)介紹 Linux 原生 AIO 的原理。
如 圖2 所示:

Linux 原生 AIO 處理流程:
當(dāng)應(yīng)用程序調(diào)用
io_submit系統(tǒng)調(diào)用發(fā)起一個(gè)異步 IO 操作后,會(huì)向內(nèi)核的 IO 任務(wù)隊(duì)列中添加一個(gè) IO 任務(wù),并且返回成功。內(nèi)核會(huì)在后臺(tái)處理 IO 任務(wù)隊(duì)列中的 IO 任務(wù),然后把處理結(jié)果存儲(chǔ)在 IO 任務(wù)中。
應(yīng)用程序可以調(diào)用
io_getevents系統(tǒng)調(diào)用來(lái)獲取異步 IO 的處理結(jié)果,如果 IO 操作還沒完成,那么返回失敗信息,否則會(huì)返回 IO 處理結(jié)果。
從上面的流程可以看出,Linux 的異步 IO 操作主要由兩個(gè)步驟組成:
1) 調(diào)用
io_submit函數(shù)發(fā)起一個(gè)異步 IO 操作。2) 調(diào)用
io_getevents函數(shù)獲取異步 IO 的結(jié)果。
下面我們主要分析,Linux 內(nèi)核是怎么實(shí)現(xiàn)異步 IO 的。
Linux 原生 AIO 使用
在介紹 Linux 原生 AIO 的實(shí)現(xiàn)之前,先通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)介紹其使用過(guò)程:
int main(){io_context_t context;struct iocb io[1], *p[1] = {&io[0]};struct io_event e[1];unsigned nr_events = 10;struct timespec timeout;char *wbuf;int wbuflen = 1024;int ret, num = 0, i;posix_memalign((void **)&wbuf, 512, wbuflen);memset(wbuf, '@', wbuflen);memset(&context, 0, sizeof(io_context_t));timeout.tv_sec = 0;timeout.tv_nsec = 10000000;int fd = open(FILEPATH, O_CREAT|O_RDWR|O_DIRECT, 0644); // 1. 打開要進(jìn)行異步IO的文件if (fd < 0) {printf("open error: %d\n", errno);return 0;}if (0 != io_setup(nr_events, &context)) { // 2. 創(chuàng)建一個(gè)異步IO上下文printf("io_setup error: %d\n", errno);return 0;}io_prep_pwrite(&io[0], fd, wbuf, wbuflen, 0); // 3. 創(chuàng)建一個(gè)異步IO任務(wù)if ((ret = io_submit(context, 1, p)) != 1) { // 4. 提交異步IO任務(wù)printf("io_submit error: %d\n", ret);io_destroy(context);return -1;}while (1) {ret = io_getevents(context, 1, 1, e, &timeout); // 5. 獲取異步IO的結(jié)果if (ret < 0) {printf("io_getevents error: %d\n", ret);break;}if (ret > 0) {printf("result, res2: %d, res: %d\n", e[0].res2, e[0].res);break;}}return 0;}
上面通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)展示了 Linux 原生 AIO 的使用過(guò)程,主要有以下步驟:
通過(guò)調(diào)用
open系統(tǒng)調(diào)用打開要進(jìn)行異步 IO 的文件,要注意的是 AIO 操作必須設(shè)置O_DIRECT直接 IO 標(biāo)志位。調(diào)用
io_setup系統(tǒng)調(diào)用創(chuàng)建一個(gè)異步 IO 上下文。調(diào)用
io_prep_pwrite或者io_prep_pread函數(shù)創(chuàng)建一個(gè)異步寫或者異步讀任務(wù)。調(diào)用
io_submit系統(tǒng)調(diào)用把異步 IO 任務(wù)提交到內(nèi)核。調(diào)用
io_getevents系統(tǒng)調(diào)用獲取異步 IO 的結(jié)果。
在上面的例子中,我們獲取異步 IO 操作的結(jié)果是在一個(gè)無(wú)限循環(huán)中進(jìn)行的,其實(shí) Linux 還支持一種基于 eventfd 事件通知的機(jī)制,可以通過(guò) eventfd 和 epoll 結(jié)合來(lái)實(shí)現(xiàn)事件驅(qū)動(dòng)的方式來(lái)獲取異步 IO 操作的結(jié)果,有興趣可以查閱相關(guān)的內(nèi)容。
總結(jié)
本文主要介紹了 Linux 原生 AIO 的原理和使用,Linux 原生 AIO 的使用比較簡(jiǎn)單,但其內(nèi)部實(shí)現(xiàn)比較復(fù)雜,在下篇文章中將會(huì)介紹 Linux 原生 AIO 的實(shí)現(xiàn)。
