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

转载的:

aio的确能够提高file磁盘的文件读写的性能.更加详细的blog在:

http://www.ibm.com/developerworks/cn/linux/l-async/


根据同步和异步,阻塞和非阻塞,可以分为四种linux上的I/O模型.

1)同步阻塞I/O

2)同步非阻塞I/O

3)异步阻塞I/O

4)异步非阻塞I/O


【同步IO与异步IO】
1,其实同步方式很好理解, 例如你调用一个function,   当这个function执行完后, 这个方法实现的功能已经完成。这里往往会跟阻塞混淆,其实是因为你采用了同步方式执行代码, 才阻塞了你的thread或者process. 而不是因为阻塞,才叫同步。
异步方式就不提供这种保证, 当你用异步方式调用一个function的时候,这个方法会马上返回,事实上多数情况
下 这种function call只是向某个任务执行体提交一个任务而已。 而你的主thread可以继续执行其他的事情, 不必等待(阻塞), 而当那个任务执行体执行完你提交的这个任务后,它会通过某种方法callback给你的thread, 告诉你,你的这个任务已经完成。

1.read/write:
对于read操作来说,它是同步的。也就是 说只有当申请读的内容真正存放到buffer中后(user mode的buffer),read函数才返回。在此期间,它会因为等待IO完成而被阻塞。研究过源码的朋友应该知道,这个阻塞发生在两个地方:一是 read操作刚刚发起,kernel会检查其它进程的need_sched标志,如果有其它进程需要调度,主动阻塞read操作,这个时候其实I/O操作 还没有发起。二是I/O操作发起后,调用lock_page对已经加锁的页面申请锁,这时由于页面已经加锁,所以加锁操作被阻塞,从而read操作阻塞, 直到I/O操作完成后页面被解锁,read操作继续执行。所以说read是同步的,其阻塞的原因如上。
对于write操作通常是异步的。因为 linux中有page cache机制,所有的写操作实际上是把文件对应的page cache中的相应页设置为dirty,然后write操作返回。这个时候对文件的修改并没有真正写到磁盘上去。所以说write是异步的,这种方式下 write不会被阻塞。如果设置了O_SYNC标志的文件,write操作再返回前会把修改的页flush到磁盘上去,发起真正的I/O请求,这种模式下 会阻塞。
2.Direct I/O
linux支持Direct I/O, 以O_DIRCET标志打开的文件,在read和write的时候会绕开page cache,直接使用user mode的buffer做为I/O操作的buffer。这种情况下的read和write直接发起I/O操作,都是同步的,并会被阻塞。
3.AIO
是 2.6 版本内核的一个标准特性,但是我们在 2.4 版本内核的补丁中也可以找到它。这种方式实现很简单,就是一个父进程clone出子进程帮其 做I/O,完成后通过signal或者callback通知父进程。用户看来是AIO,实质还是SIO。linux kernel中AIO的实现概念类似,只不过是以一组kernel thread去做的。这些kernel thread做I/O的时候使用的是和Direct I/O相同的方式。
4.mmap()
抛开它中讲vm_area和page cache映射在一起的机制不说。真正发起I/O时和read、write使用的是相同的机制,同步阻塞。



Linux的I/O机制经历了一下几个阶段的演进:
1. 同步阻塞I/O: 用户进程进行I/O操作,一直阻塞到I/O操作完成为止。
2. 同步非阻塞I/O: 用户程序可以通过设置文件描述符的属性O_NONBLOCK,I/O操作可以立即返回,但是并不保证I/O操作成功。
3. 异步事件阻塞I/O: 用户进程可以对I/O事件进行阻塞,但是I/O操作并不阻塞。通过select/poll/epoll等函数调用来达到此目的。
4. 异步时间非阻塞I/O: 也叫做异步I/O(AIO),用户程序可以通过向内核发出I/O请求命令,不用等带I/O事件真正发生,可以继续做
           另外的事情,等I/O操作完成,内核会通过函数回调或者信号机制通知用户进程。这样很大程度提高了系统吞吐量。
           
下面就AIO做详细介绍:
要使用aio的功能,需要include头文件aio.h,在编译连接的时候需要加入POSIX实时扩展库rt.下面就aio库的使用做介绍。
1. AIO整个过程所使用的数据存放在一个结构体中,struct aiocb,aio control block.看看头文件中的定义:

/* Asynchronous I/O control block.  */
struct aiocb
{
  int aio_fildes;               /* File desriptor.  */ 需要在哪个文件描述符上进行I/O
  int aio_lio_opcode;           /* Operation to be performed.  */ 这个是针对批量I/O的情况有效,读写操作类型
  int aio_reqprio;              /* Request priority offset.  */ 请求优先级(If  _POSIX_PRIORITIZED_IO  is defined, and this file supports it, then the
                                       asynchronous operation is submitted at a priority equal to that of the
                                       calling process minus aiocbp->aio_reqprio.)
  volatile void *aio_buf;       /* Location of buffer.  */ 具体内容,数据缓存
  size_t aio_nbytes;            /* Length of transfer.  */ 数据缓存的长度
  struct sigevent aio_sigevent; /* Signal number and value.  */ 用于异步I/O完成后的通知。

 内部实现使用的数据成员。
  /* Internal members.  */
  struct aiocb *__next_prio;
  int __abs_prio;
  int __policy;
  int __error_code;
  __ssize_t __return_value;

#ifndef __USE_FILE_OFFSET64
  __off_t aio_offset;           /* File offset.  */
  char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
  __off64_t aio_offset;         /* File offset.  */ 文件读写偏移
#endif
  char __unused[32];
};

2. int aio_read(struct aiocb *aiocbp);
异步读操作,向内核发出读的命令,传入的参数是一个aiocb的结构,比如
struct aiocb myaiocb;
memset(&aiocb , 0x00 , sizeof(myaiocb));
myaiocb.aio_fildes = fd;
myaiocb.aio_buf = new char[1024];
myaiocb.aio_nbytes = 1024;
if (aio_read(&myaiocb) != 0)
{
  printf("aio_read error:%s/n" , strerror(errno));
  return false;
}

3. int aio_write(struct aiocb *aiocbp);
异步写操作,向内核发出写的命令,传入的参数仍然是一个aiocb的结构,当文件描述符的O_APPEND
标志位设置后,异步写操作总是将数据添加到文件末尾。如果没有设置,则添加到aio_offset指定的
地方,比如:
struct aiocb myaiocb;
memset(&aiocb , 0x00 , sizeof(myaiocb));
myaiocb.aio_fildes = fd;
myaiocb.aio_buf = new char[1024];
myaiocb.aio_nbytes = 1024;
myaiocb.aio_offset = 0;
if (aio_write(&myaiocb) != 0)
{
  printf("aio_read error:%s/n" , strerror(errno));
  return false;
}

4. int aio_error(const struct aiocb *aiocbp);
如果该函数返回0,表示aiocbp指定的异步I/O操作请求完成。
如果该函数返回EINPROGRESS,表示aiocbp指定的异步I/O操作请求正在处理中。
如果该函数返回ECANCELED,表示aiocbp指定的异步I/O操作请求已经取消。
如果该函数返回-1,表示发生错误,检查errno。

5. ssize_t aio_return(struct aiocb *aiocbp);
这个函数的返回值相当于同步I/O中,read/write的返回值。只有在aio_error调用后
才能被调用。

6. int aio_cancel(int fd, struct aiocb *aiocbp);
取消在文件描述符fd上的aiocbp所指定的异步I/O请求。
如果该函数返回AIO_CANCELED,表示操作成功。
如果该函数返回AIO_NOTCANCELED,表示取消操作不成功,使用aio_error检查一下状态。
如果返回-1,表示发生错误,检查errno.

7. int lio_listio(int mode, struct aiocb *restrict const list[restrict],
             int nent, struct sigevent *restrict sig);
使用该函数,在很大程度上可以提高系统的性能,因为再一次I/O过程中,OS需要进行
用户态和内核态的切换,如果我们将更多的I/O操作都放在一次用户太和内核太的切换中,
减少切换次数,换句话说在内核尽量做更多的事情。这样可以提高系统的性能。

用户程序提供一个struct aiocb的数组,每个元素表示一次AIO的请求操作。需要设置struct aiocb
中的aio_lio_opcode数据成员的值,有LIO_READ,LIO_WRITE和LIO_NOP。
nent表示数组中元素的个数。最后一个参数是对AIO操作完成后的通知机制的设置。

8. 设置AIO的通知机制,有两种通知机制:信号和回调
(1).信号机制
 首先我们应该捕获SIGIO信号,对其作处理:
 struct sigaction sig_act;
 sigempty(&sig_act.sa_mask);
 sig_act.sa_flags = SA_SIGINFO;
  sig_act.sa_sigaction = aio_handler;
 
  struct aiocb myaiocb;
  bzero( (char *)&myaiocb, sizeof(struct aiocb) );
  myaiocb.aio_fildes = fd;
  myaiocb.aio_buf = malloc(BUF_SIZE+1);
  myaiocb.aio_nbytes = BUF_SIZE;
  myaiocb.aio_offset = next_offset;
 
  myaiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
  myaiocb.aio_sigevent.sigev_signo = SIGIO;
  myaiocb.aio_sigevent.sigev_value.sival_ptr = &myaiocb;

  ret = sigaction( SIGIO, &sig_act, NULL );

 信号处理函数的实现:
 void aio_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;
}

(2). 回调机制
需要设置:
myaiocb.aio_sigevent.sigev_notify = SIGEV_THREAD
my_aiocb.aio_sigevent.notify_function = aio_handler;

回调函数的原型:
typedef void (* FUNC_CALLBACK)(sigval_t sigval);

AIO机制为服务器端高并发应用程序提供了一种性能优化的手段。加大了系统吞吐量。

你可能感兴趣的:(thread,struct,function,callback,asynchronous,Signal)