一、What:异步IO是什么?

1. 一句话总结

允许进程发起很多I/O操作,而不用阻塞或等待任何操作完成

2. 详细说说

 一般来说,服务器端的I/O主要有两种情况:一是来自网络的I/O;二是对文件(设备)的I/O。Windows的异步I/O模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型,针对后者提供了AIO模型(关于是否把两者统一起来争论了很久)。
 
AIO的基本思想:
  允许进程发起很多I/O操作,而不用阻塞或等待任何操作完成,稍后或在接收到I/O操作完成通知时,进程可以检索I/O操作结果
  在异步非阻塞I/O中,我们可以同时发起多个传输操作,这需要每个传输操作都有唯一的上下文,这样我们才能在他们完成时区分到底是哪个传输操作完成了,这个工作可以通过aiocb结构体进行区分。  
  其中,struct aiocb主要包含以下字段:
   int               aio_fildes;        /* 要被读写的fd */
   void *            aio_buf;           /* 读写操作对应的内存buffer */
   __off64_t         aio_offset;        /* 读写操作对应的文件偏移 */
   size_t            aio_nbytes;        /* 需要读写的字节长度 */
   int               aio_reqprio;       /* 请求的优先级 */
   struct sigevent   aio_sigevent;      /* 异步事件,定义异步操作完成时的通知信号或回调函数 */
 

二、Why:为什么需要异步IO,它能干啥?

对于服务器程序,I/O是制约系统性能最关键的因素。对于需要处理大量连接的高并发服务器程序,异步I/O几乎是不二的选择。应用场景包括:

1)Daemon程序用于处理大量并发请求。

2)读写大文件

三、How:异步IO怎么玩

在传统的 I/O 模型中,有一个使用惟一句柄标识的 I/O通道。在 UNIX®中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。

在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO中,这是一个 aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O(称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O操作。这个 API的展示显示了如何使用它。

 

API

AIO接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。

 1. AIO接口 API

API 函数

说明

aio_read

请求异步读操作

aio_error

检查异步请求的状态

aio_return

获得完成的异步请求的返回状态

aio_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

...

};

sigevent 结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。

aio_read

aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下:

int aio_read( struct aiocb *aiocbp );

aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。

要执行读操作,应用程序必须对 aiocb 结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb 请求结构,并使用 aio_read 来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error 的用法,不过我们将稍后再作解释。

清单 2.使用 aio_read进行异步读操作的例子

#include <aio.h>

int main(int argc, char*argv[]) {

int fd, ret;

struct aiocb my_aiocb;

fd = open( "file.txt", O_RDONLY );

if (fd < 0)

perror("open");

bzero( (char *)&my_aiocb, sizeof(struct aiocb) );

my_aiocb.aio_buf = malloc(BUFSIZE+1);

if (!my_aiocb.aio_buf)

perror("malloc");

my_aiocb.aio_fildes = fd;

my_aiocb.aio_nbytes = BUFSIZE;

my_aiocb.aio_offset = 0;

ret = aio_read( &my_aiocb );

if (ret < 0)

perror("aio_read");

while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;

if ((ret = aio_return( &my_iocb )) > 0) {

}

else {

}

}

在清单 2 中,在打开要从中读取数据的文件之后,我们就清空了 aiocb 结构,然后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf 中。然后,我们将 aio_nbytes 初始化成缓冲区的大小。并将aio_offset 设置成 0(该文件中的第一个偏移量)。我们将 aio_fildes 设置为从中读取数据的文件描述符。在设置这些域之后,就调用 aio_read 请求进行读操作。我们然后可以调用 aio_error 来确定 aio_read的状态。只要状态是 EINPROGRESS,就一直忙碌等待,直到状态发生变化为止。现在,请求可能成功,也可能失败。

注意使用这个API 与标准的库函数从文件中读取内容是非常相似的。除了 aio_read 的一些异步特性之外,另外一个区别是读操作偏移量的设置。在传统的 read 调用中,偏移量是在文件描述符上下文中进行维护的。对于每个读操作来说,偏移量都需要进行更新,这样后续的读操作才能对下一块数据进行寻址。对于异步 I/O 操作来说这是不可能的,因为我们可以同时执行很多读请求,因此必须为每个特定的读请求都指定偏移量。(如:my_aiocb.aio_offset= 0;  )

aio_error

aio_error 函数被用来确定请求的状态。其原型如下:

int aio_error( struct aiocb *aiocbp );

这个函数可以返回以下内容:

EINPROGRESS,说明请求尚未完成

ECANCELLED,说明请求被应用程序取消了

-1,说明发生了错误,具体错误原因可以查阅 errno

aio_return

异步 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

aio_write 函数用来请求一个异步写操作。其函数原型如下:

int aio_write( struct aiocb *aiocbp );

aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,失败时返回值为 -1,并相应地设置 errno)。

这与 read 系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。

aio_suspend

我们可以使用 aio_suspend 函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其他超时操作。调用者提供了一个 aiocb 引用列表,其中任何一个完成都会导致 aio_suspend 返回。 aio_suspend 的函数原型如下:

int aio_suspend( const struct aiocb *const cblist[], int n,  const struct timespec *timeout );

aio_suspend 的使用非常简单。我们要提供一个 aiocb 引用列表。如果任何一个完成了,这个调用就会返回0。否则就会返回 -1,说明发生了错误。请参看清单 3。

清单 3.使用 aio_suspend函数阻塞异步 I/O

struct aioct *cblist[MAX_LIST];

bzero( (char *)cblist, sizeof(cblist) );

cblist[0] = &my_aiocb;

ret = aio_read( &my_aiocb );

ret = aio_suspend( cblist, MAX_LIST, NULL );

注意,aio_suspend 的第二个参数是 cblist 中元素的个数,而不是 aiocb 引用的个数。cblist 中任何NULL 元素都会被 aio_suspend 忽略。

如果为 aio_suspend 提供了超时,而超时情况的确发生了,那么它就会返回 -1errno 中会包含 EAGAIN

aio_cancel

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

lio_listio

最后,AIO 提供了一种方法使用 lio_listio API 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listio API 函数的原型如下:

int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

mode 参数可以是 LIO_WAIT 或 LIO_NOWAIT  LIO_WAIT 会阻塞这个调用,直到所有的 I/O 都完成为止。在操作进行排队之后,LIO_NOWAIT 就会返回。list 是一个 aiocb 引用的列表,最大元素的个数是由 nent定义的。注意 list 的元素可以为 NULLlio_listio 会将其忽略。sigevent 引用定义了在所有 I/O 操作都完成时产生信号的方法。

对于 lio_listio 的请求与传统的 read 或 write 请求在必须指定的操作方面稍有不同,如清单 4 所示。

清单 4.使用 lio_listio函数发起一系列请求

struct aiocb aiocb1, aiocb2;  struct aiocb *list[MAX_LIST];  ...  aiocb1.aio_fildes = fd;  aiocb1.aio_buf = malloc( BUFSIZE+1 );  aiocb1.aio_nbytes = BUFSIZE;  aiocb1.aio_offset = next_offset;  aiocb1.aio_lio_opcode = LIO_READ;  ...  bzero( (char *)list, sizeof(list) );  list[0] = &aiocb1;  list[1] = &aiocb2;  ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

对于读操作来说,aio_lio_opcode 域的值为 LIO_READ。对于写操作来说,我们要使用 LIO_WRITE,不过LIO_NOP 对于不执行操作来说也是有效的。

AIO通知

现在我们已经看过了可用的 AIO函数,本节将深入介绍对异步通知可以使用的方法。我们将通过信号和函数回调来探索异步函数的通知机制。

使用信号进行异步通知

使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,AIO 也可以支持这种机制。在这种范例中,应用程序需要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序然后配置一个异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,特定的 aiocb 请求被提供用来记录多个可能会出现的请求。清单 5 展示了这种通知方法。

清单 5.使用信号作为 AIO请求的通知

void setup_io( ... )  {    int fd;    struct sigaction sig_act;    struct aiocb my_aiocb;    ...       sigemptyset(&sig_act.sa_mask);    sig_act.sa_flags = SA_SIGINFO;    sig_act.sa_sigaction = aio_completion_handler;       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;       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;       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;       if (info->si_signo == SIGIO) {      req = (struct aiocb *)info->si_value.sival_ptr;           if (aio_error( req ) == 0) {                  ret = aio_return( req );      }   }    return;  }

在清单 5 中,我们在 aio_completion_handler 函数中设置信号处理程序来捕获 SIGIO 信号。然后初始化aio_sigevent 结构产生 SIGIO 信号来进行通知(这是通过 sigev_notify 中的 SIGEV_SIGNAL 定义来指定的)。当读操作完成时,信号处理程序就从该信号的 si_value 结构中提取出 aiocb,并检查错误状态和返回状态来确定 I/O 操作是否完成。

对于性能来说,这个处理程序也是通过请求下一次异步传输而继续进行 I/O 操作的理想地方。采用这种方式,在一次数据传输完成时,我们就可以立即开始下一次数据传输操作。

使用回调函数进行异步通知

另外一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。我们在 sigevent 结构中设置了对 aiocb 的引用,从而可以惟一标识正在完成的特定请求。请参看清单 6。

清单 6.对 AIO请求使用线程回调通知

void setup_io( ... )  {    int fd;    struct aiocb my_aiocb;    ...       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;       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;       if (aio_error( req ) == 0) {             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 文件是所允许的并发请求的最大个数。最大个数通常是 64KB,这对于大部分应用程序来说都已经足够了。

本节将探索 Linux 的异步 I/O 模型,从而帮助我们理解如何在应用程序中使用这种技术。

在传统的 I/O 模型中,有一个使用惟一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。

在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO中,这是一个 aiocb(AIO I/OControl Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O(称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O操作。这个 API的展示显示了如何使用它。

 

AIO API

AIO 接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。

表 1. AIO接口 API

API 函数

说明

aio_read

请求异步读操作

aio_error

检查异步请求的状态

aio_return

获得完成的异步请求的返回状态

aio_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      ...  };

sigevent 结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。

aio_read

aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下:

int aio_read( struct aiocb *aiocbp );

aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。

要执行读操作,应用程序必须对 aiocb 结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb 请求结构,并使用 aio_read 来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error 的用法,不过我们将稍后再作解释。

清单 2.使用aio_read进行异步读操作的例子

#include <aio.h>  ... int fd, ret;  struct aiocb my_aiocb;  fd = open( "file.txt", O_RDONLY );  if (fd < 0)      perror("open");  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );  my_aiocb.aio_buf = malloc(BUFSIZE+1);  if (!my_aiocb.aio_buf)      perror("malloc"); my_aiocb.aio_fildes = fd;  my_aiocb.aio_nbytes = BUFSIZE;  my_aiocb.aio_offset = 0;  ret = aio_read( &my_aiocb );  if (ret < 0)      perror("aio_read");  while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;  if ((ret = aio_return( &my_iocb )) > 0) {        } else {       }

在清单 2 中,在打开要从中读取数据的文件之后,我们就清空了 aiocb 结构,然后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf 中。然后,我们将 aio_nbytes 初始化成缓冲区的大小。并将aio_offset 设置成 0(该文件中的第一个偏移量)。我们将 aio_fildes 设置为从中读取数据的文件描述符。在设置这些域之后,就调用 aio_read 请求进行读操作。我们然后可以调用 aio_error 来确定aio_read 的状态。只要状态是 EINPROGRESS,就一直忙碌等待,直到状态发生变化为止。现在,请求可能成功,也可能失败。

 

使用 AIO接口编译程序

我们可以在 aio.h 头文件中找到函数原型和其他需要的符号。在编译使用这种接口的程序时,我们必须使用 POSIX 实时扩展库(librt)。

注意使用这个 API 与标准的库函数从文件中读取内容是非常相似的。除了 aio_read 的一些异步特性之外,另外一个区别是读操作偏移量的设置。在传统的 read 调用中,偏移量是在文件描述符上下文中进行维护的。对于每个读操作来说,偏移量都需要进行更新,这样后续的读操作才能对下一块数据进行寻址。对于异步 I/O 操作来说这是不可能的,因为我们可以同时执行很多读请求,因此必须为每个特定的读请求都指定偏移量。

aio_error

aio_error 函数被用来确定请求的状态。其原型如下:

int aio_error( struct aiocb *aiocbp );

这个函数可以返回以下内容:

EINPROGRESS,说明请求尚未完成

ECANCELLED,说明请求被应用程序取消了

-1,说明发生了错误,具体错误原因可以查阅 errno

aio_return

异步 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

aio_write 函数用来请求一个异步写操作。其函数原型如下:

int aio_write( struct aiocb *aiocbp );

aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,失败时返回值为 -1,并相应地设置 errno)。

这与 read 系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。

aio_suspend

我们可以使用 aio_suspend 函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其他超时操作。调用者提供了一个 aiocb 引用列表,其中任何一个完成都会导致 aio_suspend返回。 aio_suspend 的函数原型如下:

int aio_suspend( const struct aiocb *const cblist[],                  int n,                  const struct timespec *timeout );

aio_suspend 的使用非常简单。我们要提供一个 aiocb 引用列表。如果任何一个完成了,这个调用就会返回0。否则就会返回 -1,说明发生了错误。请参看清单 3。

清单 3.使用 aio_suspend函数阻塞异步 I/O

struct aioct *cblist[MAX_LIST];   bzero( (char *)cblist, sizeof(cblist) );  cblist[0] = &my_aiocb;  ret = aio_read( &my_aiocb );  ret = aio_suspend( cblist, MAX_LIST, NULL );

注意,aio_suspend 的第二个参数是 cblist 中元素的个数,而不是 aiocb 引用的个数。cblist 中任何NULL 元素都会被 aio_suspend 忽略。

如果为 aio_suspend 提供了超时,而超时情况的确发生了,那么它就会返回 -1errno 中会包含 EAGAIN

aio_cancel

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

lio_listio

最后,AIO 提供了一种方法使用 lio_listio API 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listio API 函数的原型如下:

int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

mode 参数可以是 LIO_WAIT 或 LIO_NOWAIT  LIO_WAIT 会阻塞这个调用,直到所有的 I/O 都完成为止。在操作进行排队之后,LIO_NOWAIT 就会返回。list 是一个 aiocb 引用的列表,最大元素的个数是由 nent定义的。注意 list 的元素可以为 NULLlio_listio 会将其忽略。sigevent 引用定义了在所有 I/O 操作都完成时产生信号的方法。

对于 lio_listio 的请求与传统的 read 或 write 请求在必须指定的操作方面稍有不同,如清单 4 所示。

清单 4.使用 lio_listio函数发起一系列请求

struct aiocb aiocb1, aiocb2;  struct aiocb *list[MAX_LIST];  ...  aiocb1.aio_fildes = fd;  aiocb1.aio_buf = malloc( BUFSIZE+1 );  aiocb1.aio_nbytes = BUFSIZE;  aiocb1.aio_offset = next_offset;  aiocb1.aio_lio_opcode = LIO_READ;  ...  bzero( (char *)list, sizeof(list) );  list[0] = &aiocb1;  list[1] = &aiocb2;  ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

对于读操作来说,aio_lio_opcode 域的值为 LIO_READ。对于写操作来说,我们要使用 LIO_WRITE,不过LIO_NOP 对于不执行操作来说也是有效的。

AIO 通知

现在我们已经看过了可用的 AIO 函数,本节将深入介绍对异步通知可以使用的方法。我们将通过信号和函数回调来探索异步函数的通知机制。

使用信号进行异步通知

使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,AIO 也可以支持这种机制。在这种范例中,应用程序需要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序然后配置一个异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,特定的 aiocb 请求被提供用来记录多个可能会出现的请求。清单 5 展示了这种通知方法。

清单 5.使用信号作为 AIO请求的通知

void setup_io( ... )  {    int fd;    struct sigaction sig_act;    struct aiocb my_aiocb;    ...       sigemptyset(&sig_act.sa_mask);    sig_act.sa_flags = SA_SIGINFO;    sig_act.sa_sigaction = aio_completion_handler;       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;       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;       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;       if (info->si_signo == SIGIO) {      req = (struct aiocb *)info->si_value.sival_ptr;           if (aio_error( req ) == 0) {                  ret = aio_return( req );      }   }    return;  }

在清单 5 中,我们在 aio_completion_handler 函数中设置信号处理程序来捕获 SIGIO 信号。然后初始化aio_sigevent 结构产生 SIGIO 信号来进行通知(这是通过 sigev_notify 中的 SIGEV_SIGNAL 定义来指定的)。当读操作完成时,信号处理程序就从该信号的 si_value 结构中提取出 aiocb,并检查错误状态和返回状态来确定 I/O 操作是否完成。

对于性能来说,这个处理程序也是通过请求下一次异步传输而继续进行 I/O 操作的理想地方。采用这种方式,在一次数据传输完成时,我们就可以立即开始下一次数据传输操作。

使用回调函数进行异步通知

另外一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。我们在 sigevent 结构中设置了对 aiocb 的引用,从而可以惟一标识正在完成的特定请求。请参看清单 6。

清单 6.对 AIO请求使用线程回调通知

void setup_io( ... )  {    int fd;    struct aiocb my_aiocb;    ...       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;       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;       if (aio_error( req ) == 0) {             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文件是所允许的并发请求的最大个数。最大个数通常是 64KB,这对于大部分应用程序来说都已经足够了。

本节将探索 Linux 的异步 I/O 模型,从而帮助我们理解如何在应用程序中使用这种技术。

在传统的 I/O 模型中,有一个使用惟一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,我们发起了一次传输操作,当传输操作完成或发生错误时,系统调用就会返回。

在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO中,这是一个 aiocb(AIO I/OControl Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O(称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O操作。这个 API的展示显示了如何使用它。

 

AIO API

AIO接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。

表 1. AIO接口 API

API 函数

说明

aio_read

请求异步读操作

aio_error

检查异步请求的状态

aio_return

获得完成的异步请求的返回状态

aio_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      ...  };

sigevent 结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。

aio_read

aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下:

int aio_read( struct aiocb *aiocbp );

aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。

要执行读操作,应用程序必须对 aiocb 结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb 请求结构,并使用 aio_read 来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error 的用法,不过我们将稍后再作解释。

清单 2.使用aio_read进行异步读操作的例子

#include <aio.h>  ... int fd, ret;  struct aiocb my_aiocb;  fd = open( "file.txt", O_RDONLY );  if (fd < 0)      perror("open");  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );  my_aiocb.aio_buf = malloc(BUFSIZE+1);  if (!my_aiocb.aio_buf)      perror("malloc"); my_aiocb.aio_fildes = fd;  my_aiocb.aio_nbytes = BUFSIZE;  my_aiocb.aio_offset = 0;  ret = aio_read( &my_aiocb );  if (ret < 0)      perror("aio_read");  while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;  if ((ret = aio_return( &my_iocb )) > 0) {        } else {       }

在清单 2 中,在打开要从中读取数据的文件之后,我们就清空了 aiocb 结构,然后分配一个数据缓冲区。并将对这个数据缓冲区的引用放到 aio_buf 中。然后,我们将 aio_nbytes 初始化成缓冲区的大小。并将aio_offset 设置成 0(该文件中的第一个偏移量)。我们将 aio_fildes 设置为从中读取数据的文件描述符。在设置这些域之后,就调用 aio_read 请求进行读操作。我们然后可以调用 aio_error 来确定 aio_read的状态。只要状态是 EINPROGRESS,就一直忙碌等待,直到状态发生变化为止。现在,请求可能成功,也可能失败。

 

使用 AIO接口编译程序

我们可以在 aio.h 头文件中找到函数原型和其他需要的符号。在编译使用这种接口的程序时,我们必须使用 POSIX 实时扩展库(librt)。

注意使用这个 API 与标准的库函数从文件中读取内容是非常相似的。除了 aio_read 的一些异步特性之外,另外一个区别是读操作偏移量的设置。在传统的 read 调用中,偏移量是在文件描述符上下文中进行维护的。对于每个读操作来说,偏移量都需要进行更新,这样后续的读操作才能对下一块数据进行寻址。对于异步 I/O 操作来说这是不可能的,因为我们可以同时执行很多读请求,因此必须为每个特定的读请求都指定偏移量。

aio_error

aio_error 函数被用来确定请求的状态。其原型如下:

int aio_error( struct aiocb *aiocbp );

这个函数可以返回以下内容:

EINPROGRESS,说明请求尚未完成

ECANCELLED,说明请求被应用程序取消了

-1,说明发生了错误,具体错误原因可以查阅 errno

aio_return

异步 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

aio_write 函数用来请求一个异步写操作。其函数原型如下:

int aio_write( struct aiocb *aiocbp );

aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,失败时返回值为 -1,并相应地设置 errno)。

这与 read 系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。

aio_suspend

我们可以使用 aio_suspend 函数来挂起(或阻塞)调用进程,直到异步请求完成为止,此时会产生一个信号,或者发生其他超时操作。调用者提供了一个 aiocb 引用列表,其中任何一个完成都会导致 aio_suspend 返回。 aio_suspend 的函数原型如下:

int aio_suspend( const struct aiocb *const cblist[],                  int n,                  const struct timespec *timeout );

aio_suspend 的使用非常简单。我们要提供一个 aiocb 引用列表。如果任何一个完成了,这个调用就会返回0。否则就会返回 -1,说明发生了错误。请参看清单 3。

清单 3.使用aio_suspend函数阻塞异步 I/O

struct aioct *cblist[MAX_LIST];   bzero( (char *)cblist, sizeof(cblist) );  cblist[0] = &my_aiocb;  ret = aio_read( &my_aiocb );  ret = aio_suspend( cblist, MAX_LIST, NULL );

注意,aio_suspend 的第二个参数是 cblist 中元素的个数,而不是 aiocb 引用的个数。cblist 中任何NULL 元素都会被 aio_suspend 忽略。

如果为 aio_suspend 提供了超时,而超时情况的确发生了,那么它就会返回 -1errno 中会包含 EAGAIN

aio_cancel

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

lio_listio

最后,AIO 提供了一种方法使用 lio_listio API 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要,因此值得我们花点时间探索一下。lio_listio API 函数的原型如下:

int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

mode 参数可以是 LIO_WAIT 或 LIO_NOWAIT  LIO_WAIT 会阻塞这个调用,直到所有的I/O 都完成为止。在操作进行排队之后,LIO_NOWAIT 就会返回。list 是一个 aiocb 引用的列表,最大元素的个数是由 nent定义的。注意 list 的元素可以为 NULLlio_listio 会将其忽略。sigevent 引用定义了在所有 I/O 操作都完成时产生信号的方法。

对于 lio_listio 的请求与传统的 read 或 write 请求在必须指定的操作方面稍有不同,如清单 4 所示。

清单 4.使用 lio_listio函数发起一系列请求

struct aiocb aiocb1, aiocb2;  struct aiocb *list[MAX_LIST];  ...  aiocb1.aio_fildes = fd;  aiocb1.aio_buf = malloc( BUFSIZE+1 );  aiocb1.aio_nbytes = BUFSIZE;  aiocb1.aio_offset = next_offset;  aiocb1.aio_lio_opcode = LIO_READ;  ...  bzero( (char *)list, sizeof(list) );  list[0] = &aiocb1;  list[1] = &aiocb2;  ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

对于读操作来说,aio_lio_opcode 域的值为 LIO_READ。对于写操作来说,我们要使用 LIO_WRITE,不过LIO_NOP 对于不执行操作来说也是有效的。

AIO 通知

现在我们已经看过了可用的 AIO 函数,本节将深入介绍对异步通知可以使用的方法。我们将通过信号和函数回调来探索异步函数的通知机制。

使用信号进行异步通知

使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,AIO 也可以支持这种机制。在这种范例中,应用程序需要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序然后配置一个异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,特定的 aiocb 请求被提供用来记录多个可能会出现的请求。清单 5 展示了这种通知方法。

清单 5.使用信号作为 AIO请求的通知

void setup_io( ... )  {    int fd;    struct sigaction sig_act;    struct aiocb my_aiocb;    ...       sigemptyset(&sig_act.sa_mask);    sig_act.sa_flags = SA_SIGINFO;    sig_act.sa_sigaction = aio_completion_handler;       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;       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;       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;       if (info->si_signo == SIGIO) {      req = (struct aiocb *)info->si_value.sival_ptr;           if (aio_error( req ) == 0) {                  ret = aio_return( req );      }   }    return;  }

在清单 5 中,我们在 aio_completion_handler 函数中设置信号处理程序来捕获 SIGIO 信号。然后初始化aio_sigevent 结构产生 SIGIO 信号来进行通知(这是通过 sigev_notify 中的 SIGEV_SIGNAL 定义来指定的)。当读操作完成时,信号处理程序就从该信号的 si_value 结构中提取出 aiocb,并检查错误状态和返回状态来确定I/O 操作是否完成。

对于性能来说,这个处理程序也是通过请求下一次异步传输而继续进行 I/O 操作的理想地方。采用这种方式,在一次数据传输完成时,我们就可以立即开始下一次数据传输操作。

使用回调函数进行异步通知

另外一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数来实现通知功能。我们在 sigevent 结构中设置了对 aiocb 的引用,从而可以惟一标识正在完成的特定请求。请参看清单 6。

清单 6.对 AIO请求使用线程回调通知

void setup_io( ... )  {    int fd;    struct aiocb my_aiocb;    ...       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;       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;       if (aio_error( req ) == 0) {             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 文件是所允许的并发请求的最大个数。最大个数通常是 64KB,这对于大部分应用程序来说都已经足够了。

参考

http://blog.sina.com.cn/s/blog_3e3fcadd0100grgk.html

慢慢聊Linux AIO的更多相关文章

  1. linux aio

    前几天nginx的0.8.x正式成为stable,然后看了下代码,发现0.8加入了linux native aio的支持,我们知道在linux下有两种aio,一种是glibc实现的aio,这个比较烂, ...

  2. Linux内核源代码解析之——我与神童聊Linux内核

    本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/11619609 我的朋友里,至少有2.5个神童. 有的 ...

  3. 浅聊Linux的五种IO模型

    在日常 Coding 中,多多少少都会接触到网络 IO,就会想要深入了解一下.看了很多文章,总是云里雾里的感觉,直到读了<UNIX网络编程 卷1:套接字联网API>中的介绍后,才豁然开朗. ...

  4. 深入理解Java AIO(三)—— Linux中的AIO实现

    我们调用的Java AIO底层也是要调用OS的AIO实现,而OS主要也就Windows和Linux这两大类,当然还有Solaris和mac这些小众的. 在 Windows 操作系统中,提供了一个叫做 ...

  5. 浅析Linux Native AIO的实现

    前段时间在自研的基于iSCSI的SAN 上跑mysql,CPU的iowait很大,后面改用Native AIO,有了非常大的改观.这里简单总结一下Native AIO的实现.对于以IO为最大瓶颈的数据 ...

  6. Linux从头学02:x86中内存【段寻址】方式的来龙去脉

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  7. 第六章第一个linux个程序:统计单词个数

    第六章第一个linux个程序:统计单词个数 从本章就开始激动人心的时刻——实战,去慢慢揭开linux神秘的面纱.本章的实例是统计一片文章或者一段文字中的单词个数.  第 1 步:建立 Linu x 驱 ...

  8. 专访Linux嵌入式开发韦东山操作系统图书作者--转

    CSDN学院讲师韦东山:悦己之作,方能悦人 发表于2015-04-28 08:09| 6669次阅读| 来源CSDN| 24 条评论| 作者夏梦竹 专访Linux嵌入式开发韦东山操作系统图书作者 摘要 ...

  9. Linux驱动开发之字符设备模板

    /***************************** ** 驱动程序模板* 版本:V1* 使用方法(末行模式下):* :%s/xxx/"你的驱动名称"/g********* ...

随机推荐

  1. unity替换mesh测试

    直接替换SkinnedMeshRender的Mesh,实现所谓断肢效果(不过最近发现,绑定多mesh似乎更好实现这样的效果.有时间准备写一篇): 只要不改变两个Mesh原始文件的层级,就不会出现权重的 ...

  2. java配置自动任务,定期执行代码

    任务调用类: package business.tools.service; import java.util.ArrayList; import java.util.Calendar; import ...

  3. 窗体移动API

    //窗体移动API [DllImport("user32.dll")] public static extern bool ReleaseCapture(); [DllImport ...

  4. CentOS6 启动流程图文解剖

    我们在使用Linux操作系统的时候,我们只需按下电源键,等待,然后输入账户和密码就可以使用Linux操作系统了.那么在按下电源到输入账号和密码之前,操作系统都做了些什么?下面就来讲述在这段时间发生的动 ...

  5. django学习の模型

    orm:对象数据库和模型的映射.如果想以简单的方式去操作数据库,例如用类的方式去操作,就像 p = Person.get(id = 1),那么就必须使得代码和数据库的结构具有映射关系,实现这种关系,你 ...

  6. 两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单。

    package C; public class Bisai { public static void main(String[] args) { String a="xyz",b= ...

  7. CF 321B Ciel and Duel(费用流)

    题目链接:http://codeforces.com/problemset/problem/321/B 题意:两个人,分别有n.m张牌.每张牌有两个属性类型和能力,类型为攻击或者防守.B的m张牌的属性 ...

  8. 部署PDA程序的时候存储不足的解决办法

    通常Windows Mobile的存储空间分为程序内存和存储内存,默认都比较小,当程序比较大的时候可能无法正常部署到设备上面,针对此问题可采用如下办法解决:1.通过VS修改,工具--选项--设备--选 ...

  9. ubuntu下卸载vmware

    直接crl+alt+t打开一个terminal,然后输入sudo vmware-installer --uninstall-product vmware-workstation即可卸载!操作如下图: ...

  10. ubuntu1604安装体验

    昨天安装了ubuntu 16 安装成了双系统,这样的速度才能接受. 各种软件支持的都还算不错.除了启动时候原始的气息,启动之后挺稳定的. 最让人开心的是新的unity的设置更加丰富了,可以自动隐藏,更 ...