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