Overlapped I/O模型深入分析

http://www.cppblog.com/Lee7/archive/2008/01/07/40650.html

Overlapped I/O简述:

Overlapped I/O也称Asynchronous  I/O,异步I/O模型。
异步I/O和同步I/O不同,同步I/O时,程序被挂起,一直到I/O处理完,程序才能获得控制。
异步I/O,调用一个函数告诉 OS,进行I/O操作,不等I/O结束就立即返回,继续程序执行,
操作系统完成I/O之后,通知消息给你。
Overlapped I/O只是一种模型,它可以由内核对象(hand),事件内核对象(hEvent), 异步过程调用(apcs) 和完成端口(I/O completion)实现。

Overlapped I/O的设计的目的:

取代多线程功能,(多线程存在同步机制,错误处理,在成千上万个线程I/O中,线程上下文切换是十分消耗CPU资源的)。
Overlapped I/O模型是OS为你传递数据,完成上下文切换,在处理完之后通知你。
由程序中的处理,变为OS的处理。内部也是用线程处理的。

Overlapped数据结构:

typedef struct _OVERLAPPED
{
  // 通常被保留,当GetOverlappedResult()传回False
  // 并且GatLastError()并非传回ERROR_IO_PENDINO时,该状态置为系统定的状态。
  DWORD Internal;
  // 通常被保留,当GetOverlappedResult()传回False时,为被传输数据的长度。
  DWORD InternalHigh;
  // 指定文件的位置,从该位置传送数据,文件位置是相对文件开始处的字节偏移量。
  // 调用 ReadFile或WriteFile函数之前调用进程设置这个成员,
  // 读写命名管道及通信设备时调用进程忽略这个成员;
  DWORD Offset;
  // 指定开始传送数据的字节偏移量的高位字,
  // 读写命名管道及通信设备时调用进程忽略这个成员;
  DWORD OffsetHigh;
  // 标识事件,数据传送完成时把它设为信号状态,
  // 调用ReadFile, WriteFile, ConnectNamedPipe TransactNamedPipe函数前,调用进程设置这个成员. 
  HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

Overlapped数据结构功能

1. 标识每个正在overlapped 的操作。
2. 程序和系统之间提供了共享区域。参数可以在区域内双向传递。

OVERLAPPED和数据缓冲区释放问题:
在请求时,不能释放,只有在I/O请求完成之后,才可以释放。
如果发出多个overlapped请求,每个overlapped读写操作,都必须包含文件位置(socket),
另外,如果有多个磁盘,I/O执行次序无法保证。(每个overlapped都是独立的请求操作)。

内核对象(hand)实现: 

例子:用overlapped模型读一个磁盘文件内容。

1.把设备句柄看作同步对象,ReadFile将设备句柄设为无信号。
ReadFile 异步I/O字节位置必须在OVERLAPPED结构中指定。

2.完成I/O,设置信息状态。为有信号。

3.WaitForSingleObject或WaitForMultipleObject判断
或者异步设备调用GetOverLappedResult函数。

内核对象(hand)实现的问题:

不能区分那一个
overlapped操作,对同一个文件handle,系统有多个异步操作时
(一边读文件头,一边写文件尾, 有一个完成,就会有信号,不能区分是那种操作。),
为每个进行中的overlapped调用GetOverlappedResult是不好的作法。

int main( )
{
  BOOL rc;
  HANDLE hFile;
  DWORD numread;
  OVERLAPPED overlap;
  char buf[ READ_SIZE ];
  char szPath[ MAX_PATH ];
  CheckOsVersion( );

  GetWindowsDirectory( szPath, sizeof( szPath ) );
  strcat( szPath, "\\WINHLP32.EXE" );
  hFile = CreateFile( szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
  if ( hFile == INVALID_HANDLE_VALUE )
  {
    printf( "Could not open %s\n", szPath );
    return -1;
  }

  memset( &overlap, 0, sizeof( overlap ) );
  overlap.Offset = 1500;

  rc = ReadFile( hFile, buf, READ_SIZE, &numread, &overlap ); // ReadFile将设备句柄设为无信号
  printf( "Issued read request\n" );
  if ( rc )
  {
    printf( "Request was returned immediately\n" );
  }
  else
  {
    if ( GetLastError( ) == ERROR_IO_PENDING )
    {
      printf( "Request queued, waiting\n" );
      WaitForSingleObject( hFile, INFINITE ); // 完成I/O,设置信息状态。为有信号。
      printf( "Request completed.\n" );
      rc = GetOverlappedResult( hFile, &overlap, &numread, FALSE );
      printf( "Result was %d\n", rc );
    }
    else
    {
      printf( "Error reading file\n" );
    }
  }
  CloseHandle( hFile );
  return EXIT_SUCCESS;
}

事件内核对象(hEvent)实现方案

Overlapped成员hEven标识事件内核对象。
CreateEvent,为每个请求创建一个事件,初始化每个请求的hEvent成员(对同一文件多个读写请求,每个操作绑定一个event对象)。
调用WaitForMultipleObject来等等其中一个(或全部)完成。

另外Event对象必须是手动重置。使用自动重置(在等待event之前设置,WaitForSingleObject()和 WaitForMultipleObjects()函数永不返回)。

自动重置事件    
WaitForSingleObject()和 WaitForMultipleObjects()会等待事件到信号状态,随后又自动将其重置为非信号状态,
这样保证了等待此事件的线程中只有一个会被唤醒。

手动重置事件
需要用户调用ResetEvent()才会重置事件。可能有若干个线程在等待同一事件, 这样当事件变为信号状态时,所有等待线程都可以运行了。
SetEvent()函数用来把事件对象设置成信号状态,ResetEvent()把事件对象重置成非信号状态,两者均需事件对象句柄作参数。

事件内核对象(hEvent)的问题:
事件内核对象在使用WaitForMultipleObjects时,只能等待64个对象。
需要另建两个数据组,并gOverlapped[nIndex].hEvent = ghEvents[nIndex]绑定起来。

 

HANDLE WINAPI CreateFile(
  __in      LPCTSTR lpFileName,
  __in      DWORD dwDesiredAccess,
  __in      DWORD dwShareMode,
  __in_opt  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in      DWORD dwCreationDisposition,
  __in      DWORD dwFlagsAndAttributes,
  __in_opt  HANDLE hTemplateFile
);

 

HANDLE CreateEvent(   
  LPSECURITY_ATTRIBUTES lpEventAttributes,   // 安全属性   
  BOOL bManualReset,   // 复位方式   
  BOOL bInitialState,   // 初始状态   
  LPCTSTR lpName   // 对象名称   
);  

 

DWORD WINAPI WaitForSingleObject(
  __in  HANDLE hHandle,
  __in  DWORD dwMilliseconds
);
BOOL WINAPI GetOverlappedResult(  
    HANDLE hFile,  
    LPOVERLAPPED lpOverlapped,  
    LPDWORD lpNumberOfBytesTransferred,  
    BOOL bWait  
) ;
int main( )
{
  BOOL rc;
  HANDLE hFile;
  DWORD numread;
  OVERLAPPED overlap;
  char buf[ READ_SIZE ];
  char szPath[ MAX_PATH ];
  CheckOsVersion( );

  GetWindowsDirectory( szPath, sizeof( szPath ) );
  strcat( szPath, "\\WINHLP32.EXE" );
  hFile = CreateFile( szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
  if ( hFile == INVALID_HANDLE_VALUE )
  {
    printf( "Could not open %s\n", szPath );
    return -1;
  }

  memset( &overlap, 0, sizeof( overlap ) );
  overlap.Offset = 1500;
// 创建一个有名的,不能被继承的,手动复原,初始状态是无信号状态的事件对象 for overlapped I/O
overlap.hEvent = CreateEvent( NULL, TRUE, FALSE, 0 );

  rc = ReadFile( hFile, buf, READ_SIZE, &numread, &overlap );
  printf( "Issued read request\n" );
  if ( rc )
  {
    printf( "Request was returned immediately\n" );
  }
  else
  {
    if ( GetLastError( ) == ERROR_IO_PENDING )
    {
      printf( "Request queued, waiting\n" );
      WaitForSingleObject( overlap.hEvent, INFINITE ); 
printf(
"Request completed.\n" ); rc = GetOverlappedResult( hFile, &overlap, &numread, FALSE ); printf( "Result was %d\n", rc ); } else { printf( "Error reading file\n" ); } }

 CloseHandle( overlap.hEvent );
 CloseHandle( hFile ); 

 return EXIT_SUCCESS;
}

异步过程调用(apcs)

异步过程调用,callback回调函数,在一个Overlapped I/O完成之后,系统调用该回调函数。
OS在有信号状态下(设备句柄),才会调用回调函数(可能有很多APCS等待处理了),
传给它完成I/O请求的错误码,传输字节数和Overlapped结构的地址。

异步过程调用(apcs)问题:
只有发overlapped请求的线程才可以提供callback函数(需要一个特定的线程为一个特定的I/O请求服务)。


五个函数可以设置信号状态:

1.SleepEx
2.WaitForSingleObjectEx
3.WaitForMultipleObjectEx
4.SingalObjectAndWait
5.MsgWaitForMultipleObjectsEx

Main函数调用WaitForSingleObjectEx, APCS被处理,调用回调函数FileIOCompletionRoutine

VOID WINAPI FileIOCompletionRoutine( 
DWORD dwErrorCode, // completion code DWORD dwNumberOfBytesTransfered, // number of bytes transferred LPOVERLAPPED lpOverlapped // pointer to structure with I/O information ) { int nIndex = (int) ( lpOverlapped->hEvent ); printf( "Read #%d returned %d. %d bytes were read.\n",
nIndex, dwErrorCode, dwNumberOfBytesTransfered );
if ( ++nCompletionCount == MAX_REQUESTS )
    SetEvent( ghEvent ); // Cause the wait to terminate
}

int main( )
{
  int i;
  char szPath[ MAX_PATH ];
  CheckOsVersion( );

  MTVERIFY( ghEvent = CreateEvent( NULL,    // No security
    TRUE,    // Manual reset - extremely important!
    FALSE,   // Initially set Event to non-signaled state
    NULL     // No name
    ) );

  GetWindowsDirectory( szPath, sizeof( szPath ) );
  strcat( szPath, "\\WINHLP32.EXE" );

  ghFile = CreateFile( szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
  if ( ghFile == INVALID_HANDLE_VALUE )
  {
    printf( "Could not open %s\n", szPath );
    return -1;
  }

  for ( i = 0; i < MAX_REQUESTS; i++ )
  {
    QueueRequest( i, i * 16384, READ_SIZE );
  }
  printf( "QUEUED!!\n" );

  for ( ;; )
  {
    DWORD rc;
    rc = WaitForSingleObjectEx( ghEvent, INFINITE, TRUE );
    if ( rc == WAIT_OBJECT_0 )
      break;
    MTVERIFY( rc == WAIT_IO_COMPLETION );
  }

  CloseHandle( ghFile );
  return EXIT_SUCCESS;
}

int QueueRequest( int nIndex, DWORD dwLocation, DWORD dwAmount )
{
  int i;
  BOOL rc;
  DWORD err;

  gOverlapped[ nIndex ].hEvent = (HANDLE) nIndex;
  gOverlapped[ nIndex ].Offset = dwLocation;

  for ( i = 0; i < MAX_TRY_COUNT; i++ )
  {
    rc = ReadFileEx( ghFile, gBuffers[ nIndex ], dwAmount,
      &gOverlapped[ nIndex ], FileIOCompletionRoutine );

    if ( rc )
    {
      printf( "Read #%d queued for overlapped I/O.\n", nIndex );
      return TRUE;
    }
    err = GetLastError( );

    if ( err == ERROR_INVALID_USER_BUFFER || err == ERROR_NOT_ENOUGH_QUOTA
      || err == ERROR_NOT_ENOUGH_MEMORY )
    {
      Sleep( 50 ); // Wait around and try later
      continue;
    }
    break;
  }

  printf( "ReadFileEx failed.\n" );
  return -1;
}

 

posted @ 2013-04-19 15:21  IAmAProgrammer  阅读(825)  评论(0编辑  收藏  举报