转自:http://blog.chinaunix.net/uid-24567872-id-87676.htmlhtml
Linux® 中最经常使用的输入/输出(I/O)模型是同步 I/O。在这个模型中,当请求发出以后,应用程序就会阻塞,直到请求知足为止。这是很好的一种解决方案,由于调用应用程序在等待 I/O 请求完成时不须要使用任何中央处理单元(CPU)。可是在某些状况中,I/O 请求可能须要与其余进程产生交叠。可移植操做系统接口(POSIX)异步 I/O(AIO)应用程序接口(API)就提供了这种功能。在本文中,咱们将对这个 API 概要进行介绍,并来了解一下如何使用它。node
Linux 异步 I/O 是 Linux 内核中提供的一个至关新的加强。它是 2.6 版本内核的一个标准特性,可是咱们在 2.4 版本内核的补丁中也能够找到它。AIO 背后的基本思想是容许进程发起不少 I/O 操做,而不用阻塞或等待任何操做完成。稍后或在接收到 I/O 操做完成的通知时,进程就能够检索 I/O 操做的结果。并发
在深刻介绍 AIO API 以前,让咱们先来探索一下 Linux 上可使用的不一样 I/O 模型。这并非一个详尽的介绍,可是咱们将试图介绍最经常使用的一些模型来解释它们与异步 I/O 之间的区别。图 1 给出了同步和异步模型,以及阻塞和非阻塞的模型。async
每一个 I/O 模型都有本身的使用模式,它们对于特定的应用程序都有本身的优势。本节将简要对其一一进行介绍。函数
![]() |
|
最经常使用的一个模型是同步阻塞 I/O 模型。在这个模型中,用户空间的应用程序执行一个系统调用,这会致使应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种再也不消费 CPU 而只是简单等待响应的状态,所以从处理的角度来看,这是很是有效的。
图 2 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为经常使用的一种模型。其行为很是容易理解,其用法对于典型的应用程序来讲都很是有效。在调用 read
系统调用时,应用程序会阻塞并对内核进行上下文切换。而后会触发读操做,当响应返回时(从咱们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。而后应用程序就会解除阻塞(read
调用返回)。
从应用程序的角度来讲,read
调用会延续很长时间。实际上,在内核执行读操做和其余工做时,应用程序的确会被阻塞。
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操做不会当即完成,read
操做可能会返回一个错误代码,说明这个命令不能当即知足(EAGAIN
或EWOULDBLOCK
),如图 3 所示。
非阻塞的实现是 I/O 命令可能并不会当即知足,须要应用程序调用许屡次来等待操做完成。这可能效率不高,由于在不少状况下,当内核执行这个命令时,应用程序必需要进行忙碌等待,直到数据可用为止,或者试图执行其余工做。正如图 3 所示的同样,这个方法能够引入 I/O 操做的延时,由于数据在内核中变为可用到用户调用 read
返回数据之间存在必定的间隔,这会致使总体数据吞吐量的下降。
另一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,而后使用阻塞 select
系统调用来肯定一个 I/O 描述符什么时候有操做。使 select
调用很是有趣的是它能够用来为多个描述符提供通知,而不只仅为一个描述符提供通知。对于每一个提示符来讲,咱们能够请求这个描述符能够写数据、有读数据可用以及是否发生错误的通知。
图 4. 异步阻塞 I/O 模型的典型流程 (select)
select
调用的主要问题是它的效率不是很是高。尽管这是异步通知使用的一种方便模型,可是对于高性能的 I/O 操做来讲不建议使用。
最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会当即返回,说明 read
请求已经成功发起了。在后台完成读操做时,应用程序而后会执行其余处理操做。当 read
的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成此次 I/O 处理过程。
在一个进程中为了执行多个 I/O 请求而对计算操做和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差别。当一个或多个 I/O 请求挂起时,CPU 能够执行其余任务;或者更为常见的是,在发起其余 I/O 的同时对已经完成的 I/O 进行操做。
下一节将深刻介绍这种模型,探索这种模型使用的 API,而后展现几个命令。
![]() ![]() |
![]() |
从前面 I/O 模型的分类中,咱们能够看出 AIO 的动机。这种阻塞模型须要在 I/O 操做开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操做。同步非阻塞模型容许处理和 I/O 操做重叠进行,可是这须要应用程序根据重现的规则来检查 I/O 操做的状态。这样就剩下异步非阻塞 I/O 了,它容许处理和 I/O 操做重叠进行,包括 I/O 操做完成的通知。
除了须要阻塞以外,select
函数所提供的功能(异步阻塞 I/O)与 AIO 相似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。
![]() ![]() |
![]() |
本节将探索 Linux 的异步 I/O 模型,从而帮助咱们理解如何在应用程序中使用这种技术。
在传统的 I/O 模型中,有一个使用唯一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,咱们发起了一次传输操做,当传输操做完成或发生错误时,系统调用就会返回。
![]() |
|
在异步非阻塞 I/O 中,咱们能够同时发起多个传输操做。这须要每一个传输操做都有唯一的上下文,这样咱们才能在它们完成时区分究竟是哪一个传输操做完成了。在 AIO 中,这是一个 aiocb
(AIO I/O Control Block)结构。这个结构包含了有关传输的全部信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb
结构就被用来唯一标识所完成的 I/O 操做。这个 API 的展现显示了如何使用它。
![]() ![]() |
![]() |
AIO 接口的 API 很是简单,可是它为数据传输提供了必需的功能,并给出了两个不一样的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。
API 函数 | 说明 |
---|---|
aio_read |
请求异步读操做 |
aio_error |
检查异步请求的状态 |
aio_return |
得到完成的异步请求的返回状态 |
aio_write |
请求异步写操做 |
aio_suspend |
挂起调用进程,直到一个或多个异步请求已经完成(或失败) |
aio_cancel |
取消异步 I/O 请求 |
lio_listio |
发起一系列 I/O 操做 |
每一个 API 函数都使用 aiocb
结构开始或检查。这个结构有不少元素,可是清单 1 仅仅给出了须要(或能够)使用的元素。
|
sigevent
结构告诉 AIO 在 I/O 操做完成时应该执行什么操做。咱们将在 AIO 的展现中对这个结构进行探索。如今咱们将展现各个 AIO 的 API 函数是如何工做的,以及咱们应该如何使用它们。
aio_read
函数请求对一个有效的文件描述符进行异步读操做。这个文件描述符能够表示一个文件、套接字甚至管道。aio_read
函数的原型以下:
int aio_read( struct aiocb *aiocbp ); |
aio_read
函数在请求进行排队以后会当即返回。若是执行成功,返回值就为 0;若是出现错误,返回值就为 -1,并设置 errno
的值。
要执行读操做,应用程序必须对 aiocb
结构进行初始化。下面这个简短的例子就展现了如何填充 aiocb
请求结构,并使用 aio_read
来执行异步读请求(如今暂时忽略通知)操做。它还展现了 aio_error
的用法,不过咱们将稍后再做解释。
|
在清单 2 中,在打开要从中读取数据的文件以后,咱们就清空了 aiocb
结构,而后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf
中。而后,咱们将 aio_nbytes
初始化成缓冲区的大小。并将aio_offset
设置成 0(该文件中的第一个偏移量)。咱们将 aio_fildes
设置为从中读取数据的文件描述符。在设置这些域以后,就调用 aio_read
请求进行读操做。咱们而后能够调用 aio_error
来肯定 aio_read
的状态。只要状态是 EINPROGRESS
,就一直忙碌等待,直到状态发生变化为止。如今,请求可能成功,也可能失败。
![]() |
|
注意使用这个 API 与标准的库函数从文件中读取内容是很是类似的。除了 aio_read
的一些异步特性以外,另一个区别是读操做偏移量的设置。在传统的 read
调用中,偏移量是在文件描述符上下文中进行维护的。对于每一个读操做来讲,偏移量都须要进行更新,这样后续的读操做才能对下一块数据进行寻址。对于异步 I/O 操做来讲这是不可能的,由于咱们能够同时执行不少读请求,所以必须为每一个特定的读请求都指定偏移量。
aio_error
函数被用来肯定请求的状态。其原型以下:
int aio_error( struct aiocb *aiocbp ); |
这个函数能够返回如下内容:
EINPROGRESS
,说明请求还没有完成ECANCELLED
,说明请求被应用程序取消了-1
,说明发生了错误,具体错误缘由能够查阅 errno
异步 I/O 和标准块 I/O 之间的另一个区别是咱们不能当即访问这个函数的返回状态,由于咱们并无阻塞在 read
调用上。在标准的 read
调用中,返回状态是在该函数返回时提供的。可是在异步 I/O 中,咱们要使用aio_return
函数。这个函数的原型以下:
ssize_t aio_return( struct aiocb *aiocbp ); |
只有在 aio_error
调用肯定请求已经完成(可能成功,也可能发生了错误)以后,才会调用这个函数。aio_return
的返回值就等价于同步状况中 read
或 write
系统调用的返回值(所传输的字节数,若是发生错误,返回值就为 -1
)。
aio_write
函数用来请求一个异步写操做。其函数原型以下:
int aio_write( struct aiocb *aiocbp ); |
aio_write
函数会当即返回,说明请求已经进行排队(成功时返回值为 0
,失败时返回值为 -1
,并相应地设置 errno
)。
这与 read
系统调用相似,可是有一点不同的行为须要注意。回想一下对于 read
调用来讲,要使用的偏移量是很是重要的。然而,对于 write
来讲,这个偏移量只有在没有设置 O_APPEND
选项的文件上下文中才会很是重要。若是设置了 O_APPEND
,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。不然,aio_offset
域就肯定了数据在要写入的文件中的偏移量。
咱们可使用 aio_suspend
函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其余超时操做。调用者提供了一个 aiocb
引用列表,其中任何一个完成都会致使 aio_suspend
返回。aio_suspend
的函数原型以下:
int aio_suspend( const struct aiocb *const cblist[], |
aio_suspend
的使用很是简单。咱们要提供一个 aiocb
引用列表。若是任何一个完成了,这个调用就会返回 0
。不然就会返回 -1
,说明发生了错误。请参看清单 3。
清单 3. 使用 aio_suspend 函数阻塞异步 I/O
|
注意,aio_suspend
的第二个参数是 cblist
中元素的个数,而不是 aiocb
引用的个数。cblist
中任何 NULL
元素都会被 aio_suspend
忽略。
若是为 aio_suspend
提供了超时,而超时状况的确发生了,那么它就会返回 -1
,errno
中会包含 EAGAIN
。
aio_cancel
函数容许咱们取消对某个文件描述符执行的一个或全部 I/O 请求。其原型以下:
int aio_cancel( int fd, struct aiocb *aiocbp ); |
要取消一个请求,咱们须要提供文件描述符和 aiocb
引用。若是这个请求被成功取消了,那么这个函数就会返回 AIO_CANCELED
。若是请求完成了,这个函数就会返回 AIO_NOTCANCELED
。
要取消对某个给定文件描述符的全部请求,咱们须要提供这个文件的描述符,以及一个对 aiocbp
的 NULL
引用。若是全部的请求都取消了,这个函数就会返回 AIO_CANCELED
;若是至少有一个请求没有被取消,那么这个函数就会返回 AIO_NOT_CANCELED
;若是没有一个请求能够被取消,那么这个函数就会返回 AIO_ALLDONE
。咱们而后可使用 aio_error
来验证每一个 AIO 请求。若是这个请求已经被取消了,那么 aio_error
就会返回 -1
,而且 errno
会被设置为 ECANCELED
。
最后,AIO 提供了一种方法使用 lio_listio
API 函数同时发起多个传输。这个函数很是重要,由于这意味着咱们能够在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操做。从性能的角度来看,这很是重要,所以值得咱们花点时间探索一下。lio_listio
API 函数的原型以下:
int lio_listio( int mode, struct aiocb *list[], int nent, |
mode
参数能够是 LIO_WAIT
或 LIO_NOWAIT
。LIO_WAIT
会阻塞这个调用,直到全部的 I/O 都完成为止。在操做进行排队以后,LIO_NOWAIT
就会返回。list
是一个 aiocb
引用的列表,最大元素的个数是由 nent
定义的。注意 list
的元素能够为 NULL
,lio_listio
会将其忽略。sigevent
引用定义了在全部 I/O 操做都完成时产生信号的方法。
对于 lio_listio
的请求与传统的 read
或 write
请求在必须指定的操做方面稍有不一样,如清单 4 所示。
|
对于读操做来讲,aio_lio_opcode
域的值为 LIO_READ
。对于写操做来讲,咱们要使用 LIO_WRITE
,不过 LIO_NOP
对于不执行操做来讲也是有效的。
![]() ![]() |
![]() |
如今咱们已经看过了可用的 AIO 函数,本节将深刻介绍对异步通知可使用的方法。咱们将经过信号和函数回调来探索异步函数的通知机制。
使用信号进行进程间通讯(IPC)是 UNIX 中的一种传统机制,AIO 也能够支持这种机制。在这种范例中,应用程序须要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序而后配置一个异步请求将在请求完成时产生一个信号。做为信号上下文的一部分,特定的 aiocb
请求被提供用来记录多个可能会出现的请求。清单 5 展现了这种通知方法。
|
在清单 5 中,咱们在 aio_completion_handler
函数中设置信号处理程序来捕获 SIGIO
信号。而后初始化 aio_sigevent
结构产生 SIGIO
信号来进行通知(这是经过 sigev_notify
中的 SIGEV_SIGNAL
定义来指定的)。当读操做完成时,信号处理程序就从该信号的 si_value
结构中提取出 aiocb
,并检查错误状态和返回状态来肯定 I/O 操做是否完成。
对于性能来讲,这个处理程序也是经过请求下一次异步传输而继续进行 I/O 操做的理想地方。采用这种方式,在一次数据传输完成时,咱们就能够当即开始下一次数据传输操做。
另一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。咱们在 sigevent
结构中设置了对 aiocb
的引用,从而能够唯一标识正在完成的特定请求。请参看清单 6。
|
在清单 6 中,在建立本身的 aiocb
请求以后,咱们使用 SIGEV_THREAD
请求了一个线程回调函数来做为通知方法。而后咱们将指定特定的通知处理程序,并将要传输的上下文加载处处理程序中(在这种状况中,是个对aiocb
请求本身的引用)。在这个处理程序中,咱们简单地引用到达的 sigval
指针并使用 AIO 函数来验证请求已经完成。
![]() ![]() |
![]() |
proc 文件系统包含了两个虚拟文件,它们能够用来对异步 I/O 的性能进行优化:
![]() ![]() |
![]() |
使用异步 I/O 能够帮助咱们构建 I/O 速度更快、效率更高的应用程序。若是咱们的应用程序能够对处理和 I/O 操做重叠进行,那么 AIO 就能够帮助咱们构建能够更高效地使用可用 CPU 资源的应用程序。尽管这种 I/O 模型与在大部分 Linux 应用程序中使用的传统阻塞模式都不一样,可是异步通知模型在概念上来讲却很是简单,能够简化咱们的设计。
学习
设备驱动的aio怎么实现呢?
Linux® 中最经常使用的输入/输出(I/O)模型是同步 I/O。在这个模型中,当请求发出以后,应用程序就会阻塞,直到请求知足为止。这是很好的一种解决方案,由于调用应用程序在等待 I/O 请求完成时不须要使用任何中央处理单元(CPU)。可是在某些状况中,I/O 请求可能须要与其余进程产生交叠。可移植操做系统接口(POSIX)异步 I/O(AIO)应用程序接口(API)就提供了这种功能。在本文中,咱们将对这个 API 概要进行介绍,并来了解一下如何使用它。
Linux 异步 I/O 是 Linux 内核中提供的一个至关新的加强。它是 2.6 版本内核的一个标准特性,可是咱们在 2.4 版本内核的补丁中也能够找到它。AIO 背后的基本思想是容许进程发起不少 I/O 操做,而不用阻塞或等待任何操做完成。稍后或在接收到 I/O 操做完成的通知时,进程就能够检索 I/O 操做的结果。
在深刻介绍 AIO API 以前,让咱们先来探索一下 Linux 上可使用的不一样 I/O 模型。这并非一个详尽的介绍,可是咱们将试图介绍最经常使用的一些模型来解释它们与异步 I/O 之间的区别。图 1 给出了同步和异步模型,以及阻塞和非阻塞的模型。
每一个 I/O 模型都有本身的使用模式,它们对于特定的应用程序都有本身的优势。本节将简要对其一一进行介绍。
![]() |
|
最经常使用的一个模型是同步阻塞 I/O 模型。在这个模型中,用户空间的应用程序执行一个系统调用,这会致使应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种再也不消费 CPU 而只是简单等待响应的状态,所以从处理的角度来看,这是很是有效的。
图 2 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为经常使用的一种模型。其行为很是容易理解,其用法对于典型的应用程序来讲都很是有效。在调用 read
系统调用时,应用程序会阻塞并对内核进行上下文切换。而后会触发读操做,当响应返回时(从咱们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。而后应用程序就会解除阻塞(read
调用返回)。
从应用程序的角度来讲,read
调用会延续很长时间。实际上,在内核执行读操做和其余工做时,应用程序的确会被阻塞。
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操做不会当即完成,read
操做可能会返回一个错误代码,说明这个命令不能当即知足(EAGAIN
或EWOULDBLOCK
),如图 3 所示。
非阻塞的实现是 I/O 命令可能并不会当即知足,须要应用程序调用许屡次来等待操做完成。这可能效率不高,由于在不少状况下,当内核执行这个命令时,应用程序必需要进行忙碌等待,直到数据可用为止,或者试图执行其余工做。正如图 3 所示的同样,这个方法能够引入 I/O 操做的延时,由于数据在内核中变为可用到用户调用 read
返回数据之间存在必定的间隔,这会致使总体数据吞吐量的下降。
另一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,而后使用阻塞 select
系统调用来肯定一个 I/O 描述符什么时候有操做。使 select
调用很是有趣的是它能够用来为多个描述符提供通知,而不只仅为一个描述符提供通知。对于每一个提示符来讲,咱们能够请求这个描述符能够写数据、有读数据可用以及是否发生错误的通知。
图 4. 异步阻塞 I/O 模型的典型流程 (select)
select
调用的主要问题是它的效率不是很是高。尽管这是异步通知使用的一种方便模型,可是对于高性能的 I/O 操做来讲不建议使用。
最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会当即返回,说明 read
请求已经成功发起了。在后台完成读操做时,应用程序而后会执行其余处理操做。当 read
的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成此次 I/O 处理过程。
在一个进程中为了执行多个 I/O 请求而对计算操做和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差别。当一个或多个 I/O 请求挂起时,CPU 能够执行其余任务;或者更为常见的是,在发起其余 I/O 的同时对已经完成的 I/O 进行操做。
下一节将深刻介绍这种模型,探索这种模型使用的 API,而后展现几个命令。
![]() ![]() |
![]() |
从前面 I/O 模型的分类中,咱们能够看出 AIO 的动机。这种阻塞模型须要在 I/O 操做开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操做。同步非阻塞模型容许处理和 I/O 操做重叠进行,可是这须要应用程序根据重现的规则来检查 I/O 操做的状态。这样就剩下异步非阻塞 I/O 了,它容许处理和 I/O 操做重叠进行,包括 I/O 操做完成的通知。
除了须要阻塞以外,select
函数所提供的功能(异步阻塞 I/O)与 AIO 相似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。
![]() ![]() |
![]() |
本节将探索 Linux 的异步 I/O 模型,从而帮助咱们理解如何在应用程序中使用这种技术。
在传统的 I/O 模型中,有一个使用唯一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,咱们发起了一次传输操做,当传输操做完成或发生错误时,系统调用就会返回。
![]() |
|
在异步非阻塞 I/O 中,咱们能够同时发起多个传输操做。这须要每一个传输操做都有唯一的上下文,这样咱们才能在它们完成时区分究竟是哪一个传输操做完成了。在 AIO 中,这是一个 aiocb
(AIO I/O Control Block)结构。这个结构包含了有关传输的全部信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb
结构就被用来唯一标识所完成的 I/O 操做。这个 API 的展现显示了如何使用它。
![]() ![]() |
![]() |
AIO 接口的 API 很是简单,可是它为数据传输提供了必需的功能,并给出了两个不一样的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。
API 函数 | 说明 |
---|---|
aio_read |
请求异步读操做 |
aio_error |
检查异步请求的状态 |
aio_return |
得到完成的异步请求的返回状态 |
aio_write |
请求异步写操做 |
aio_suspend |
挂起调用进程,直到一个或多个异步请求已经完成(或失败) |
aio_cancel |
取消异步 I/O 请求 |
lio_listio |
发起一系列 I/O 操做 |
每一个 API 函数都使用 aiocb
结构开始或检查。这个结构有不少元素,可是清单 1 仅仅给出了须要(或能够)使用的元素。
|
sigevent
结构告诉 AIO 在 I/O 操做完成时应该执行什么操做。咱们将在 AIO 的展现中对这个结构进行探索。如今咱们将展现各个 AIO 的 API 函数是如何工做的,以及咱们应该如何使用它们。
aio_read
函数请求对一个有效的文件描述符进行异步读操做。这个文件描述符能够表示一个文件、套接字甚至管道。aio_read
函数的原型以下:
int aio_read( struct aiocb *aiocbp ); |
aio_read
函数在请求进行排队以后会当即返回。若是执行成功,返回值就为 0;若是出现错误,返回值就为 -1,并设置 errno
的值。
要执行读操做,应用程序必须对 aiocb
结构进行初始化。下面这个简短的例子就展现了如何填充 aiocb
请求结构,并使用 aio_read
来执行异步读请求(如今暂时忽略通知)操做。它还展现了 aio_error
的用法,不过咱们将稍后再做解释。
|
在清单 2 中,在打开要从中读取数据的文件以后,咱们就清空了 aiocb
结构,而后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf
中。而后,咱们将 aio_nbytes
初始化成缓冲区的大小。并将aio_offset
设置成 0(该文件中的第一个偏移量)。咱们将 aio_fildes
设置为从中读取数据的文件描述符。在设置这些域以后,就调用 aio_read
请求进行读操做。咱们而后能够调用 aio_error
来肯定 aio_read
的状态。只要状态是 EINPROGRESS
,就一直忙碌等待,直到状态发生变化为止。如今,请求可能成功,也可能失败。
![]() |
|
注意使用这个 API 与标准的库函数从文件中读取内容是很是类似的。除了 aio_read
的一些异步特性以外,另一个区别是读操做偏移量的设置。在传统的 read
调用中,偏移量是在文件描述符上下文中进行维护的。对于每一个读操做来讲,偏移量都须要进行更新,这样后续的读操做才能对下一块数据进行寻址。对于异步 I/O 操做来讲这是不可能的,由于咱们能够同时执行不少读请求,所以必须为每一个特定的读请求都指定偏移量。
aio_error
函数被用来肯定请求的状态。其原型以下:
int aio_error( struct aiocb *aiocbp ); |
这个函数能够返回如下内容:
EINPROGRESS
,说明请求还没有完成ECANCELLED
,说明请求被应用程序取消了-1
,说明发生了错误,具体错误缘由能够查阅 errno
异步 I/O 和标准块 I/O 之间的另一个区别是咱们不能当即访问这个函数的返回状态,由于咱们并无阻塞在 read
调用上。在标准的 read
调用中,返回状态是在该函数返回时提供的。可是在异步 I/O 中,咱们要使用aio_return
函数。这个函数的原型以下:
ssize_t aio_return( struct aiocb *aiocbp ); |
只有在 aio_error
调用肯定请求已经完成(可能成功,也可能发生了错误)以后,才会调用这个函数。aio_return
的返回值就等价于同步状况中 read
或 write
系统调用的返回值(所传输的字节数,若是发生错误,返回值就为 -1
)。
aio_write
函数用来请求一个异步写操做。其函数原型以下:
int aio_write( struct aiocb *aiocbp ); |
aio_write
函数会当即返回,说明请求已经进行排队(成功时返回值为 0
,失败时返回值为 -1
,并相应地设置 errno
)。
这与 read
系统调用相似,可是有一点不同的行为须要注意。回想一下对于 read
调用来讲,要使用的偏移量是很是重要的。然而,对于 write
来讲,这个偏移量只有在没有设置 O_APPEND
选项的文件上下文中才会很是重要。若是设置了 O_APPEND
,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。不然,aio_offset
域就肯定了数据在要写入的文件中的偏移量。
咱们可使用 aio_suspend
函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其余超时操做。调用者提供了一个 aiocb
引用列表,其中任何一个完成都会致使 aio_suspend
返回。aio_suspend
的函数原型以下:
int aio_suspend( const struct aiocb *const cblist[], |
aio_suspend
的使用很是简单。咱们要提供一个 aiocb
引用列表。若是任何一个完成了,这个调用就会返回 0
。不然就会返回 -1
,说明发生了错误。请参看清单 3。
清单 3. 使用 aio_suspend 函数阻塞异步 I/O
|
注意,aio_suspend
的第二个参数是 cblist
中元素的个数,而不是 aiocb
引用的个数。cblist
中任何 NULL
元素都会被 aio_suspend
忽略。
若是为 aio_suspend
提供了超时,而超时状况的确发生了,那么它就会返回 -1
,errno
中会包含 EAGAIN
。
aio_cancel
函数容许咱们取消对某个文件描述符执行的一个或全部 I/O 请求。其原型以下:
int aio_cancel( int fd, struct aiocb *aiocbp ); |
要取消一个请求,咱们须要提供文件描述符和 aiocb
引用。若是这个请求被成功取消了,那么这个函数就会返回 AIO_CANCELED
。若是请求完成了,这个函数就会返回 AIO_NOTCANCELED
。
要取消对某个给定文件描述符的全部请求,咱们须要提供这个文件的描述符,以及一个对 aiocbp
的 NULL
引用。若是全部的请求都取消了,这个函数就会返回 AIO_CANCELED
;若是至少有一个请求没有被取消,那么这个函数就会返回 AIO_NOT_CANCELED
;若是没有一个请求能够被取消,那么这个函数就会返回 AIO_ALLDONE
。咱们而后可使用 aio_error
来验证每一个 AIO 请求。若是这个请求已经被取消了,那么 aio_error
就会返回 -1
,而且 errno
会被设置为 ECANCELED
。
最后,AIO 提供了一种方法使用 lio_listio
API 函数同时发起多个传输。这个函数很是重要,由于这意味着咱们能够在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操做。从性能的角度来看,这很是重要,所以值得咱们花点时间探索一下。lio_listio
API 函数的原型以下:
int lio_listio( int mode, struct aiocb *list[], int nent, |
mode
参数能够是 LIO_WAIT
或 LIO_NOWAIT
。LIO_WAIT
会阻塞这个调用,直到全部的 I/O 都完成为止。在操做进行排队以后,LIO_NOWAIT
就会返回。list
是一个 aiocb
引用的列表,最大元素的个数是由 nent
定义的。注意 list
的元素能够为 NULL
,lio_listio
会将其忽略。sigevent
引用定义了在全部 I/O 操做都完成时产生信号的方法。
对于 lio_listio
的请求与传统的 read
或 write
请求在必须指定的操做方面稍有不一样,如清单 4 所示。
|
对于读操做来讲,aio_lio_opcode
域的值为 LIO_READ
。对于写操做来讲,咱们要使用 LIO_WRITE
,不过 LIO_NOP
对于不执行操做来讲也是有效的。
![]() ![]() |
![]() |
如今咱们已经看过了可用的 AIO 函数,本节将深刻介绍对异步通知可使用的方法。咱们将经过信号和函数回调来探索异步函数的通知机制。
使用信号进行进程间通讯(IPC)是 UNIX 中的一种传统机制,AIO 也能够支持这种机制。在这种范例中,应用程序须要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序而后配置一个异步请求将在请求完成时产生一个信号。做为信号上下文的一部分,特定的 aiocb
请求被提供用来记录多个可能会出现的请求。清单 5 展现了这种通知方法。
|
在清单 5 中,咱们在 aio_completion_handler
函数中设置信号处理程序来捕获 SIGIO
信号。而后初始化 aio_sigevent
结构产生 SIGIO
信号来进行通知(这是经过 sigev_notify
中的 SIGEV_SIGNAL
定义来指定的)。当读操做完成时,信号处理程序就从该信号的 si_value
结构中提取出 aiocb
,并检查错误状态和返回状态来肯定 I/O 操做是否完成。
对于性能来讲,这个处理程序也是经过请求下一次异步传输而继续进行 I/O 操做的理想地方。采用这种方式,在一次数据传输完成时,咱们就能够当即开始下一次数据传输操做。
另一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。咱们在 sigevent
结构中设置了对 aiocb
的引用,从而能够唯一标识正在完成的特定请求。请参看清单 6。
|
在清单 6 中,在建立本身的 aiocb
请求以后,咱们使用 SIGEV_THREAD
请求了一个线程回调函数来做为通知方法。而后咱们将指定特定的通知处理程序,并将要传输的上下文加载处处理程序中(在这种状况中,是个对aiocb
请求本身的引用)。在这个处理程序中,咱们简单地引用到达的 sigval
指针并使用 AIO 函数来验证请求已经完成。
![]() ![]() |
![]() |
proc 文件系统包含了两个虚拟文件,它们能够用来对异步 I/O 的性能进行优化:
![]() ![]() |
![]() |
使用异步 I/O 能够帮助咱们构建 I/O 速度更快、效率更高的应用程序。若是咱们的应用程序能够对处理和 I/O 操做重叠进行,那么 AIO 就能够帮助咱们构建能够更高效地使用可用 CPU 资源的应用程序。尽管这种 I/O 模型与在大部分 Linux 应用程序中使用的传统阻塞模式都不一样,可是异步通知模型在概念上来讲却很是简单,能够简化咱们的设计。
学习
设备驱动的aio怎么实现呢?