16.3同步I/O
1.向设备读写数据的函数 为:ReadFile和WriteFile,
BOOL ReadFile(HANDLE hFile,LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped);
BOOL WriteFile(HANDLE hFile,CONST VOID * lpBuffer,
DWORD nNumberOfBytesToWrite,
LPWORD lpNumberOfBytesWrite,
LPOVERLAPPED lpOverlapped);
虽然这两个函数名字中都有File,但是它们都可以被用于任何设备。
参数说明:
hFile 是你想要访问的文件的句柄。
注意:当设备打开时,你不能使用 FILE_FLAG_OVERLAPPED标志,否则系统会认为你要异步访问该设备。
LpBuffer 用来指向读入数据的缓冲区或用于向文件中写入数据的缓冲区。
nNumberOfBytesToRead 和nNumberOfBytesToWrite分别告诉ReadFile和WriteFile从文件中读出和写入多少字节。
LpNumberOfBytesRead 和lpNumberOfBytesWrite分别给出一个DWORD的地址,函数将在其中添入成功地向设备传输的字节数。
lpOverlapped 在进行同步I/O时应当为NULL。
注意:只有设备是使用GENERIC_READ标志打开时,才可以使用 ReadFile。只有设备是使用GENERIC_WRITE标志打开时,才可以使用WriteFile。
2.向设备强制刷新数据:
使用函数FlushFileBuffer 来强制系统把缓存数据写回设备,
BOOL FlushFileBuffer(HANDLE hFile);
该函数强迫同参数hFile 标识的设备相关联的缓存数据写回设备。该设备必须使用GENERIC_WRITE打开。
16.4异步I/O
Win32 有四种技术用于异步I/O。下表简单总结了这几种技术,
技术 | 说明 |
使一个设备内核对象变为有信号 | 它允许一个线程发 出I/O请求,另一个线程进行处理。如果你要对单个设备进行多个同时的I/O请求,这种技术就没有用。 |
使一个事件内核对象变为有信号 | 允许对单个设备进 行多个同时的I/O请求。允许一个线程发出I/O请求,另一个线程进行处理。 |
警告I/O | 允许对单个设备进 行多个同时的I/O请求。发出I/O请求的线程也必须处理它。 |
I/O完成端口 | 允许对单个设备进 行多个同时的I/O请求。允许一个线程发出I/O请求,另一个线程进行处理。该技术的申缩性和灵活性最好。 |
1. 使一个设备内核对象变为有信号
在异步I/O中要发出请 求,还得使用ReadFile和WriteFile函数,不过在它们的参数lpOverlapped必须被传递一个被初始化了的OVERLAPPED结构 指针。
Win32使用overlapped(重叠)指出花在执行I/O请求的时间与你的线程做其它事 情的时间重叠了。
当异步I/O时,调用ReadFile和WriteFile时,必须分配一个 OVERLAPPED结构,并初始化它的Offset、OffsetHigh和hEvent成员。当设备是文件时,Offset和OffsetHigh才 有用,对于其它设备,这两个成员必须被设置成0。
当访问文件时,Offset 和OffsetHigh指出了文件中你想要开始I/O操作的64位偏移量,例如:如果你想从一个文件的第123字节开始读取100个字节,可以这样做,
HANDLE hFile = CreateFile(…,FILE_FLAG_OVERLAPPED,…);
BYTE Buffer[100];
DWORD nNumBytesRead;
OVERLAPPED Overlapped;
Overlapped.Offset = 123;
Overlapped.OffsetHigh = 0;
Overlapped.hEvent = NULL;
ReadFile(hFile,bBuffer,sizeof(bBuffer),&nNumBytesRead,&Overlapped);
ReadFile和WriteFile 要求在每次异步I/O开始的字节必须在结构OVERLAPPED中指定。这与同步I/O是完全不同的。
Win32把设备句柄看作是可同步的对 象,即它或处于有信号状态,或处于无信号状态。当你调用ReadFile或WriteFile时,这些函数首先需要做的事情之一是将设备句柄重设置为无信 号状态。当所有数据都被读入或写入设备,系统就将设备句柄置为有信号状态。可以通过调用WaitForSingleObject或 WaitForMultpleObjects函数,线程就可以判断异步设备操作何时完成,即设备句柄何时被设置成有信号状态。也可以使用另外一个函数来达 到同前两个函数同样的效果,而且这个函数比前两个更好,
BOOL GetOverlappedResult(HANDLE hFile,
LPOVERLAPPED lpOverlapped,LPDWORD lpcbTransfer,BOOL fWait);
参数说明:
前两个参数必须是调用ReadFile 或WriteFile中的参数值。
lpcbTransfer 是成功传递的字节数。
FWait 表示GetOverlappedResult是否要等待重叠操作完成之后再返回。
2. 使一个事件内核对象变为有信号
在OVERLAPPED结构中有一个成员hEvent,它是 一个事件内核对象。如果你想同时执行多个异步设备I/O请求,你应该为每一个请求创建一个事件。初始化每一个请求的结构OVERLAPPED成员 hEvent,再调用ReadFile和WriteFile。当你到达了需要与I/O请求的完成同步的那一点时,只要调用 WaitForMultipleObjects,传递同每个I/O请求的OVERLAPPED结构相关联的事件句柄。这样,你就可以使用同一设备句柄,而 容易可靠地进行多个同时的异步设备I/O操作。
3. 使用警告I/O
当一个线程创建时,系统还给这个线程分配了一个队列,它叫异步过程调 用队列(APC,Asynchronous Procedure Call)。线程的异步过程调用队列类似于消息队列,不过消息队列是实现在“用户界面控件”上的消息,而异步过程调用队列是实现在“低层内核”,所以 APC队列执行的更有效也更快。
如何将消息发送到“APC”队列呢?调用ReadFileEx或WriteFileEx函数来发出I/O请求,这样I/O请求的结果就会被放到 APC队列中。还有另一个函数,它允许你人工的向APC队列中添加一项,
DWORD QueueUserAPC(PAPCFJNC pfnAPC,HANDLE hThread,DWORD dwData);
参数说明:
pfnAPC是一个APC函数的指针,该函数如下:VOID WINAPI APCFunc(DWORD dwParam);
hThread 是你想要添加表项的线程的句柄,注意它可以是系统中的任何线程,如果hThread是另一个进程中的线程,那么pfnAPC属于这个线程的进程。
DwData是传递给回调函数的一个32位值。
在I/O请求结果放入APC队列后,线程并不是马上就能处理这些请求,如果要想这些请求可以被 马上处理,就必须使线程处于“警觉状态”,如何让线程处于警觉状态呢?可以使用下面这几个函数实现,
DWORD SleepEx(DWORD dwTimeOut,BOOL fAlertable);
DWORD WaitForSingleObjectEx(HANDLE hObject,DWORD dwTimeOut,BOOL fAlertable);
DWORD WaitForMultipleObjectsEx(DWORD cObjects,LPHANDLE lphObjects,BOOL fWaitAll,DWORD dwTimeOut,BOOL fAlertable);
DWORD SignalObjectAndWait(HANDLE hObjectToSignal,HANDLE hObjectToWaitOn,DWORD dwMilliseconds,BOOL bAlertable);
DWORD MsgWaitForMultipleObjectsEx(DWORD nCount,LPHANDLE pHandles,DWORD dwMilliseconds,DWORD dwWakeMask,DWORD dwFlags);
当调用上面五个函数之一使你的线程进入警觉状态时,系统首先检查线 程的APC队列,如果队列中至少有一项,系统就不会让线程休眠,系统会把该项从APC队列中取出来,你的线程调用 回调函数,传递给它完成的I/O请求的错误码,传输的字节数和OVERLAPPED结构的地址。当回调函数返回时,系统检查APC队列中是否有更多的项。 如果有,它们会被继续处理。如果没有函数调用就会返回。再说一下这五个函数的返回值,如果返回值是WAIT_IO_COMPLETION,那么线程的继续 执行就是因为线程的APC队列中至少有一个表项被处理了。如果返回值是别的,就说明线程被唤醒或者是因为睡眠超时了,或者是指定的内核对象变成有信号状态 了,或者是一个互斥对象被废弃