概述
linux 中最常用的 IO 模型是同步 IO,在這個模型中,請求發出後應用程式會阻塞直到滿足條件(阻塞 IO),或在不滿足條件的情況下立即傳回出錯(非阻塞 IO),這樣做的好處是程式在等待 IO 請求完成時不會占用 CPU
POSIX 定義了異步 IO 應用程式接口(AIO API),linux 2.6 以上版本的核心也實作了核心級别的異步 IO 調用
異步 IO 的基本思想是允許程序發起很多 IO 操作,而不用阻塞任何一個,也不用等待任何操作的完成,直到 IO 操作完成時,程序可以檢索 IO 操作的結果
linux 下主要有兩套異步 IO,分别是 glibc 實作版本,和 linux 核心libaio 封裝的版本
IO 模型簡介
write、read 如果沒有設定 O_NONBLOCK 辨別則為同步阻塞式 IO,一旦 IO 發起,則程序一直等待直到操作完成
設定了 O_NONBLOCK 辨別後,write、read 成為非阻塞 IO,調用後如果資源可用則進行操作,并立即傳回,如果資源不可用則直接傳回出錯,這樣的情況下,程式通常需要進入忙等待狀态,反複調用 IO 操作,直到正常傳回
以上兩種 IO 模型雖然可以很好地完成單機的 IO 操作,但是對于并發的請求則無法實作
對于并發的多個請求,可以使用 IO 複用模型,如 select、poll、epoll 等,但是程序必須阻塞直到操作完成
如果需要進行并發、非阻塞的 IO 操作,比如 CPU 密集型應用及較慢的 IO 操作應用場景下,使用異步 IO 是一個很好地選擇
POSIX AIO -- glibc 版本異步 IO 簡介
glibc 版本異步 IO 主要包含以下接口(全部定義于 aio.h 中,調用時必須使用 POSIX 實時擴充庫 librt):
函數 | 功能 | 原型 |
aio_read | 請求異步讀操作 | int aio_read(struct aiocb *aiocbp); |
aio_write | 請求異步寫操作 | int aio_write(struct aiocb *aiocbp); |
aio_error | 檢查異步請求的狀态 | int aio_error(const struct aiocb *aiocbp); |
aio_return | 獲得完成的異步請求的傳回狀态 | ssize_t aio_return(struct aiocb *aiocbp); |
aio_suspend | 挂起調用程序,直到一個或多個異步請求已經完成(或失敗) | int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout); |
aio_cancel | 取消異步 I/O 請求 | int aio_cancel(int fildes, struct aiocb *aiocbp); |
lio_listio | 同時發起多個異步IO傳輸 | int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig ); |
aiocb 結構
上述函數用到了一個 struct aiocb 結構
主要包含以下字段:
struct aiocb {
int aio_fildes; // 要被讀寫的檔案描述符
volatile void *aio_buf; // 讀寫操作的記憶體 buffer
__off64_t aio_offset; // 讀寫操作的檔案偏移
size_t aio_nbytes; // 需要讀寫的位元組長度
int aio_reqprio; // 請求優先級
struct sigevent aio_sigevent; // 異步操作完成後的信号或回調函數
...
};
上述結構中有一個 aio_sigevent 域,用于定義異步操作完成時通知信号或回調函數
struct sigevent
{
int sigev_notify; // 響應類型
int sigev_signo; // 信号
union sigval sigev_value; // 信号傳遞的參數
void (*sigev_notify_function)(union sigval); // 回調函數
pthread_attr_t *sigev_notify_attributes; // 線程回調
}
上述結構中用到了一個聯合體 sigval:
typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;
通常被稱為“信号的 4 位元組值”,制定了信号傳遞的參數
函數說明
aio_read、aio_write
int aio_read( struct aiocb *aiocbp );
int aio_write( struct aiocb *aiocbp );
調用成功傳回 0,失敗傳回 -1 并設定 errno
将請求添加到 request_queue
通過參數 aiocbp 指向的結構可以設定檔案描述符、檔案偏移量、緩沖區及大小等屬性,函數執行後立即傳回
對于 aio_write,如果設定了 O_APPEND,則檔案偏移量屬性會被忽略
aio_error
int aio_error( struct aiocb *aiocbp );
用于查詢請求的狀态
傳回值如下:
傳回值 | 意義 |
EINPROGRESS | 請求尚未完成 |
ECANCELLED | 請求已經被用用程式取消 |
-1 | 調用出錯,出錯原因檢視 errno |
aio_return
ssize_t aio_return( struct aiocb *aiocbp );
擷取異步 IO 傳回值
調用成功傳回讀寫的字元數,出錯傳回 -1
aio_suspend
int aio_suspend( const struct aiocb *const cblist[],
int n,
const struct timespec *timeout );
阻塞程序,直到清單中的某個異步請求完成
cblist 中任何一個異步請求完成,函數都會傳回 0,出錯傳回 -1
aio_cancel
int aio_cancel( int fd, struct aiocb *aiocbp );
取消一個異步請求,第二個參數為 NULL 則取消所有該 fd 上的異步請求
成功取消傳回 AIO_CANCELED,請求已經完成則傳回 AIO_NOTCANCELED
在取消多個請求的情況下,如果至少有一個請求沒有被取消,則傳回 AIO_NOT_CANCELED,如果沒有一個請求可以被取消,則傳回 AIO_ALLDONE
lio_listio
int lio_listio( int mode,
struct aiocb *list[],
int nent,
struct sigevent *sig );
同時發起多個異步請求,可以很大程度上提高系統的性能
mode 參數可選 LIO_WAIT 或 LIO_NOWAIT 來聲明該函數是否阻塞
nent 參數定義了 list 清單的最大元素個數
list 清單中可以有值為 NULL 的請求,則該請求被忽略
sigevent 的指針定義了在所有 IO 操作都完成時産生的信号或調用的回調函數