AIO 簡介
Linux 異步(asynchronous) I/O 是 Linux 2.6 內(nèi)核的標(biāo)準(zhǔn)功能,你也可以給 2.4 內(nèi)核打上補(bǔ)丁包。 AIO 背后的基本思想是:允許進(jìn)程發(fā)起多個 I/O 操作,不必阻塞(block)或等待(wait)到操作完成,而是在 I/O 發(fā)出完成通知之后,該進(jìn)程便可獲取到 I/O 的結(jié)果。
I/O 模型
以下是 Linux 下可用的 I/O 模型,同步、異步、阻塞和非阻塞模型。

這些 I/O 模型在特定應(yīng)用使用過程中,各有利弊。本節(jié)會對這些模型做簡要探討。
同步阻塞 I/O
同步阻塞 I/O 是最常見的模型。在該模型中,用戶空間(user-space)的程序發(fā)起系統(tǒng)調(diào)用后等待返回結(jié)果,導(dǎo)致應(yīng)用程序阻塞直到系統(tǒng)調(diào)用完成(數(shù)據(jù)傳輸或錯誤)。發(fā)起調(diào)用的程序只是等待響應(yīng),不占用 CPU,因此從執(zhí)行的角度來看是比較高效的。
傳統(tǒng)的阻塞 I/O 模型是當(dāng)今應(yīng)用中最常用的模型。它的執(zhí)行流程很簡單,當(dāng)發(fā)起 read 系統(tǒng)調(diào)用時,程序被阻塞并將上下文切換到內(nèi)核。然后啟動 read,當(dāng)響應(yīng)返回時,響應(yīng)數(shù)據(jù)將從內(nèi)核空間復(fù)制到用戶空間緩沖區(qū)。然后程序解除阻塞(read 調(diào)用返回)。

從應(yīng)用程序的角度來看,read 操作的時間很長。但是這個應(yīng)用實(shí)際上是被阻塞的,這個 read 操作與內(nèi)核中的其他任務(wù)其實(shí)是交替執(zhí)行的。
同步非阻塞 I/O
同步阻塞比較低效,改進(jìn)版是同步非阻塞 I/O。在該模型中,設(shè)備以非阻塞方式打開。這意味著(如圖3所示),不用立即完成 I/O,read 可能會返回:無法立即執(zhí)行的錯誤代碼(EAGAIN 或 EWOULDBLOCK)。

非阻塞的含義是 I/O 命令可能不會立即生效,需要應(yīng)用程序進(jìn)行多次調(diào)用直到 I/O 完成。這可能非常低效,因為在大多數(shù)情況下,應(yīng)用程序必須一直運(yùn)行(busy 狀態(tài)),直到數(shù)據(jù)可用或嘗試在內(nèi)核執(zhí)行 read 命令的過程中去執(zhí)行其他任務(wù)。如圖3所示,同步非阻塞方式會導(dǎo)致 I/O 延遲,因為內(nèi)核中可用的數(shù)據(jù)與調(diào)用 read 的用戶返回之間的任何差距可能會拉低整體吞吐量。
異步阻塞 I/O
另一個阻塞范例是基于阻塞通知的非阻塞 I/O。在此模型中,非阻塞 I/O 被設(shè)定,然后使用 select 操作阻塞系統(tǒng)調(diào)用,直到 I/O 描述符有變動。select 調(diào)用可以為多個 I/O 描述符提供通知。對于每個描述符,你可以調(diào)用通知描述符的相關(guān)功能,如:寫入數(shù)據(jù)、讀取數(shù)據(jù)的可用性以及是否發(fā)生錯誤。

select 調(diào)用效率比較低。雖然它是異步通知的模式,但不建議用于高性能 I/O。
異步非阻塞 I/O (AIO)
異步非阻塞 I/O 模型是 I/O 并行操作的一種。read 請求立即返回,表示read 已成功調(diào)用。然后,應(yīng)用程序可以在后臺 read 操作完成前做其他事情。當(dāng) read 響應(yīng)到達(dá)時,可以生成信號或基于線程回調(diào)來完成 I/O 事務(wù)。

在單個進(jìn)程中可以對多個 I/O 請求并行計算、處理,是利用了處理速度和 I/O 速度之間的速度差。當(dāng)一個或多個慢 I/O 請求處于待處理狀態(tài)時,CPU 可以先執(zhí)行其他任務(wù),或者在其他 I/O 執(zhí)行過程中去操作已完成的 I/O。
Linux AIO 介紹
在傳統(tǒng)的 I/O 模型中,每個 I/O 通道都有一個唯一的句柄標(biāo)識。在UNIX? 中,叫做是文件描述符(對于文件,管道,socket 等都是相同的)。阻塞 I/O 時,傳輸或在系統(tǒng)調(diào)用完成或發(fā)生錯誤時返回。
AIO 最早在 Linux kernel 2.5 中出現(xiàn),現(xiàn)在已經(jīng)在 2.6 的生產(chǎn)環(huán)境發(fā)布。
在異步非阻塞 I/O 中,可以同時開啟多個傳輸。因此需要一個描述傳輸?shù)纳舷挛男畔?。?AIO 中,這是一個 aiocb(AIO I/O 控制塊)結(jié)構(gòu)。該結(jié)構(gòu)包含有關(guān)傳輸?shù)乃行畔?,包括用于?shù)據(jù)的用戶緩沖區(qū)。當(dāng)發(fā)生 I/O 通知(稱為完成)時,提供 aiocb 結(jié)構(gòu)來唯一地標(biāo)識完成的 I/O。
AIO API
AIO API 接口非常簡單,但它提供了使用幾種不同通知模型進(jìn)行數(shù)據(jù)傳輸?shù)谋匾δ堋?/p>
表1. AIO 接口 APIs
| API 函數(shù) | 備注 |
|---|---|
| aio_read | 請求異步 read 操作 |
| aio_error | 檢查異步請求狀態(tài) |
| aio_return | 獲取已完成的異步請求返回狀態(tài) |
| aio_write | 請求異步 write 操作 |
| aio_suspend | 掛起調(diào)用進(jìn)程,直到一個或多個異步請求完成(或失?。?/td> |
| aio_cancel | 取消異步 I/O 請求 |
| lio_listio | 批量發(fā)起異步 I/O 操作 |
這些 API 函數(shù)都用 aiocb 結(jié)構(gòu)來初始化或檢查。該結(jié)構(gòu)有很多字段,但清單1僅列出了你可以使用的那些字段。
清單1. aiocb 結(jié)構(gòu)的相關(guān)字段
struct aiocb {
int aio_fildes; // File Descriptor
int aio_lio_opcode; // Valid only for lio_listio (r/w/nop)
volatile void *aio_buf; // Data Buffer
size_t aio_nbytes; // Number of Bytes in Data Buffer
struct sigevent aio_sigevent; // Notification Structure
/* Internal fields */
...
};
sigevent 結(jié)構(gòu)告訴 AIO 當(dāng) I/O 完成時該怎么做?,F(xiàn)在我將向您展示 AIO 的各個 API 功能如何工作以及如何使用它們。
AIO 通知
下面將介紹異步通知的方法。我將通過信號量和回調(diào)函數(shù)來探索異步通知。
基于信號量的異步通知
使用信號量進(jìn)行進(jìn)程間通信(IPC)是UNIX 的經(jīng)典機(jī)制,AIO 也支持該方式。在下面的案例中,應(yīng)用程序定義了當(dāng)發(fā)生指定信號時調(diào)用的信號處理程序。然后,應(yīng)用程序指定異步請求將在請求完成時產(chǎn)生一個信號。作為信號上下文的一部分,提供特定的 aiocb 請求來跟蹤多個潛在未完成的請求。
清單5. 使用信號量做通知的 AIO 請求
void setup_io( ... )
{
int fd;
struct sigaction sig_act;
struct aiocb my_aiocb;
...
/* Set up the signal handler */
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_SIGINFO;
sig_act.sa_sigaction = aio_completion_handler;
/* Set up the AIO request */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset;
/* Link the AIO request with the Signal Handler */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
my_aiocb.aio_sigevent.sigev_signo = SIGIO;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
/* Map the Signal to the Signal Handler */
ret = sigaction( SIGIO, &sig_act, NULL );
...
ret = aio_read( &my_aiocb );
}
void aio_completion_handler( int signo, siginfo_t *info, void *context )
{
struct aiocb *req;
/* Ensure it's our signal */
if (info->si_signo == SIGIO) {
req = (struct aiocb *)info->si_value.sival_ptr;
/* Did the request complete? */
if (aio_error( req ) == 0) {
/* Request completed successfully, get the return status */
ret = aio_return( req );
}
}
return;
}
在清單5中, 設(shè)置了信號處理程序來捕獲 aio_completion_handler 函數(shù)中的 SIGIO 信號。然后,可以通過初始化 aio_sigevent 結(jié)構(gòu)來引發(fā) SIGIO 通知(通過 sigev_notify 中的 SIGEV_SIGNAL 定義指定)。讀取完成時,信號處理程序從信號的 si_value 結(jié)構(gòu)中提取特定的 aiocb,并通過檢查錯誤狀態(tài)和返回狀態(tài)來確定 I/O 完成。
對于性能,完成處理器程序是通過請求下一個異步傳輸來繼續(xù) I/O 的理想選擇。這樣一來,完成一次傳輸完成后,你可以馬上開始下一個。
基于回調(diào)函數(shù)的異步通知
系統(tǒng)回調(diào)是一種備用的通知機(jī)制。該機(jī)制不是通過觸發(fā)通知信號,而是通過調(diào)用用戶空間中的函數(shù)來通知。初始化 aiocb 的 sigevent 結(jié)構(gòu),作為正在完成的特定請求的唯一標(biāo)識;見清單6。
清單6.使用線程回調(diào)通知的 AIO 請求
void setup_io( ... )
{
int fd;
struct aiocb my_aiocb;
...
/* Set up the AIO request */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset;
/* Link the AIO request with a thread callback */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
my_aiocb.aio_sigevent.notify_attributes = NULL;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
...
ret = aio_read( &my_aiocb );
}
void aio_completion_handler( sigval_t sigval )
{
struct aiocb *req;
req = (struct aiocb *)sigval.sival_ptr;
/* Did the request complete? */
if (aio_error( req ) == 0) {
/* Request completed successfully, get the return status */
ret = aio_return( req );
}
return;
}
在清單6中,創(chuàng)建 aiocb 請求后,使用 SIGEV_THREAD 請求線程回調(diào)用于通知方法。然后,指定特定的通知處理程序并加載要傳遞給處理程序的上下文(在本例中是對 aiocb 請求本身的引用)。在處理程序中,您只需轉(zhuǎn)換傳入的 sigval 指針,并使用 AIO 函數(shù)來驗證請求是否完成。
AIO 的系統(tǒng)調(diào)優(yōu)
proc 目錄下包含了兩個可以用于調(diào)優(yōu)異步 I/O 性能的虛擬文件:
- /proc/sys/fs/aio-nr 中是當(dāng)前系統(tǒng)異步 I/O 請求數(shù)的最大范圍。
- /proc/sys/fs/aio-max-nr 中是允許并發(fā)請求的最大數(shù)量,一般是65536(即64KB,對大部分程序來說已經(jīng)足夠了)。
總結(jié)
使用異步 I/O 可以構(gòu)建出更快更高效的 I/O 應(yīng)用。如果你的應(yīng)用程序可以并行處理和 I/O,則 AIO 可以幫你提高 CPU 資源使用率。雖然異步 I/O 模式與大多數(shù) Linux 應(yīng)用程序中的傳統(tǒng)阻塞模式不同,但異步通知模型在概念上很簡單,可以簡化設(shè)計。
原文地址:https://www.ibm.com/developerworks/library/l-async/index.html