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

AIO 简介

Linux 异步(asynchronous) I/O 是 Linux 2.6 内核的标准功能,你也可以给 2.4 内核打上补丁包。 AIO 背后的基本思想是:允许进程发起多个 I/O 操作,不必阻塞(block)或等待(wait)到操作完成,而是在 I/O 发出完成通知之后,该进程便可获取到 I/O 的结果。

I/O 模型

以下是 Linux 下可用的 I/O 模型,同步、异步、阻塞和非阻塞模型。

图1.基本 Linux I/O 模型

这些 I/O 模型在特定应用使用过程中,各有利弊。本节会对这些模型做简要探讨。

同步阻塞 I/O

同步阻塞 I/O 是最常见的模型。在该模型中,用户空间(user-space)的程序发起系统调用后等待返回结果,导致应用程序阻塞直到系统调用完成(数据传输或错误)。发起调用的程序只是等待响应,不占用 CPU,因此从执行的角度来看是比较高效的。

传统的阻塞 I/O 模型是当今应用中最常用的模型。它的执行流程很简单,当发起 read 系统调用时,程序被阻塞并将上下文切换到内核。然后启动 read,当响应返回时,响应数据将从内核空间复制到用户空间缓冲区。然后程序解除阻塞(read 调用返回)。

图2.同步阻塞I / O模型的典型流程

从应用程序的角度来看,read 操作的时间很长。但是这个应用实际上是被阻塞的,这个 read 操作与内核中的其他任务其实是交替执行的。

同步非阻塞 I/O

同步阻塞比较低效,改进版是同步非阻塞 I/O。在该模型中,设备以非阻塞方式打开。这意味着(如图3所示),不用立即完成 I/O,read 可能会返回:无法立即执行的错误代码(EAGAIN 或 EWOULDBLOCK)。

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

非阻塞的含义是 I/O 命令可能不会立即生效,需要应用程序进行多次调用直到 I/O 完成。这可能非常低效,因为在大多数情况下,应用程序必须一直运行(busy 状态),直到数据可用或尝试在内核执行 read 命令的过程中去执行其他任务。如图3所示,同步非阻塞方式会导致 I/O 延迟,因为内核中可用的数据与调用 read 的用户返回之间的任何差距可能会拉低整体吞吐量

异步阻塞 I/O

另一个阻塞范例是基于阻塞通知的非阻塞 I/O。在此模型中,非阻塞 I/O 被设定,然后使用 select 操作阻塞系统调用,直到 I/O 描述符有变动。select 调用可以为多个 I/O 描述符提供通知。对于每个描述符,你可以调用通知描述符的相关功能,如:写入数据、读取数据的可用性以及是否发生错误。

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

select 调用效率比较低。虽然它是异步通知的模式,但不建议用于高性能 I/O。

异步非阻塞 I/O (AIO)

异步非阻塞 I/O 模型是 I/O 并行操作的一种。read 请求立即返回,表示read 已成功调用。然后,应用程序可以在后台 read 操作完成前做其他事情。当 read 响应到达时,可以生成信号或基于线程回调来完成 I/O 事务。

图5.异步非阻塞 I/O 模型的典型流程

在单个进程中可以对多个 I/O 请求并行计算、处理,是利用了处理速度和 I/O 速度之间的速度差。当一个或多个慢 I/O 请求处于待处理状态时,CPU 可以先执行其他任务,或者在其他 I/O 执行过程中去操作已完成的 I/O。

Linux AIO 介绍

在传统的 I/O 模型中,每个 I/O 通道都有一个唯一的句柄标识。在UNIX® 中,叫做是文件描述符(对于文件,管道,socket 等都是相同的)。阻塞 I/O 时,传输或在系统调用完成或发生错误时返回。

AIO 最早在 Linux kernel 2.5 中出现,现在已经在 2.6 的生产环境发布。

在异步非阻塞 I/O 中,可以同时开启多个传输。因此需要一个描述传输的上下文信息。在 AIO 中,这是一个 aiocb(AIO I/O 控制块)结构。该结构包含有关传输的所有信息,包括用于数据的用户缓冲区。当发生 I/O 通知(称为完成)时,提供 aiocb 结构来唯一地标识完成的 I/O。

AIO API

AIO API 接口非常简单,但它提供了使用几种不同通知模型进行数据传输的必要功能。

表1. AIO 接口 APIs

API 函数 备注
aio_read 请求异步 read 操作
aio_error 检查异步请求状态
aio_return 获取已完成的异步请求返回状态
aio_write 请求异步 write 操作
aio_suspend 挂起调用进程,直到一个或多个异步请求完成(或失败)
aio_cancel 取消异步 I/O 请求
lio_listio 批量发起异步 I/O 操作

这些 API 函数都用 aiocb 结构来初始化或检查。该结构有很多字段,但清单1仅列出了你可以使用的那些字段。

清单1. aiocb 结构的相关字段

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 结构告诉 AIO 当 I/O 完成时该怎么做。现在我将向您展示 AIO 的各个 API 功能如何工作以及如何使用它们。

AIO 通知

下面将介绍异步通知的方法。我将通过信号量和回调函数来探索异步通知。

基于信号量的异步通知

使用信号量进行进程间通信(IPC)是UNIX 的经典机制,AIO 也支持该方式。在下面的案例中,应用程序定义了当发生指定信号时调用的信号处理程序。然后,应用程序指定异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,提供特定的 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中, 设置了信号处理程序来捕获 aio_completion_handler 函数中的 SIGIO 信号。然后,可以通过初始化 aio_sigevent 结构来引发 SIGIO 通知(通过 sigev_notify 中的 SIGEV_SIGNAL 定义指定)。读取完成时,信号处理程序从信号的 si_value 结构中提取特定的 aiocb,并通过检查错误状态和返回状态来确定 I/O 完成。

对于性能,完成处理器程序是通过请求下一个异步传输来继续 I/O 的理想选择。这样一来,完成一次传输完成后,你可以马上开始下一个。

基于回调函数的异步通知

系统回调是一种备用的通知机制。该机制不是通过触发通知信号,而是通过调用用户空间中的函数来通知。初始化 aiocbsigevent 结构,作为正在完成的特定请求的唯一标识;见清单6。

清单6.使用线程回调通知的 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中,创建 aiocb 请求后,使用 SIGEV_THREAD 请求线程回调用于通知方法。然后,指定特定的通知处理程序并加载要传递给处理程序的上下文(在本例中是对 aiocb 请求本身的引用)。在处理程序中,您只需转换传入的 sigval 指针,并使用 AIO 函数来验证请求是否完成。

AIO 的系统调优

proc 目录下包含了两个可以用于调优异步 I/O 性能的虚拟文件:

  • /proc/sys/fs/aio-nr 中是当前系统异步 I/O 请求数的最大范围。
  • /proc/sys/fs/aio-max-nr 中是允许并发请求的最大数量,一般是65536(即64KB,对大部分程序来说已经足够了)。

总结

使用异步 I/O 可以构建出更快更高效的 I/O 应用。如果你的应用程序可以并行处理和 I/O,则 AIO 可以帮你提高 CPU 资源使用率。虽然异步 I/O 模式与大多数 Linux 应用程序中的传统阻塞模式不同,但异步通知模型在概念上很简单,可以简化设计。

原文地址:https://www.ibm.com/developerworks/library/l-async/index.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容