zoukankan      html  css  js  c++  java
  • 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;
    }

  • 相关阅读:
    POJ 1094 (传递闭包 + 拓扑排序)
    POJ 2594 (传递闭包 + 最小路径覆盖)
    POJ 3041 Asteroids(二分图最大匹配)
    差分数组原理与其前缀和的应用
    树状数组的理解以及简单应用
    HDU 4635 (完全图 和 有向图缩点)
    2.基本数据类型
    Python基础
    数据库类型
    Vue-2
  • 原文地址:https://www.cnblogs.com/shangdawei/p/3030827.html
Copyright © 2011-2022 走看看