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

本章回 答了如下几个问题:

  ◆ 什么是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类型,是使用它自己的文件句柄作为同步机制。大致流程如下:

  1. 调用CreateFile(),设置dwFlagsAndAttributes含FILE_FLAG_ OVERLAPPED 标志,通知操作系统对此文件的访问采取异 步I/O模式;
  2. 建立一个 OVERLAPPED 结构,其中包含I/O请求所需参数;
  3. 调用ReadFile()或WriteFile(),最后一个参数为 OVERLAPPED 结构为参数。此时Win32会在后台处理 你的请求;
  4. 使用wait…()函数,并将该文件的文件句柄作为参数,等待操作结束。文件句柄是核心对象,一旦操作完成即被激发;
  5. 使用GetOverlappedResult()函数,查询操作结果。此时你获得的结果和调用ReadFile()、WriteFile()而没指定 overlapped I/O所传回的东西一样。
  6. 文件使用完毕,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配搭手动事件,会产生两个问题:

  1. 使用WaitForMultipleObjects()最多只能等待MAXIMUM_WAIT_OBJECTS个对象。对于Win32,此值目前为64。 如果等待的对象超过64,就会出问题。
  2. 必须不断地根据“哪一个事件被激发”而计算如何反应。

异步过程调用可解决此问题。此时只要使用“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才能凸显其优越性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值