使用異步 I/O 提高程序性能

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 模型昂芜,同步、異步泌神、阻塞和非阻塞模型良漱。

圖1.基本 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)用返回)梧油。

圖2.同步阻塞I / 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)裙秋。

圖3.同步非阻塞 I/O 模型的典型流程

非阻塞的含義是 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ò)誤。

圖4.異步阻塞 I/O 模型的典型流程(*select*)

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ù)。

圖5.異步非阻塞 I/O 模型的典型流程

在單個(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ù)來通知。初始化 aiocbsigevent 結(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末庙洼,一起剝皮案震驚了整個(gè)濱河市顿痪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌油够,老刑警劉巖蚁袭,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異石咬,居然都是意外死亡揩悄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門鬼悠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來删性,“玉大人,你說我怎么就攤上這事焕窝〉磐Γ” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵它掂,是天一觀的道長(zhǎng)巴帮。 經(jīng)常有香客問我,道長(zhǎng)群发,這世上最難降的妖魔是什么晰韵? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮熟妓,結(jié)果婚禮上雪猪,老公的妹妹穿的比我還像新娘。我一直安慰自己起愈,他們只是感情好只恨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抬虽,像睡著了一般官觅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阐污,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天休涤,我揣著相機(jī)與錄音,去河邊找鬼。 笑死功氨,一個(gè)胖子當(dāng)著我的面吹牛序苏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捷凄,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼忱详,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了跺涤?” 一聲冷哼從身側(cè)響起匈睁,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桶错,沒想到半個(gè)月后航唆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡院刁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年佛点,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黎比。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡超营,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阅虫,到底是詐尸還是另有隱情演闭,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布颓帝,位于F島的核電站米碰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏购城。R本人自食惡果不足惜吕座,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘪板。 院中可真熱鬧吴趴,春花似錦、人聲如沸侮攀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兰英。三九已至撇叁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畦贸,已是汗流浹背陨闹。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趋厉。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓泡一,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親觅廓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容