POSIX AIO 是在使用者控件模拟異步 IO 的功能,不需要核心支援,而 linux AIO 則是 linux 核心原聲支援的異步 IO 調用,行為更加低級
關于 linux IO 模型及 AIO、POSIX AIO 的簡介,請參看:
POSIX AIO -- glibc 版本異步 IO 簡介
libaio 實作的異步 IO 主要包含以下接口:
函數 | 功能 | 原型 |
io_setup | 建立一個異步IO上下文(io_context_t是一個句柄) | int io_setup(int maxevents, io_context_t *ctxp); |
io_destroy | 銷毀一個異步IO上下文(如果有正在進行的異步IO,取消并等待它們完成) | int io_destroy(io_context_t ctx); |
io_submit | 送出異步IO請求 | long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp); |
io_cancel | 取消一個異步IO請求 | long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result); |
io_getevents | 等待并擷取異步IO請求的事件(也就是異步請求的處理結果) | long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout); |
iocb 結構
struct iocb主要包含以下字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
io_event 結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
異步 IO 上下文
aio_context_t 即 AIO 上下文句柄,該結構體對應核心中的一個 struct kioctx 結構,用來給一組異步 IO 請求提供一個上下文環境,每個程序可以有多個 aio_context_t,io_setup 的第一個參數聲明了同時駐留在核心中的異步 IO 上下文數量
kioctx 結構主要包含以下字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
其中,aio_ring_info 結構用于存放請求結果 io_event 結構的 ring buffer,主要包含以下字段:
struct aio_ring_info
{
unsigned long mmap_base; // ring buffer 的首位址
unsigned long mmap_size; // ring buffer 空間大小
struct page** ring_pages; // ring buffer 對應的 page 數組
long nr_pages; // 配置設定空間對應的頁面數目
unsigned nr; // io_event 的數目
unsigned tail; // io_event 的存取遊标
}
aio_ring_info 結構中,nr_page * PAGE_SIZE = mmap_size
以上資料結構都是在核心位址空間上配置設定的,是核心專有的,使用者程式無法通路和使用
但是 io_event 結構是核心在使用者位址空間上配置設定的 buffer,使用者可以修改,但是首位址、大小等資訊都是由核心維護的,使用者程式通過 io_getevents 函數修改
實作原理
io_setup 函數建立了一個 AIO 上下文,并通過值-結果參數 aio_context_t 類型指針傳回其句柄
io_setup 調用後,核心會通過 mmap 在對應的使用者位址空間配置設定一段記憶體,由 aio_ring_info 結構中的 mmap_base、mmap_size 描述這個映射對應的位置和大小,由 ring_pages、nr_pages 描述實際配置設定的實體記憶體頁面資訊,異步 IO 完成後,核心會将異步 IO 的結果寫入其中
在 mmap_base 指向的使用者位址空間上,會存放着一個 struct aio_ring 結構,用來管理 ring buffer,主要包含以下字段:
1 2 3 4 5 6 7 8 9 10 | |
這個資料結構存在于使用者位址空間中,核心作為生産者,在 buffer 中放入資料,并修改 tail 字段,使用者程式作為消費者從 buffer 中取出資料,并修改 head 字段
每一個請求使用者都會建立一個 iocb 結構用于描述這個請求,而對應于使用者傳遞的每一個 iocb 結構,核心都會生成一個與之對應的 kiocb 結構,并隻該結構中的 ring_info 中預留一個 io_events 空間,用于儲存處理的結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
這以後,對應的異步讀寫請求就通過調用 file->f_op->aio_read 或 file->f_op->aio_write 被送出到了虛拟檔案系統,與普通的檔案讀寫請求非常類似,但是送出完後 IO 請求立即傳回,而不等待虛拟檔案系統完成相應操作
對于虛拟檔案系統傳回 EIOCBRETRY 需要重試的情況,核心會在目前 CPU 的 aio 線程中添加一個任務,讓 aio 完成該任務的重新送出
與 POSIX AIO 差別
從上圖中的流程就可以看出,linux 版本的 AIO 與 POSIX 版本的 AIO 最大的不同在于 linux 版本的 AIO 實際上利用了 CPU 和 IO 裝置異步工作的特性,與同步 IO 相比,很大程度上節約了 CPU 資源的浪費
而 POSIX AIO 利用了線程與線程之間的異步工作特性,在使用者線程中實作 IO 的異步操作
POSIX AIO 支援非 direct-io,而且實作非常靈活,可配置性很高,可以利用核心提供的page cache來提高效率,而 linux 核心實作的 AIO 就隻支援 direct-io,cache 的工作就需要使用者程序考慮了