UNBUFCP1.C

/*++ 

Copyright 1994 - 1998 Microsoft Corporation

Module Name:

unbufcp1.c

Abstract:

This single-threaded version shows how to multiplex I/O to a number of files with
a single thread. This is the most efficient mechanism if you do not need the
asynchronous completion model that the dual-threaded version offers.

Only one thread and one I/O completion port is used. The file handles for the
source and destination file are both associated with the same port. The
thread starts off by posting a number of overlapped
reads from the source file. It then waits on the I/O completion port.
Whenever a read completes, it immediately turns it around into a write to
the destination file. Whenever a write completes, it immediately posts the
next read from the source file.

Thread 1
|
|
kick off a few
overlapped reads
|
|
->GetQueuedCompletionStatus(WritePort) <-----------
| | |
| |------------------------------- |
| | | |
| write has completed, read has completed, |
| kick off another kick off the write. |
| read | |
| | | |
|____| |_____________|

Author:

John Vert (jvert) 21-Dec-1994

Revision History:

24-Apr-1995 Brian Sherrell (briansh) - Added platform detection &
I/O Completion Port creation with INVALID_HANDLE_VALUE.

22-March-1996 Brian Sherrell (briansh) - Pending I/O bug fix.

3-Apr-1996 Brian Sherrell (briansh) - Correct versioning logic.

--*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

//
// File handles for the copy operation. All read operations are
// from SourceFile. All write operations are to DestFile.
//
HANDLE SourceFile;
HANDLE DestFile;

//
//version information
//
OSVERSIONINFO ver;

//
// I/O completion port. All read and writes to the files complete
// to this port.
//
HANDLE IoPort;

//
// Key values used to determine whether a read or a write
// has completed.
//
#define ReadKey 0
#define WriteKey 1

//
// Structure used to track each outstanding I/O. The maximum
// number of I/Os that will be outstanding at any time is
// controllable by the MAX_CONCURRENT_IO definition.
//

#define MAX_CONCURRENT_IO 20

typedef struct _COPY_CHUNK {
OVERLAPPED Overlapped;
LPVOID Buffer;
} COPY_CHUNK, *PCOPY_CHUNK;

COPY_CHUNK CopyChunk[MAX_CONCURRENT_IO];

//
// Define the size of the buffers used to do the I/O.
// 64K is a nice number.
//
#define BUFFER_SIZE (64*1024)

//
// The system's page size will always be a multiple of the
// sector size. Do all I/Os in page-size chunks.
//
DWORD PageSize;


//
// Local function prototype
//
VOID
CopyLoop(
ULARGE_INTEGER FileSize
);

int
_CRTAPI1
main(
int argc,
char *argv[],
char *envp
)
{
ULARGE_INTEGER FileSize;
ULARGE_INTEGER InitialSize;
BOOL Success;
DWORD Status;
DWORD StartTime, EndTime;
SYSTEM_INFO SystemInfo;
HANDLE BufferedHandle;

if (argc != 3) {
fprintf(stderr, "Usage: %s SourceFile DestinationFile\n", argv[0]);
exit(1);
}

//
//confirm we are running on Windows NT 3.5 or greater, if not, display notice and
//terminate. Completion ports are only supported on Win32 & Win32s. Creating a
//Completion port with no handle specified is only supported on NT 3.51, so we need
//to know what we're running on. Note, Win32s does not support console apps, thats
//why we exit here if we are not on Windows NT.
//
//
//ver.dwOSVersionInfoSize needs to be set before calling GetVersionInfoEx()
//
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

//
//Failure here could mean several things 1. On an NT system,
//it indicates NT version 3.1 because GetVersionEx() is only
//implemented on NT 3.5. 2. On Windows 3.1 system, it means
//either Win32s version 1.1 or 1.0 is installed.
//
Success = GetVersionEx((LPOSVERSIONINFO) &ver);

if ( (!Success) || //GetVersionEx() failed - see above.
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) ) //GetVersionEx() succeeded but we are not on NT.
{
MessageBox(NULL,
"This sample application can only be run on Windows NT. 3.5 or greater\n"
"This application will now terminate.",
"UnBufCp1",
MB_OK |
MB_ICONSTOP |
MB_SETFOREGROUND );
exit( 1 );
}


//
// Get the system's page size.
//
GetSystemInfo(&SystemInfo);
PageSize = SystemInfo.dwPageSize;

//
// Open the source file and create the destination file.
// Use FILE_FLAG_NO_BUFFERING to avoid polluting the
// system cache with two copies of the same data.
//

SourceFile = CreateFile(argv[1],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
NULL);
if (SourceFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "failed to open %s, error %d\n", argv[1], GetLastError());
exit(1);
}
FileSize.LowPart = GetFileSize(SourceFile, &FileSize.HighPart);
if ((FileSize.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) {
fprintf(stderr, "GetFileSize failed, error %d\n", GetLastError());
exit(1);
}

DestFile = CreateFile(argv[2],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
SourceFile);
if (DestFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "failed to open %s, error %d\n", argv[2], GetLastError());
exit(1);
}

//
// Extend the destination file so that the filesystem does not
// turn our asynchronous writes into synchronous ones.
//
InitialSize.QuadPart = (FileSize.QuadPart + PageSize - 1) & ~(PageSize-1);
Status = SetFilePointer(DestFile,
InitialSize.LowPart,
(PLONG)&InitialSize.HighPart,
FILE_BEGIN);
if ((Status == 0xffffffff) && (GetLastError() != NO_ERROR)) {
fprintf(stderr, "initial SetFilePointer failed, error %d\n", GetLastError());
exit(1);
}
Success = SetEndOfFile(DestFile);
if (!Success) {
fprintf(stderr, "SetEndOfFile failed, error %d\n", GetLastError());
exit(1);
}

//
//In NT 3.51 it is not necessary to specify the FileHandle parameter
//of CreateIoCompletionPort()--It is legal to specify the FileHandle
//as INVALID_HANDLE_VALUE. However, for NT 3.5 an overlapped file
//handle is needed.
//
//We know already that we are running on NT, or else we wouldn't have
//gotten this far, so lets see what version we are running on.
//
if (ver.dwMajorVersion == 3 && ver.dwMinorVersion == 50) //we're running on NT 3.5 - Completion Ports exists.
IoPort = CreateIoCompletionPort(SourceFile, //file handle to associate with I/O completion port.
NULL, //optional handle to existing I/O completion port.
ReadKey, //completion key.
1); //# of threads allowed to execute concurrently.
else
{
// //we are running on NT 3.51 or greater
//Create the I/O Completion Port
//
IoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,//file handle to associate with I/O completion port
NULL, //optional handle to existing I/O completion port
ReadKey, //completion key
1); //# of threads allowed to execute concurrently


if (IoPort == NULL) {
fprintf(stderr, "failed to create ReadPort, error %d\n", GetLastError());
exit(1);
}

//
//If we need to, aka we're running on NT 3.51, let's associate a file handle with the
//completion port.
//

IoPort = CreateIoCompletionPort(SourceFile,
IoPort,
ReadKey,
1);

if (IoPort == NULL)
{
fprintf(stderr,
"failed to create IoPort, error %d\n",
GetLastError());

exit(1);

}
}

//
// Associate the destination file handle with the
// I/O completion port.
//

IoPort = CreateIoCompletionPort(DestFile,
IoPort,
WriteKey,
1);
if (IoPort == NULL) {
fprintf(stderr, "failed to create WritePort, error %d\n", GetLastError());
exit(1);
}



StartTime = GetTickCount();

//
// Do the copy
//
CopyLoop(FileSize);

EndTime = GetTickCount();

CloseHandle(SourceFile);
CloseHandle(DestFile);

//
// We need another handle to the destination file that is
// opened without FILE_FLAG_NO_BUFFERING. This allows us to set
// the end-of-file marker to a position that is not sector-aligned.
//
BufferedHandle = CreateFile(argv[2],
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (BufferedHandle == INVALID_HANDLE_VALUE) {
fprintf(stderr,
"failed to open buffered handle to %s, error %d\n",
argv[2],
GetLastError());
exit(1);
}


//
// Set the destination's file size to the size of the
// source file, in case the size of the source file was
// not a multiple of the page size.
//
Status = SetFilePointer(BufferedHandle,
FileSize.LowPart,
(PLONG)&FileSize.HighPart,
FILE_BEGIN);
if ((Status == 0xffffffff) && (GetLastError() != NO_ERROR)) {
fprintf(stderr, "final SetFilePointer failed, error %d\n", GetLastError());
exit(1);
}
Success = SetEndOfFile(BufferedHandle);
if (!Success) {
fprintf(stderr, "SetEndOfFile failed, error %d\n", GetLastError());
exit(1);
}
CloseHandle(BufferedHandle);

printf("%d bytes copied in %.3f seconds\n",
FileSize.LowPart,
(float)(EndTime-StartTime)/1000.0);
printf("%.2f MB/sec\n",
((LONGLONG)FileSize.QuadPart/(1024.0*1024.0)) / (((float)(EndTime-StartTime)) / 1000.0));

return(0);

}

VOID
CopyLoop(
ULARGE_INTEGER FileSize
)
{
ULARGE_INTEGER ReadPointer;
BOOL Success;
DWORD NumberBytes;
LPOVERLAPPED CompletedOverlapped;
DWORD Key;
PCOPY_CHUNK Chunk;
int PendingIO = 0;
int i;

//
// Start reading the file. Kick off MAX_CONCURRENT_IO reads, then just
// loop waiting for I/O to complete.
//
ReadPointer.QuadPart = 0;

for (i=0; i < MAX_CONCURRENT_IO; i++) {
if (ReadPointer.QuadPart >= FileSize.QuadPart) {
break;
}
//
// Use VirtualAlloc so we get a page-aligned buffer suitable
// for unbuffered I/O.
//
CopyChunk[i].Buffer = VirtualAlloc(NULL,
BUFFER_SIZE,
MEM_COMMIT,
PAGE_READWRITE);
if (CopyChunk[i].Buffer == NULL) {
fprintf(stderr, "VirtualAlloc %d failed, error %d\n",i, GetLastError());
exit(1);
}
CopyChunk[i].Overlapped.Offset = ReadPointer.LowPart;
CopyChunk[i].Overlapped.OffsetHigh = ReadPointer.HighPart;
CopyChunk[i].Overlapped.hEvent = NULL; // not needed

Success = ReadFile(SourceFile,
CopyChunk[i].Buffer,
BUFFER_SIZE,
&NumberBytes,
&CopyChunk[i].Overlapped);

if (!Success && (GetLastError() != ERROR_IO_PENDING)) {
fprintf(stderr,
"ReadFile at %lx failed, error %d\n",
ReadPointer.LowPart,
GetLastError());
} else {
ReadPointer.QuadPart += BUFFER_SIZE;
++PendingIO;
}
}

//
// We have started the initial async. reads, enter the main loop.
// This simply waits until an I/O completes, then issues the next
// I/O. When a write completes, the next read is issued. When a
// read completes, the corresponding write is issued.
//
while (PendingIO) {
Success = GetQueuedCompletionStatus(IoPort,
&NumberBytes,
&Key,
&CompletedOverlapped,
(DWORD)-1);
if (!Success && (CompletedOverlapped == NULL)) {
//
// The call has failed.
//
fprintf(stderr,
"GetQueuedCompletionStatus on the IoPort failed, error %d\n",
GetLastError());
exit(1);
}
if (!Success) {
//
// The call has succeeded, but the initial I/O operation
// has failed.
//
fprintf(stderr,
"GetQueuedCompletionStatus on the IoPort removed a failed\n"
"I/O packet, error %d\n",GetLastError());
//
// This is probably bad, but what the heck, it's only a demo program,
// so proceed blindly ahead.
//
}

Chunk = (PCOPY_CHUNK)CompletedOverlapped;

if (Key == ReadKey) {

//
// A read has completed, issue the corresponding write.
//

//
// Round the number of bytes to write up to a sector boundary.
//
NumberBytes = (NumberBytes + PageSize - 1) & ~(PageSize-1);

Success = WriteFile(DestFile,
Chunk->Buffer,
NumberBytes,
&NumberBytes,
&Chunk->Overlapped);

if (!Success && (GetLastError() != ERROR_IO_PENDING))
fprintf(stderr,
"WriteFile at %lx failed, error %d\n",
Chunk->Overlapped.Offset,
GetLastError());

//
// decrement pending I/O count
//
--PendingIO;

} else if (Key == WriteKey) {

//
// A write has completed, issue the next read.
//

if (ReadPointer.QuadPart < FileSize.QuadPart) {
Chunk->Overlapped.Offset = ReadPointer.LowPart;
Chunk->Overlapped.OffsetHigh = ReadPointer.HighPart;
ReadPointer.QuadPart += BUFFER_SIZE;
Success = ReadFile(SourceFile,
Chunk->Buffer,
NumberBytes,
&NumberBytes,
&Chunk->Overlapped);
if (!Success && (GetLastError() != ERROR_IO_PENDING)) {
fprintf(stderr,
"ReadFile at %lx failed, error %d\n",
Chunk->Overlapped.Offset,
GetLastError());
}
} else {
//
// There are no more reads left to issue, just
// wait for the pending writes to drain.
//
--PendingIO;
}
}
}
}