帶你全面了解Linux原生異步 IO 原理與使用和 AIO 實現(xiàn)(Native AIO)(超級詳細)
什么是異步 IO?
異步 IO:當應(yīng)用程序發(fā)起一個 IO 操作后,調(diào)用者不能立刻得到結(jié)果,而是在內(nèi)核完成 IO 操作后,通過信號或回調(diào)來通知調(diào)用者。
異步 IO 與同步 IO 的區(qū)別如 圖1 所示:

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

Linux 原生 AIO 處理流程:
當應(yīng)用程序調(diào)用 io_submit 系統(tǒng)調(diào)用發(fā)起一個異步 IO 操作后,會向內(nèi)核的 IO 任務(wù)隊列中添加一個 IO 任務(wù),并且返回成功。
內(nèi)核會在后臺處理 IO 任務(wù)隊列中的 IO 任務(wù),然后把處理結(jié)果存儲在 IO 任務(wù)中。
應(yīng)用程序可以調(diào)用 io_getevents 系統(tǒng)調(diào)用來獲取異步 IO 的處理結(jié)果,如果 IO 操作還沒完成,那么返回失敗信息,否則會返回 IO 處理結(jié)果。
從上面的流程可以看出,Linux 的異步 IO 操作主要由兩個步驟組成:
調(diào)用 io_submit 函數(shù)發(fā)起一個異步 IO 操作。
調(diào)用 io_getevents 函數(shù)獲取異步 IO 的結(jié)果。
下面我們主要分析,Linux 內(nèi)核是怎么實現(xiàn)異步 IO 的。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。∏?00名進群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)?


在介紹 Linux 原生 AIO 的實現(xiàn)之前,先通過一個簡單的例子來介紹其使用過程:
上面通過一個簡單的例子來展示了 Linux 原生 AIO 的使用過程,主要有以下步驟:
通過調(diào)用 open 系統(tǒng)調(diào)用打開要進行異步 IO 的文件,要注意的是 AIO 操作必須設(shè)置 O_DIRECT 直接 IO 標志位。
調(diào)用 io_setup 系統(tǒng)調(diào)用創(chuàng)建一個異步 IO 上下文。
調(diào)用 io_prep_pwrite 或者 io_prep_pread 函數(shù)創(chuàng)建一個異步寫或者異步讀任務(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é)果是在一個無限循環(huán)中進行的,其實 Linux 還支持一種基于 eventfd 事件通知的機制,可以通過 eventfd 和 epoll 結(jié)合來實現(xiàn)事件驅(qū)動的方式來獲取異步 IO 操作的結(jié)果,有興趣可以查閱相關(guān)的內(nèi)容。
上面主要介紹了 Linux 原生 AIO 的原理和使用,Linux 原生 AIO 的使用比較簡單,但其內(nèi)部實現(xiàn)比較復雜,下面介紹 Linux 原生 AIO 的實現(xiàn)過程.
本文基于 Linux-2.6.0 版本內(nèi)核源碼
一般來說,使用 Linux 原生 AIO 需要 3 個步驟:
調(diào)用 io_setup 函數(shù)創(chuàng)建一個一般 IO 上下文。
調(diào)用 io_submit 函數(shù)向內(nèi)核提交一個異步 IO 操作。
調(diào)用 io_getevents 函數(shù)獲取異步 IO 操作結(jié)果。
所以,我們可以通過分析這三個函數(shù)的實現(xiàn)來理解 Linux 原生 AIO 的實現(xiàn)。
Linux 原生 AIO 實現(xiàn)在源碼文件 /fs/aio.c 中。
創(chuàng)建異步 IO 上下文
要使用 Linux 原生 AIO,首先需要創(chuàng)建一個異步 IO 上下文,在內(nèi)核中,異步 IO 上下文使用 kioctx 結(jié)構(gòu)表示,定義如下:
在 kioctx 結(jié)構(gòu)中,比較重要的成員為 active_reqs 和 ring_info。active_reqs 保存了所有正在進行的異步 IO 操作,而 ring_info 成員用于存放異步 IO 操作的結(jié)果。
kioctx 結(jié)構(gòu)如 圖1 所示:

如 圖1 所示,active_reqs 成員保存的異步 IO 操作隊列是以 kiocb 結(jié)構(gòu)為單元的,而 ring_info 成員指向一個類型為 aio_ring_info 結(jié)構(gòu)的環(huán)形緩沖區(qū)(Ring Buffer)。
所以我們先來看看 kiocb 結(jié)構(gòu)和 aio_ring_info 結(jié)構(gòu)的定義:
kiocb 結(jié)構(gòu)比較簡單,主要用于保存異步 IO 操作的一些信息,如:
ki_filp:用于保存進行異步 IO 的文件對象。
ki_ctx:指向所屬的異步 IO 上下文對象。
ki_list:用于連接當前異步 IO 上下文中的所有 IO 操作對象。
ki_user_data:這個字段主要提供給用戶自定義使用,比如區(qū)分異步 IO 操作,或者設(shè)置一個回調(diào)函數(shù)等。
ki_pos:用于保存異步 IO 操作的文件偏移量。
而 aio_ring_info 結(jié)構(gòu)是一個環(huán)形緩沖區(qū)的實現(xiàn),其定義如下:
這個環(huán)形緩沖區(qū)主要用于保存已經(jīng)完成的異步 IO 操作的結(jié)果,異步 IO 操作的結(jié)果使用 io_event 結(jié)構(gòu)表示。如 圖2 所示:

圖2 中的 head 代表環(huán)形緩沖區(qū)的開始位置,而 tail 代表環(huán)形緩沖區(qū)的結(jié)束位置,如果 tail 大于 head,則表示有完成的異步 IO 操作結(jié)果可以獲取。如果 head 等于 tail,則表示沒有完成的異步 IO 操作。
環(huán)形緩沖區(qū)的 head 和 tail 位置保存在 aio_ring 的結(jié)構(gòu)中,其定義如下:
上面介紹了那么多數(shù)據(jù)結(jié)構(gòu),只是為了接下來的源碼分析更加容易明白。
現(xiàn)在,我們開始分析異步 IO 上下文的創(chuàng)建過程,異步 IO 上下文的創(chuàng)建通過調(diào)用 io_setup 函數(shù)完成,而 io_setup 函數(shù)會調(diào)用內(nèi)核函數(shù) sys_io_setup,其實現(xiàn)如下:
sys_io_setup 函數(shù)的實現(xiàn)比較簡單,首先調(diào)用 ioctx_alloc 申請一個異步 IO 上下文對象,然后把異步 IO 上下文對象的標識符返回給調(diào)用者。
所以,sys_io_setup 函數(shù)的核心過程是調(diào)用 ioctx_alloc ?函數(shù),我們繼續(xù)分析 ioctx_alloc 函數(shù)的實現(xiàn):
ioctx_alloc ?函數(shù)主要完成以下工作:
調(diào)用 kmem_cache_alloc 函數(shù)向內(nèi)核申請一個異步 IO 上下文對象。
初始化異步 IO 上下文各個成員變量,如初始化異步 IO 操作隊列。
調(diào)用 aio_setup_ring 函數(shù)初始化環(huán)形緩沖區(qū)。
環(huán)形緩沖區(qū)初始化函數(shù) aio_setup_ring 的實現(xiàn)有點小復雜,主要涉及內(nèi)存管理的知識點,所以這里跳過這部分的分析,有興趣的可以私聊我一起討論。
提交異步 IO 操作
提交異步 IO 操作是通過 io_submit 函數(shù)完成的,io_submit 需要提供一個類型為 iocb 結(jié)構(gòu)的數(shù)組,表示要進行的異步 IO 操作相關(guān)的信息,我們先來看看 iocb 結(jié)構(gòu)的定義:
io_submit 函數(shù)最終會調(diào)用內(nèi)核函數(shù) sys_io_submit 來實現(xiàn)提供異步 IO 操作,我們來分析 sys_io_submit 函數(shù)的實現(xiàn):
sys_io_submit 函數(shù)的實現(xiàn)比較簡單,主要從用戶空間復制異步 IO 操作信息到內(nèi)核空間,然后調(diào)用 io_submit_one 函數(shù)提交異步 IO 操作。我們重點分析 io_submit_one 函數(shù)的實現(xiàn):
上面代碼已經(jīng)對 io_submit_one 函數(shù)進行了詳細的注釋,這里總結(jié)一下 io_submit_one 函數(shù)主要完成的工作:
通過調(diào)用 fget 函數(shù)獲取文件句柄對應(yīng)的文件對象。
調(diào)用 aio_get_req 函數(shù)獲取一個類型為 kiocb 結(jié)構(gòu)的異步 IO 操作對象,這個結(jié)構(gòu)前面已經(jīng)分析過。另外,aio_get_req 函數(shù)還會把異步 IO 操作對象添加到異步 IO 上下文的 active_reqs 隊列中。
根據(jù)不同的異步 IO 操作類型來進行不同的處理,如 異步讀操作 會調(diào)用文件對象的 aio_read 方法來進行處理。不同的文件系統(tǒng),其 aio_read 方法的實現(xiàn)不一樣,如 Ext3 文件系統(tǒng)的 aio_read 方法會指向 generic_file_aio_read 函數(shù)。
如果異步 IO 操作被添加到內(nèi)核的 IO 請求隊列中,那么就直接返回。否則就代表 IO 操作已經(jīng)完成,那么就調(diào)用 aio_complete 函數(shù)完成收尾工作。
io_submit_one 函數(shù)的操作過程如 圖3 所示:

所以,io_submit_one 函數(shù)的主要任務(wù)就是向內(nèi)核提交 IO 請求。
異步 IO 操作完成
當異步 IO 操作完成后,內(nèi)核會調(diào)用 aio_complete 函數(shù)來把處理結(jié)果放進異步 IO 上下文的環(huán)形緩沖區(qū) ring_info 中,我們來分析一下 aio_complete 函數(shù)的實現(xiàn):
aio_complete 函數(shù)的 iocb 參數(shù)是我們通過調(diào)用 io_submit_once 函數(shù)提交的異步 IO 對象,而參數(shù) res 和 res2 是用內(nèi)核進行 IO 操作完成后返回的結(jié)果。
aio_complete 函數(shù)的主要工作如下:
根據(jù)環(huán)形緩沖區(qū)的 tail 指針獲取一個空閑的 io_event 對象來保存 IO 操作的結(jié)果。
對環(huán)形緩沖區(qū)的 tail 指針進行加一操作,指向下一個空閑的位置。
當把異步 IO 操作的結(jié)果保存到環(huán)形緩沖區(qū)后,用戶層就可以通過調(diào)用 io_getevents 函數(shù)來讀取 IO 操作的結(jié)果,io_getevents 函數(shù)最終會調(diào)用 sys_io_getevents 函數(shù)。
我們來分析 sys_io_getevents 函數(shù)的實現(xiàn):
從上面的代碼可以看出,sys_io_getevents 函數(shù)主要調(diào)用 read_events 函數(shù)來讀取異步 IO 操作的結(jié)果,我們接著分析 read_events 函數(shù):
read_events 函數(shù)主要還是調(diào)用 aio_read_evt 函數(shù)來從環(huán)形緩沖區(qū)中讀取異步 IO 操作的結(jié)果,如果讀取成功,就把結(jié)果復制到用戶空間中。
aio_read_evt 函數(shù)是從環(huán)形緩沖區(qū)中讀取異步 IO 操作的結(jié)果,其實現(xiàn)如下:
aio_read_evt 函數(shù)的主要工作就是判斷環(huán)形緩沖區(qū)是否為空,如果不為空就從環(huán)形緩沖區(qū)中讀取異步 IO 操作的結(jié)果,并且保存到參數(shù) ent 中,并且移動環(huán)形緩沖區(qū)的 head 指針到下一個位置。
