本文參考了:

  • 《Understanding the Linux Kernel, Third Edition》16.4章
  • aio - POSIX asynchronous I/O overview

簡述

POSIX 非同步 IO interface(AIO)定義了允許進程創建一個或多個非同步的 IO 操作的介面。進程可以在 IO 操作完成之後得到操作系統的通知,手段包括:不通知、信號、實例化thread。

POSIX 標準

注意:這只是 POSIX(The Portable Operating System Interface)定義的介面,並不是實現。Linux 內核對非同步 IO 的具體實現在後面。(This is an API, not implementation!

POSIX 1003.1標準定義了非同步訪問文件的 library function:

系統調用描述aio_read()非同步讀取文件aio_write()非同步寫入文件aio_fsync發出對當前所有 outstanding 的非同步IO操作進行 flush 的請求 (不會阻塞)aio_error()獲取一個處於 outstanding 狀態非同步 IO 操作的 error codeaio_return()獲取一個已經完成的非同步 IO 操作的返回碼aio_cancel()取消一個outstanding的非同步 IO 操作aio_suspend()暫停進程,直到至少有一個outstanding的非同步 IO 操作完成

使用非同步 IO 其實是很簡單的。大致來說分為三步:

  1. 程序先是使用普通的 open()系統調用。
  2. 然後創建一個叫做 aiocb的 control block填滿,比較重要的一些欄位如下:
  • aio_fildes: 對應文件的 fd( open()系統調用返回的)
  • aio_buf : 為此文件準備的User mode buffer
  • aio_nbytes : 有多少 bytes 應該被傳輸
  • aio_offset : read、write 操作應該從哪個 offset 開始(注意這個和同步的 IO 操作是獨立)
  • aio_sigevent : 調用者想要什麼方式獲取 IO 成功的回調通知,包括 SIGEV_NONESIGEV_SIGNALSIGEV_THREAD,分別對應上文提到的三個通知方式。
  • aio_lio_opcode : IO 操作類型:read write sync

具體的定義如下:

#include <aiocb.h>

struct aiocb {
/* The order of these fields is implementation-dependent */

int aio_fildes; /* File descriptor */
off_t aio_offset; /* File offset */
volatile void *aio_buf; /* Location of buffer */
size_t aio_nbytes; /* Length of transfer */
int aio_reqprio; /* Request priority */
struct sigevent aio_sigevent; /* Notification method */
int aio_lio_opcode; /* Operation to be performed;
lio_listio() only */

/* Various implementation-internal fields not shown */
};

/* Operation codes for aio_lio_opcode: */

enum { LIO_READ, LIO_WRITE, LIO_NOP };

3. 最後把 aiocb這個 control block 的地址傳給 aio_read()或者 aio_write()

這兩個函數都會在kernel或library將對應 IO 的數據傳輸加入傳輸隊列之後立即退出。

之後進程可以用 aio_error()檢查非同步 IO 的進行狀態:

aio_error()返回值描述EINPROGRESS還在傳輸中0成功完成其他錯誤碼操作失敗

可以用 aio_return()獲取成功read或write了多少個 bytes,或者-1表示失敗。

Linux 內核支持

在操作系統內核不支持非同步 IO 的情況下,非同步 IO 也能夠實現,實現思路如下: aio_read()aio_write()先克隆當前進程,讓子進程執行同步的 read()write()系統調用,然後父進程結束 aio_read()aio_write(),從而實現非阻塞,主進程可以開始做其他事情。顯然,這種沒有得到內核支持的非同步 IO 操作是比較低效的。

Linux 內核從2.6版本起開始支持下面這些系統調用:

系統調用描述io_setup()為當前進程創建一個非同步IO上下文(asynchronous i/o contex)io_submit()提交一個或多個非同步 IO 操作io_getevents()獲取一些 outstanding 非同步 IO 的運行狀態io_cancel()取消一個非同步 IOio_destroy()摧毀當前進程的非同步 IO 上下文

非同步 IO上下文(AIO context)

用戶態的進程要調用 io_submit()之前,先得調用 io_setup()創建非同步 IO 上下文。

基本上來說,一個 AIO context 就是一個用於追蹤所有進行中的非同步IO操作的數據結構,這個 struct 叫做 kioctx。一個應用可能會創建多個 AIO context,一個進程的所有 kioctx用一個鏈表相連: ioctx_list

這個 ioctx_list保存在該進程的memory descriptor上:

來源於《Understanding the Linux Kernel, Third Edition》 P355

kioctx中有一個重要的數據結構: AIO ringAIO ring的作用是:kernel 把 outstanding 的 非同步 IO 操作完成情況寫到這裡面,由於 AIO ring是位於進程的地址空間的,所以進程可以直接從這個數據介面裡面讀取非同步 IO 的狀態,而不用執行相對較慢的系統調用。

提交非同步 IO 操作

aio_submit()系統調用包含三個參數:

  • ctx_id : io_setup()返回的 id
  • iocbpp : 一個 iocb指針的列表, iocb包含描述一個非同步 IO 操作的信息。
  • nr : iocbpp的長度

其中 iocb和 POSIX 標準中的 aiocb是一樣的,同樣包含 aio_fildes,aio_buf,aio_nbytes,aio_offset,aio_lio_opcode這些欄位。

Linux kernel 有一個 service routine : sys_io_submit(),執行下列操作:

  1. 檢查 iocbpp列表包含的 iocb descriptors是不是合法的。
  2. 通過 xtx_idioctx_list 中檢索出對應的 kioctx
  3. 對每一個 iocb descriptor,執行下列操作:
  • 通過 aio_fildes獲取文件 fd。
  • 為該非同步 IO 操作新建一個 kiocb descriptor。
  • 檢查 AIO ring中是否有足夠的空間來存儲完成結果。
  • 根據IO 類型( aio_lio_opcode欄位)設置 ki_retry方法。
  • 執行 aio_run_iocb()函數,這個函數本質上就是調用上一步的 ki_retry方法,開始執行 IO 操作。如果 ki_retry方法返回 EIOCBRETRY,就表示該非同步 IO 操作已經被提交了,但是還沒有完成。一段時間後 aio_lio_opcode之後會被再次執行,當提示 IO 完成時,就調用 aio_complete()將完成狀態寫入到 AIO ring

推薦閱讀:

相關文章