AIO 簡(jiǎn)介
Linux 異步(asynchronous) I/O 是 Linux 2.6 內(nèi)核的標(biāo)準(zhǔn)功能嫂丙,你也可以給 2.4 內(nèi)核打上補(bǔ)丁包跟啤。 AIO 背后的基本思想是:允許進(jìn)程發(fā)起多個(gè) I/O 操作隅肥,不必阻塞(block)或等待(wait)到操作完成武福,而是在 I/O 發(fā)出完成通知之后捉片,該進(jìn)程便可獲取到 I/O 的結(jié)果伍纫。
I/O 模型
以下是 Linux 下可用的 I/O 模型昂芜,同步、異步泌神、阻塞和非阻塞模型良漱。
這些 I/O 模型在特定應(yīng)用使用過程中,各有利弊欢际。本節(jié)會(huì)對(duì)這些模型做簡(jiǎn)要探討母市。
同步阻塞 I/O
同步阻塞 I/O 是最常見的模型。在該模型中损趋,用戶空間(user-space)的程序發(fā)起系統(tǒng)調(diào)用后等待返回結(jié)果患久,導(dǎo)致應(yīng)用程序阻塞直到系統(tǒng)調(diào)用完成(數(shù)據(jù)傳輸或錯(cuò)誤)。發(fā)起調(diào)用的程序只是等待響應(yīng)蒋失,不占用 CPU,因此從執(zhí)行的角度來看是比較高效的桐玻。
傳統(tǒng)的阻塞 I/O 模型是當(dāng)今應(yīng)用中最常用的模型篙挽。它的執(zhí)行流程很簡(jiǎn)單,當(dāng)發(fā)起 read 系統(tǒng)調(diào)用時(shí)镊靴,程序被阻塞并將上下文切換到內(nèi)核嫉髓。然后啟動(dòng) read观腊,當(dāng)響應(yīng)返回時(shí),響應(yīng)數(shù)據(jù)將從內(nèi)核空間復(fù)制到用戶空間緩沖區(qū)算行。然后程序解除阻塞(read 調(diào)用返回)梧油。
從應(yīng)用程序的角度來看,read 操作的時(shí)間很長(zhǎng)州邢。但是這個(gè)應(yīng)用實(shí)際上是被阻塞的儡陨,這個(gè) read 操作與內(nèi)核中的其他任務(wù)其實(shí)是交替執(zhí)行的。
同步非阻塞 I/O
同步阻塞比較低效量淌,改進(jìn)版是同步非阻塞 I/O骗村。在該模型中,設(shè)備以非阻塞方式打開呀枢。這意味著(如圖3所示)胚股,不用立即完成 I/O,read 可能會(huì)返回:無法立即執(zhí)行的錯(cuò)誤代碼(EAGAIN 或 EWOULDBLOCK)裙秋。
非阻塞的含義是 I/O 命令可能不會(huì)立即生效琅拌,需要應(yīng)用程序進(jìn)行多次調(diào)用直到 I/O 完成。這可能非常低效摘刑,因?yàn)樵诖蠖鄶?shù)情況下进宝,應(yīng)用程序必須一直運(yùn)行(busy 狀態(tài)),直到數(shù)據(jù)可用或嘗試在內(nèi)核執(zhí)行 read 命令的過程中去執(zhí)行其他任務(wù)枷恕。如圖3所示党晋,同步非阻塞方式會(huì)導(dǎo)致 I/O 延遲,因?yàn)閮?nèi)核中可用的數(shù)據(jù)與調(diào)用 read 的用戶返回之間的任何差距可能會(huì)拉低整體吞吐量徐块。
異步阻塞 I/O
另一個(gè)阻塞范例是基于阻塞通知的非阻塞 I/O未玻。在此模型中,非阻塞 I/O 被設(shè)定胡控,然后使用 select 操作阻塞系統(tǒng)調(diào)用扳剿,直到 I/O 描述符有變動(dòng)。select 調(diào)用可以為多個(gè) I/O 描述符提供通知铜犬。對(duì)于每個(gè)描述符,你可以調(diào)用通知描述符的相關(guān)功能轻庆,如:寫入數(shù)據(jù)癣猾、讀取數(shù)據(jù)的可用性以及是否發(fā)生錯(cuò)誤。
select 調(diào)用效率比較低余爆。雖然它是異步通知的模式纷宇,但不建議用于高性能 I/O。
異步非阻塞 I/O (AIO)
異步非阻塞 I/O 模型是 I/O 并行操作的一種蛾方。read 請(qǐng)求立即返回像捶,表示read 已成功調(diào)用上陕。然后,應(yīng)用程序可以在后臺(tái) read 操作完成前做其他事情拓春。當(dāng) read 響應(yīng)到達(dá)時(shí)释簿,可以生成信號(hào)或基于線程回調(diào)來完成 I/O 事務(wù)。
在單個(gè)進(jìn)程中可以對(duì)多個(gè) I/O 請(qǐng)求并行計(jì)算硼莽、處理庶溶,是利用了處理速度和 I/O 速度之間的速度差。當(dāng)一個(gè)或多個(gè)慢 I/O 請(qǐng)求處于待處理狀態(tài)時(shí)懂鸵,CPU 可以先執(zhí)行其他任務(wù)偏螺,或者在其他 I/O 執(zhí)行過程中去操作已完成的 I/O。
Linux AIO 介紹
在傳統(tǒng)的 I/O 模型中匆光,每個(gè) I/O 通道都有一個(gè)唯一的句柄標(biāo)識(shí)套像。在UNIX? 中,叫做是文件描述符(對(duì)于文件终息,管道夺巩,socket 等都是相同的)。阻塞 I/O 時(shí)采幌,傳輸或在系統(tǒng)調(diào)用完成或發(fā)生錯(cuò)誤時(shí)返回劲够。
AIO 最早在 Linux kernel 2.5 中出現(xiàn),現(xiàn)在已經(jīng)在 2.6 的生產(chǎn)環(huán)境發(fā)布休傍。
在異步非阻塞 I/O 中征绎,可以同時(shí)開啟多個(gè)傳輸。因此需要一個(gè)描述傳輸?shù)纳舷挛男畔⒛ト T?AIO 中人柿,這是一個(gè) aiocb(AIO I/O 控制塊)結(jié)構(gòu)。該結(jié)構(gòu)包含有關(guān)傳輸?shù)乃行畔⒚ρ幔ㄓ糜跀?shù)據(jù)的用戶緩沖區(qū)凫岖。當(dāng)發(fā)生 I/O 通知(稱為完成)時(shí),提供 aiocb 結(jié)構(gòu)來唯一地標(biāo)識(shí)完成的 I/O逢净。
AIO API
AIO API 接口非常簡(jiǎn)單哥放,但它提供了使用幾種不同通知模型進(jìn)行數(shù)據(jù)傳輸?shù)谋匾δ堋?/p>
表1. AIO 接口 APIs
API 函數(shù) | 備注 |
---|---|
aio_read | 請(qǐng)求異步 read 操作 |
aio_error | 檢查異步請(qǐng)求狀態(tài) |
aio_return | 獲取已完成的異步請(qǐng)求返回狀態(tài) |
aio_write | 請(qǐng)求異步 write 操作 |
aio_suspend | 掛起調(diào)用進(jìn)程,直到一個(gè)或多個(gè)異步請(qǐng)求完成(或失數痢) |
aio_cancel | 取消異步 I/O 請(qǐng)求 |
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 完成時(shí)該怎么做∩缏叮現(xiàn)在我將向您展示 AIO 的各個(gè) API 功能如何工作以及如何使用它們。
AIO 通知
下面將介紹異步通知的方法琼娘。我將通過信號(hào)量和回調(diào)函數(shù)來探索異步通知峭弟。
基于信號(hào)量的異步通知
使用信號(hào)量進(jìn)行進(jìn)程間通信(IPC)是UNIX 的經(jīng)典機(jī)制附鸽,AIO 也支持該方式。在下面的案例中瞒瘸,應(yīng)用程序定義了當(dāng)發(fā)生指定信號(hào)時(shí)調(diào)用的信號(hào)處理程序坷备。然后,應(yīng)用程序指定異步請(qǐng)求將在請(qǐng)求完成時(shí)產(chǎn)生一個(gè)信號(hào)挨务。作為信號(hào)上下文的一部分击你,提供特定的 aiocb 請(qǐng)求來跟蹤多個(gè)潛在未完成的請(qǐng)求。
清單5. 使用信號(hào)量做通知的 AIO 請(qǐng)求
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è)置了信號(hào)處理程序來捕獲 aio_completion_handler 函數(shù)中的 SIGIO 信號(hào)丁侄。然后,可以通過初始化 aio_sigevent 結(jié)構(gòu)來引發(fā) SIGIO 通知(通過 sigev_notify 中的 SIGEV_SIGNAL 定義指定)朝巫。讀取完成時(shí)鸿摇,信號(hào)處理程序從信號(hào)的 si_value 結(jié)構(gòu)中提取特定的 aiocb,并通過檢查錯(cuò)誤狀態(tài)和返回狀態(tài)來確定 I/O 完成劈猿。
對(duì)于性能拙吉,完成處理器程序是通過請(qǐng)求下一個(gè)異步傳輸來繼續(xù) I/O 的理想選擇。這樣一來揪荣,完成一次傳輸完成后筷黔,你可以馬上開始下一個(gè)。
基于回調(diào)函數(shù)的異步通知
系統(tǒng)回調(diào)是一種備用的通知機(jī)制仗颈。該機(jī)制不是通過觸發(fā)通知信號(hào)佛舱,而是通過調(diào)用用戶空間中的函數(shù)來通知。初始化 aiocb 的 sigevent 結(jié)構(gòu)挨决,作為正在完成的特定請(qǐng)求的唯一標(biāo)識(shí)请祖;見清單6。
清單6.使用線程回調(diào)通知的 AIO 請(qǐng)求
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 請(qǐng)求后肆捕,使用 SIGEV_THREAD 請(qǐng)求線程回調(diào)用于通知方法。然后盖高,指定特定的通知處理程序并加載要傳遞給處理程序的上下文(在本例中是對(duì) aiocb 請(qǐng)求本身的引用)慎陵。在處理程序中,您只需轉(zhuǎn)換傳入的 sigval 指針喻奥,并使用 AIO 函數(shù)來驗(yàn)證請(qǐng)求是否完成席纽。
AIO 的系統(tǒng)調(diào)優(yōu)
proc 目錄下包含了兩個(gè)可以用于調(diào)優(yōu)異步 I/O 性能的虛擬文件:
- /proc/sys/fs/aio-nr 中是當(dāng)前系統(tǒng)異步 I/O 請(qǐng)求數(shù)的最大范圍。
- /proc/sys/fs/aio-max-nr 中是允許并發(fā)請(qǐng)求的最大數(shù)量映凳,一般是65536(即64KB胆筒,對(duì)大部分程序來說已經(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)阻塞模式不同矫渔,但異步通知模型在概念上很簡(jiǎn)單彤蔽,可以簡(jiǎn)化設(shè)計(jì)。
原文地址:https://www.ibm.com/developerworks/library/l-async/index.html