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

《Win32多线程程序设计》(12)---overlapped I/O

2014-02-12 17:21 323 查看
文字版电子书-----请点击这里

overlapped I/O  是Win32 的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O 进行过程中仍然能够继续处理事务。

i 激发的文件handles

i 激发的event  对象

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

i I/O completion ports

Win32 文件操作函数

Win32 之中有三个基本的函数用来执行I/O,它们是:

iCreateFile()

iReadFile()

iWriteFile()

CreateFile() 可以用来打开各式各样的资源,包括(但不限制于):

i 文件(硬盘、软盘、光盘或其他)

i 串行口和并行口(serial and parallel ports)

iNamed pipes

iConsole

HANDLE CreateFile(
LPCTSTR lpFileName, // 指向文件名称
DWORD dwDesiredAccess, // 存取模式(读或写)
DWORD dwShareMode, // 共享模式(share mode)
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性结构
DWORD dwCreationDisposition, // 如何产生
DWORD dwFlagsAndAttributes, // 文件属性
HANDLE hTemplateFile // 一个临时文件,将拥有全部的属性拷贝
);


其中第6个参数dw FlagsAndAttributes 是使用overlapped  I/O 的 关 键。这个参数可以藉由许多个数值组合在一起而完成,其中对于本处讨论最重要的一个数值便是FILE_ FLAG_OVERLAPPED。你可以藉着这个参数,指定使用同步(传统的)调用,或是使用overlapped(异步)调用,但不能够两个都指定。

换句话说,如果这个标记值设立,那么对该文件的每一个操作都将是overlapped。

一个不常被讨论的overlapped  I/O 性质是,它可以在同一时间读(或写)文件的许多部分。微妙处在于这些操作都使用相同的文件handle。因此,当你使用overlapped  I/O时,没有所谓“目前的文件位置”这样的观念。每一次读或写的操作都必须包含其文件位置。

如果你发出许多个overlapped  请求,那么执行次序无法保证。虽然你在单一磁盘中对文件进行操作时很少会有这样的行为,但如果面对多个磁盘,或不同种类的设备(如网络和磁盘),就常常会看到I/O  请求完全失去次序。

BOOL ReadFile(
HANDLE hFile, // 欲读之文件
LPVOID lpBuffer, // 接收数据之缓冲区
DWORD nNumberOfBytesToRead, // 欲读取的字节个数
LPDWORD lpNumberOfBytesRead, // 实际读取的字节个数的地址
LPOVERLAPPED lpOverlapped // 指针,指向overlapped info
);
BOOL WriteFile(
HANDLE hFile, // 欲写之文件
LPCVOID lpBuffer, // 储存数据之缓冲区
DWORD nNumberOfBytesToWrite, // 欲写入的字节个数
LPDWORD lpNumberOfBytesWritten, // 实际写入的字节个数的地址
LPOVERLAPPED lpOverlapped // 指针,指向overlapped info
);
这两个函数很像C  runtime 函数中的fread()  和fwrite(),差别在于最后一个参数lpOverlapped。如果CreateFile() 的第6个参数被指定为FILE_FLAG_ OVERLAPPED,你就必须在上述的lpO verlapped 参数中提供一个指针,指向一个OVERLAPPED 结构。

OVERLAPPED 结构

OVERLAPPED 结构执行两个重要的功能。第一,它像一把钥匙,用以识别每一个目前正在进行的overlapped  操作。第二,它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。

OVERLAPPED 结构看起来像这样:

typedef struct _OVERLAPPED {

DWORD Internal;

DWORD InternalHigh;

DWORD Offset;

DWORD OffsetHigh;

HANDLE hEvent;

} OVERLAPPED, *LPOVERLAPPED;

Internal  通常它被保留。然而当GetOverlappedResult()  传回F ALSE 并且GetLastError() 并非传回ERROR_IO_PENDING 时,这个栏位将内含一个视系统而定的状态

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

Offset  文件之中开始被读或被写的偏移位置(以字节为单位)。该偏移位置从文件头开始起算。如果目标设备(例如pipes)并没有支持文件位置,此栏位将被忽略

OffsetHigh  64 位的文件偏移位置中,较高的32  位。如果目标设备(例如pipes)并没有支持文件位置,此栏位将被忽略

hEvent  一个手动重置(manual-reset)的event  对象,当overlapped  I/O 完成时即被激发。ReadFileEx() 和W riteFileEx() 会忽略这个栏位,彼时它可能被用来传递一个用户自定义的指针

(1)被激发的 File Handles

最简单的overlapped  I/O 类型,是使用它自己的文件handle 作为同步机制。首先你以FILE_ FLAG_OVERLAPPED 告诉Win32 说你不要使用默认的同步I/O。然后,你设立一个OVERLAPPED 结构,其中内含“I/O 请求”的所有必要参数,并以此识别这个“I/O 请求”,直到它完成为止。接下来,调用ReadFile() 并以OVERLAPPED 结构的地址作为最后一个参数。这时候,理论上,Win32 会在后台处理你的请求。你的程序可以放心地继续处理其他事情。

如果你需要等待overlapped I/O  的执行结果,作为WaitForMultipleObjects() 的一部分,请在handle  数组中加上这个文件handle。文件handle  是一个核心对象,一旦操作完毕即被激发。当你完成操作之后,请调用G etOverlappedResult() 以确定结果如何。

调用G etOverlappedResult(),你获得的结果和“调用ReadFile()  或WriteFile() 而没有指定overlapped  I/O”所传回的东西一样。这个函数的价值在于,在文件操作真正完成之前,你不可能确实知道它是否成功。甚至在一个完美无瑕的环境下读一个已知的磁盘文件,也有可能发生硬件错误、服务器当掉,或任何未能预期的错误。因此,调用G etOverlappedResult() 是很重要的。

BOOL GetOverlappedResult(
HANDLE hFile,
LPOVERLAPPED lpOverlapped,
LPDWORD lpNumberOfBytesTransferred,
BOOL bWait
);


参数

hFile  文件或设备(device)的handle。

lpOverlapped  一个指针,指向OVERLAPPED 结构。

lpNumberOfBytesTransferred  一个指针,指向DWORD,用以表示真正被传输的字节个数。

bWait  一个布尔值,用以表示是否要等待操作完成。TRUE 表示要等待。

返回值

如果overlapped  操作成功,此函数传回TRUE。失败则传回FALSE。GetLastError() 可获得更详细的失败信息。如果bWait  为FALSE  而overlapped 还是没有完成,GetLastError() 会传回ERROR_IO_INCOMPLETE。

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

//
// Constants
//
#define READ_SIZE       512

//
// Function prototypes
//
void CheckOsVersion();

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");
// Open the file for overlapped reads
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;
}

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

// Request the data
rc = ReadFile(
hFile,
buf,
READ_SIZE,
&numread,
&overlap
);
printf("Issued read request\n");

// Was the operation queued?
if (rc)
{
// The data was read successfully
printf("Request was returned immediately\n");
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
// We could do something else for awhile here...

printf("Request queued, waiting...\n");
WaitForSingleObject(hFile, INFINITE);
printf("Request completed.\n");

rc = GetOverlappedResult(
hFile,
&overlap,
&numread,
FALSE
);
printf("Result was %d\n", rc);
}
else
{
// We should check for memory and quota
// errors here and retry. See the samples
// IoByEvnt and IoByAPC.

// Something went wrong
printf("Error reading file\n");
}
}

CloseHandle(hFile);

return EXIT_SUCCESS;
}

//
// Make sure we are running under an operating
// system that supports overlapped I/O to files.
//
void CheckOsVersion()
{
OSVERSIONINFO   ver;
BOOL            bResult;

ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

bResult = GetVersionEx((LPOSVERSIONINFO) &ver);

if ( (!bResult) ||
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )
{
fprintf(stderr, "IoByFile must be run under Windows NT.\n");
exit(EXIT_FAILURE);
}

}


被激发的 Event 对象

OVERLAPPED 结构中的最后一个栏位,是一个event  handle。如果你使用文件handle 作为激发对象,那么此栏位可为NULL。当这个栏位被设定为一个event  对象时,系统核心会在overlapped  操作完成的时候,自动将此event 对象给激发起来。由于每一个overlapped  操作都有它自己独一无二的OVERLAPPED 结构,所以每一个结构都有它自己独一无二的一个event  对象,用以代表该操作。

有一件事很重要:你所使用的event  对象必须是手动重置(manual-reset)而非自动重置(auto-reset,详见第4章)。如果你使用自动重置,就可能产生出race condition ,因为系统核心有可能在你有机会等待该event  对象之前,先激发它,而你知道,event 对象的激发状态是不能够保留的(这一点和semaphore 不同)。于是这个event  状态将遗失,而你的Wait...() 函数永不返回。但是一个手动重置的event,一旦激发,就一直处于激发状态,直到你动手将它改变。

使用event  对象搭配overlapped  I/O,你就可以对同一个文件发出多个读取操作和多个写入操作,每一个操作有自己的event  对象;然后再调用WaitForMultipleObjects() 来等待其中之一(或全部)完成。

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "MtVerify.h"

//
// Constants
//
#define MAX_REQUESTS    5
#define READ_SIZE       512
#define MAX_TRY_COUNT   5

//
// Function prototypes
//
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount);
void CheckOsVersion();

//
// Global variables
//

// Need to keep the events in their own array
// so we can wait on them.
HANDLE  ghEvents[MAX_REQUESTS];
// Keep track of each individual I/O operation
OVERLAPPED gOverlapped[MAX_REQUESTS];
// Handle to the file of interest.
HANDLE ghFile;
// Need a place to put all this data
char gBuffers[MAX_REQUESTS][READ_SIZE];

int main()
{
int i;
BOOL rc;
char szPath[MAX_PATH];

CheckOsVersion();

GetWindowsDirectory(szPath, sizeof(szPath));
strcat(szPath, "\\WINHLP32.EXE");
// Open the file for overlapped reads
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++)
{
// Read some bytes every few K
QueueRequest(i, i*16384, READ_SIZE);
}

printf("QUEUED!!\n");

// Wait for all the operations to complete.
MTVERIFY( WaitForMultipleObjects(
MAX_REQUESTS, ghEvents, TRUE, INFINITE
) != WAIT_FAILED );

// Describe what just happened.
for (i=0; i<MAX_REQUESTS; i++)
{
DWORD dwNumread;

rc = GetOverlappedResult(
ghFile,
&gOverlapped[i],
&dwNumread,
FALSE
);
printf("Read #%d returned %d. %d bytes were read.\n",
i, rc, dwNumread);
CloseHandle(gOverlapped[i].hEvent);
}

CloseHandle(ghFile);

return EXIT_SUCCESS;
}

/*
* Call ReadFile to start an overlapped request.
* Make sure we handle errors that are recoverable.
* Properly set up the event object for this operation.
*/
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
int i;
BOOL rc;
DWORD dwNumread;
DWORD err;

MTVERIFY(
ghEvents[nIndex] = CreateEvent(
NULL,    // No security
TRUE,    // Manual reset - extremely important!
FALSE,   // Initially set Event to non-signaled state
NULL     // No name
)
);
gOverlapped[nIndex].hEvent = ghEvents[nIndex];
gOverlapped[nIndex].Offset = dwLocation;

for (i=0; i<MAX_TRY_COUNT; i++)
{
rc = ReadFile(
ghFile,
gBuffers[nIndex],
dwAmount,
&dwNumread,
&gOverlapped[nIndex]
);

// Handle success
if (rc)
{
printf("Read #%d completed immediately.\n", nIndex);
return TRUE;
}

err = GetLastError();

// Handle the error that isn't an error. rc is zero here.
if (err == ERROR_IO_PENDING)
{
// asynchronous i/o is still in progress
printf("Read #%d queued for overlapped I/O.\n", nIndex);
return TRUE;
}

// Handle recoverable error
if ( err == ERROR_INVALID_USER_BUFFER ||
err == ERROR_NOT_ENOUGH_QUOTA ||
err == ERROR_NOT_ENOUGH_MEMORY )
{
Sleep(50);  // Wait around and try later
continue;
}

// Give up on fatal error.
break;
}

printf("ReadFile failed.\n");
return -1;
}

//
// Make sure we are running under an operating
// system that supports overlapped I/O to files.
//
void CheckOsVersion()
{
OSVERSIONINFO   ver;
BOOL            bResult;

ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

bResult = GetVersionEx((LPOSVERSIONINFO) &ver);

if ( (!bResult) ||
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )
{
fprintf(stderr, "IoByEvnt must be run under Windows NT.\n");
exit(EXIT_FAILURE);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Win32 多线程 thread