您的位置:首页 > 移动开发

Overlapped I/O,在你身后变戏法

2010-07-14 12:28 246 查看
本章回
答了如下几个问题:

◆ 什么是Overlapped
I/O?为什么需要Overlapped
I/O?如何让数据传输支持Overlapped
I/O?

◆ 数据传输结束后,Win32提供了哪些方式对用户进行通告,以便进行适当的善后?

◆ 影响线程优先级的因素有哪些?如何获取或设置进程线程优先级?优先级的改变容易带来哪些问题?又该如何应对?

◆ 什么是被激发的文件句柄?什么是被激发的事件?什么是异步进程调用(APCs)?这些方式各是如何实现Overlapped
I/O的?各有何优缺点?

◆ 使用Overlapped
I/O的初衷是使“受制于I/O的程序”中获得高效率。但是否是各种情况下Overlapped
I/O都能提高系统效率吗?

◆ 什么是I/O completion
port?这个机制是怎么工作的?有什么优点?为什么说此方式能够很好地支持scalable(可升级的)系统?而对于工作于此模式下的文件,从提高系统
效率考虑,怎样才能避免无谓的completion
packet通告呢?

什么是Overlapped
I/O?

Overlapped
I/O,常被设计为多线程处理,以便在一个“受制于I/O的程序”中获得高效率。
利用Win32所谓的overlapped
I/O特性,可以让I/O操作并行处理,并且当一个I/O完成时,程序会收到一个通告。有些系统把这个特性称为非阻塞I/O,或异步I/O。
overlapped
I/O是Win32的一项技术,你可以要求操作系统为你传送数据,并且传送完毕后通知你。这项技术使你的程序在I/O进行中仍然能够继续处理事务。事实
上,操作系统内部正是以线程来完成overlapped
I/O。这样,你可以获得线程的所有利益,而不需付出痛苦代价。

在Windows 95环境下,Overlapped
I/O使用有些限制。它不支持磁盘或光盘中的文件操作。

Win32文件操作函数

HANDLE CreateFile(

LPCTSTR
lpFileName,
// pointer to name of the file
DWORD
dwDesiredAccess,
// access (read-write) mode
DWORD
dwShareMode,
// share mode
LPSECURITY_ATTRIBUTES
lpSecurityAttributes,// pointer to security attributes
DWORD
dwCreationDisposition, // how to create
DWORD
dwFlagsAndAttributes, // file attributes
HANDLE
hTemplateFile
// handle to file with attributes to copy

);
BOOL ReadFile(

HANDLE
hFile,
// handle of file to read
LPVOID
lpBuffer,
// pointer to buffer that receives data
DWORD
nNumberOfBytesToRead, // number of bytes to
read
LPDWORD lpNumberOfBytesRead, // pointer
to number of bytes read
LPOVERLAPPED
lpOverlapped
// pointer to structure for data

);
BOOL WriteFile(

HANDLE
hFile,
// handle to file to write to
LPCVOID
lpBuffer,
// pointer to data to write to file
DWORD
nNumberOfBytesToWrite,
// number of bytes to write
LPDWORD
lpNumberOfBytesWritten, // pointer to number of
bytes written
LPOVERLAPPED
lpOverlapped
// pointer to structure for overlapped
I/O

);
BOOL CloseHandle(

HANDLE
hObject // handle to object to
close

);

一些说明:

CreateFile()可处理的对象包括:files、pipes、mailslots、communication
resource、disk device(Windows NT only)、consoles、directories(open
only)。
如果要使用异步I/O,CreateFile()时设置dwFlagsAndAttributes含FILE_FLAG_OVERLAPPED

志,通知操作系统对此文件的访问采取异步I/O模式。

由于异步I/O模式下,可以在同一时间读(或写)文件的许多部分,所以没有“目前的文件位置”这一概念。每次读写都必须包含其文件位置。

OVERLAPPED
结构

typedef struct _OVERLAPPED
{

DWORD Internal;
DWORD
InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;

} OVERLAPPED
;

说明:
Internal

通常它被保留。然而,当GetOverlappedResult()返回FALSE,GetLastError()返回ERROR_IO_PENDING
时,这个栏位将内含一个视系统而定的状态。

InternalHigh

通常它被保留。然而,当GetOverlappedResult()返回TRUE时,这个栏位将内含“被传输的数据长度”。

Offset

定义文件开始读(或写)的开始位置,以字节为单位。该偏离位置从文件头开始起算。如果目标设备不支持文件位置(比如管道),此栏忽略。

OffsetHigh

64位的文件偏离量,较高的32位。如果目标设备不支持文件位置(比如管道),此栏忽略。

hEvent
一个手动事件。当overlapped
I/O完成后即被激发。ReadFileEx()和WriteFileEx()忽略这个栏位,彼时它可能用来传输一个用户自定义的指针。
注意:
OVERLAPPED

构的生命周期应超越ReadFile()和WriteFile()函数。

被激发的File
Handles

注意,此方式在Windows95/98下不被支持。详看MSDN中关于GetOverlappedResult()函数的说明。
在Windows NT下,最简单的overlapped
I/O类型,是使用它自己的文件句柄作为同步机制。大致流程如下:

调用CreateFile(),设置dwFlagsAndAttributes含FILE_FLAG_OVERLAPPED
标志,通知操作系统对此文件的访问采取异
步I/O模式;

建立一个OVERLAPPED
结构,其中包含I/O请求所需参数;

调用ReadFile()或WriteFile(),最后一个参数为OVERLAPPED
结构为参数。此时Win32会在后台处理
你的请求;

使用wait…()函数,并将该文件的文件句柄作为参数,等待操作结束。文件句柄是核心对象,一旦操作完成即被激发;

使用GetOverlappedResult()函数,查询操作结果。此时你获得的结果和调用ReadFile()、WriteFile()而没指定overlapped
I/O所传回的东西一样。

文件使用完毕,CloseHandle()。

BOOL GetOverlappedResult(
HANDLE
hFile,
// handle to file, pipe, or comm device
LPOVERLAPPED
lpOverlapped,
// pointer to overlapped
structure
LPDWORD lpNumberOfBytesTransferred, //
pointer to actual bytes count
BOOL
bWait
//
wait
flag
);
说明:如果bWait设置为FALSE,如果overlapped
I/O还没完成,函数返回FALSE,调用GetLastError()会返回ERROR_IO_INCOMPLETE。所以,调用
GetOverlappedResult()时如果bWait设置为FALSE,可即时查询overlapped
I/O的状态。

举例:
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED
,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return -1;
}

// Initialize the OVERLAPPED
structure
memset(&overlap, 0,
sizeof(overlap));
overlap.Offset = 1500;

// Request the data
rc = ReadFile(
hFile,buf,READ_SIZE,&numread,&overlap);

if (rc)
{
// The data was read successfully
}
else
{
if (GetLastError() ==
ERROR_IO_PENDING)
{
VERIFY(WAIT_OBJECT_0 ==
WaitForSingleObject(hFile, INFINITE));
rc = GetOverlappedResult(hFile,
&overlap,&numread,FALSE);
}
else
{
// Something went wrong
}
}

CloseHandle(hFile);

说明:

虽然你要求一个overlapped

作,但如果数据已被放入cache中,或操作系统认为它可以快速地取得那份你所要求的数据,那么文件操作会在ReadFile()返回前完成。此时
ReadFile()返回TRUE。

如果你要求一个文件操作为overlapped
操作,而操作系统把这个“操作请求”放到队列中等待执行,那么ReadFile()或WriteFile()
都会传回FALSE以示失败。但函数返回FALSE有多种原因,可调用GetLastError()调查原因,如果是ERROR_IO_PENDING,
那说明“操作请求”被放入队列等待执行;其它值,那可能真正代表一个错误了。

OVERLAPPED

构中包含了“这个操作从哪里开始的信息”,可以处理64位的偏移量。

WaitForSingleObject(hFile, INFINITE);
rc = GetOverlappedResult(hFile,
&overlap,&numread,FALSE);其实可合并为一句:rc
= GetOverlappedResult(hFile,
&overlap,&numread,TRUE);

被激发的事件对象

使用文件句柄作为激发机制,有一个明显的限制:如果多个线程对同一个文件进行操作,由于只有一个相同的handle,对于每个可能进行的overlapped

作都调用GetOverlappedResult()查看操作是否完成,这将不是一个很有效率的做法——因为很多的时候并不是自己所期待的操作完成了。另
外,Windows95/98下不可以使用文件句柄作为激发机制。

OVERLAPPED

构中的最后一个栏位是一个事件句柄。如果使用文件句柄作为激发对象,可将该位设置为NULL。如果该位被设定为一个事件对象时,系统核心会在overlapped

作完成后,自动设置此事件为激发。

由于每个overlapped

作都有它独一无二的OVERLAPPED

构,每个结构都有它独一无二的事件对象,用以代表该操作。

注意:OVERLAPPED

构中的事件必须是手动事件。否则,如果事件为自动事件,由于系统核心可能会在你有机会等待该事件前就激发它,而自动事件的激发状态是不能保留的,于是事件
遗失,这将导致你的等待永远无法返回。

使用手动事件配搭overlapped
I/O,就可以对同一个文件发出多个读取操作和多个写入操作,每个操作都有自己的事件对象。然后调用wait…()函数等待其中之一或全部完成。

异步过程调用(Asynchronous Procedure Calls,APCs)

使用overlapped
I/O配搭手动事件,会产生两个问题:

使用WaitForMultipleObjects()最多只能等待MAXIMUM_WAIT_OBJECTS个对象。对于Win32,此值目前为64。
如果等待的对象超过64,就会出问题。

必须不断地根据“哪一个事件被激发”而计算如何反应。

异步过程调用可解决此问题。此时只要使用“Ex”版的ReadFile()和WriteFile()。这两函数可额外指定一个参数,定义一个
callback函数。当一个overlapped
I/O完成时,系统调用该callback函数。这个callback函数被称为I/O completion
routine,因为,系统是在一个特定的overlapped
I/O完成后调用它的。

但是,需要注意的是,一个特定的overlapped
I/O完成后,Windows并不会贸然中断你的程序,然后调用你所提供的callback函数。那显然可能会带来新的问题。只有线程说“好,现在是一个
安全时机”时系统才会调用你的callback函数。以Windows的说法,只有线程处于所谓的“alertabe”状态,回调函数才会被执行,否则对
I/O
completion
routine的调用会被暂时搁置下来。因此,当一个线程终于处于“alertabe”状态时,可能有一堆储备的APCs等待被处理。

如果线程因为以下5个函数而处于等待状态,而其“alertabe”标志被设置为TRUE,则该现程就是处于“alertabe”状态:
DWORD SleepEx(
DWORD dwMilliseconds,
// time-out interval in milliseconds
BOOL
bAlertable
// early completion flag
);
DWORD WaitForSingleObjectEx(
HANDLE
hHandle,
// handle to object to wait for
DWORD dwMilliseconds,
// time-out interval, in milliseconds
BOOL
bAlertable
// return to execute I/O completion routine if TRUE
);
DWORD WaitForMultipleObjectsEx(
DWORD
nCount,
// number of handles in handle array
CONST HANDLE
*lpHandles, // points to the object-handle
array
BOOL
fWaitAll,
// wait flag
DWORD
dwMilliseconds,
// time-out interval in milliseconds
BOOL
bAlertable
// alertable wait flag
)
DWORD MsgWaitForMultipleObjectsEx(
DWORD
nCount,
//
number of handles in handle array
LPHANDLE
pHandles,
// pointer to an object-handle array
DWORD dwMilliseconds,
// time-out interval in milliseconds
DWORD
dwWakeMask,
// type of input events to wait for
DWORD
dwFlags
// wait flags
);
DWORD SignalObjectAndWait(
HANDLE
hObjectToSignal, // handle to object to
signal
HANDLE
hObjectToWaitOn, // handle to object to wait
for
DWORD
dwMilliseconds,
// time-out interval in milliseconds
BOOL
bAlertable
// alertable flag
);

只有使用上面这些函数进行等待ReadFileEx()或WriteFileEx()操作,回调函数才会被执行。

回调函数解释

VOID CALLBACK
FileIOCompletionRoutine(
DWORD
dwErrorCode,
// completion code
DWORD
dwNumberOfBytesTransfered, // number of bytes
transferred
LPOVERLAPPED
lpOverlapped
// pointer to structure with I/O information
);

dwErrorCode:0表示操作完成,ERROR_HANDLE_EOF表示操作到文件尾

dwNumberOfBytesTransfered:真正被传输的数据字节数

lpOverlapped:指向OVERLAPPED
结构,由开启overlapped
I/O操作的函数提供。由于APCs时OVERLAPPED
结构中hEvent栏位不需要用来放置一个事件句柄,此栏程序员可自由运用,比如用作回调函数的入
口参数。

对文件进行Overlapped
I/O的缺点

在Windows NT测试时发现,Windows
NT似乎是以“I/O请求”的大小来决定是否进行overlapped
I/O。如果数据量较小,系统实际上总是采取非overlapped
方式进行文件读取;数据量略大些,采取Overlapped
I/O其实比单纯调用ReadFile()并无优越性,相反地,效率降低。如果文件数据量比较大,Overlapped
I/O才能凸显其优越性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: