Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Операционные Системы

.pdf
Скачиваний:
37
Добавлен:
02.03.2016
Размер:
1.94 Mб
Скачать

Для постановки запроса ввода-вывода в очередь драйвера устройства, используются функции ReadFile и WriteFile, где последний параметр указывает на заполненную структуру OVERLAPPED (см. Таблица 17).

Таблица 17. Структура OVERLAPPED.

typedef struct _OVERLAPPED {

//[out] Код ошибки

DWORD Internal;

//[out] Число переданных байт

DWORD InternalHigh;

//[in] младшие 32 бита смещения

DWORD Offset; указателя

//[in] старшие 32 бита смещения указателя

DWORD OffsetHigh;

//[in] Событие, связанное с операцией

HANDLE hEvent;

}OVERLAPPED, *LPOVERLAPPED;

DWORD Offset и OffsetHigh формируют начальную позицию, с которой будет происходить операция ввода-вывода.

HANDLE hEvent описатель события, которое перейдѐт в сигнальное состояние после окончания операции ввода-вывода. Если используется оповестительный (см. далее) вводвывод может содержать любое значение. Если события нет, то установите в NULL.

DWORD Internal служит для хранения кода ошибки операции ввода вывода.

DWORD InternalHigh после окончания операции содержит число переданных байт.

Уведомления о завершении ввода-вывода

Сигнализация объекта ядра «файл»

Каждая операция ввода-вывода над устройством переводит его в занятое состояние, с которым можно синхронизироваться Wait-функциями.

Пример 24. Синхронизация с объектом ядра «файл».

//Открываем файл для асинхронных операций

HANDLE hfile = CreateFile(...,FILE_FLAG_OVERLAPPED,...);

//Буфер для чтения

BYTE bBuffer[100];

//Будем читать начиная с позиции 345

OVERLAPPED o = { 0 }; o.Offset = 345;

//Читаем 100 байт

81

BOOL fReadDone = ReadFile(hfile, bBuffer, 100, NULL,&o);

DWORD dwError = GetLastError();

// Проверяем результат: Пошла асинхронная операция if (!fReadDone && (dwError == ERROR_IO_PENDING))

{

// Ждѐм окончания

WaitForSingleObject(hfile, INFINITE);

}

else if (fReadDone)

{

// Операция завершилась как синхронная

}

else

{

// Операция завершилась с ошибкой

}

Синхронизация с объектом ядра «событие»

С каждой операцией ввода-вывода можно связать объект событие, которое перейдѐт в сигнальное состояние после окончания операции. Описатель события передаѐтся в пятом поле структуры OVERLAPPED.

Пример 25. Синхронизация с объектом ядра «событие».

#define BUFSIZE 4096

//Исходный файл (должен существовать) const char szSrc[] = "c:\\Sample.txt";

//Конечный файл

const char szTgt[] = "c:\\New.txt";

OVERLAPPED oRead = {0, 0, 0, 0, CreateEvent(NULL, FALSE, FALSE, NULL)};

OVERLAPPED oWrite = {0, 0, 1024, 0, CreateEvent(NULL, FALSE, FALSE, NULL)};

// Всегда будем пользоваться VirtualAlloc для буфера char* Buf = (char*) VirtualAlloc(NULL, BUFSIZE,

82

MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

int _tmain()

{

// Открытие заданного файла для чтения

HANDLE hSrc = CreateFile(szSrc, GENERIC_READ, 0, NULL, OPEN_EXISTING,

FILE_FLAG_OVERLAPPED, NULL);

// Создание файла для записи

HANDLE hTgt = CreateFile(szTgt, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,

FILE_FLAG_OVERLAPPED, NULL);

// Если файлы открыты

if ((hSrc != INVALID_HANDLE_VALUE) && (hTgt != INVALID_HANDLE_VALUE))

{

// читаем из исходного файла

BOOL fOK = ReadFile(hSrc, Buf, BUFSIZE, NULL, &oRead);

if (!fOK)

{

if (GetLastError() == ERROR_IO_PENDING)

{

WaitForSingleObject(oRead.hEvent,

INFINITE);

}

else

{

printf("Ошибка чтения!"); CloseHandle(hSrc); return 0;

}

} else printf("Синхронная операция чтения");

// устанавливаем длину целевого файла.

SetFilePointer(hTgt, 1024+oRead.InternalHigh, NULL, FILE_BEGIN);

83

SetEndOfFile(hTgt);

// Записываем число байт, считанных ReadFile fOK = WriteFile(hTgt, Buf, oRead.InternalHigh,

NULL, &oWrite); if (!fOK)

{

if (GetLastError() == ERROR_IO_PENDING)

{

WaitForSingleObject(oWrite.hEvent,

INFINITE);

}

else

{

printf("Ошибка записи!"); CloseHandle(hTgt); return 0;

}

} else printf("Синхронная операция записи");

}

Оповестительный ввод–вывод

Создавая поток, система связывает с ним очередь асинхронно вызываемых процедур (APC – asynchronous procedure call). Выдавая запрос ввода-вывода, вы можете указать драйверу устройства, что необходимо занести запись в APC-очередь вызывающего потока. Для этого применяются две функции ReadFileEx и WriteFileEx:

Таблица 18. Прототипы функций Read Fi le E x и W ri t eFi le E x .

BOOL ReadFileEx(

HANDLE hfile,

PVOID pvBuffer,

DWORD nNumBytesToRead,

OVERLAPPED* pOverlapped,

LPOVERLAPPED_COMPLETION_ROUTINE

pfnCompletionRoutine);

BOOL WriteFileEx(

84

HANDLE hfile, CONST VOID *pvBuffer,

DWORD nNumBytesToWrite, OVERLAPPED* pOverlapped,

LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine);

Первые четыре параметра функций совпадают с параметрами ReadFile и WriteFile, в пятом параметре передаѐтся указатель на процедуру APC:

Таблица 19. Прототип процедуры APC.

VOID WINAPI CompletionRoutine(

DWORD dwError,

DWORD dwNumBytes,

OVERLAPPED* po);

DWORD dwError – код ошибки операции ввода-вывода. DWORD dwNumBytes – число переданных байт.

OVERLAPPED* po – указатель на структуру OVERLAPPED.

Когда подсистема ввода-вывода закончит обработку запроса, APC– процедура не вызывается до тех пор, пока поток не перейдѐт в так называемое «тревожное» состояние. Это состояние говорит системе о том, что поток готов обработать все процедуры из APC–очереди. В это состояние поток переходит, вызвав одну из следующих функций:

SleepEx(DWORD dwTime, BOOL fAlertable);

WaitForSingleObjectEx(HANDLE hObject, DWORD dwTime, BOOL fAretable);

WaitForMultipleObjectsEx(DWORD dwObjects, PHANDLE phObjects, BOOL fWaitAll, DWORD dwTimeOut, BOOL fAlertable);

Последний параметр нужно установить в TRUE для перевода потока в «тревожное» состояние. Все функции останавливают поток либо до появления события, либо до появления процедур в APC–очереди. Если в очереди существует хотя бы одна APC–процедура поток не останавливается, а переключается на еѐ выполнение.

Пример 26. Использование оповестительного вводавывода.

#define BUFSIZE 4096

// Исходный файл (должен существовать)

85

const char szSrc[] = "e:\\Sample.txt"; // Конечный файл

const char szTgt[] = "e:\\New.txt";

OVERLAPPED oRead = {0, 0, 0, 0, NULL}; OVERLAPPED oWrite = {0, 0, 1024, 0, NULL};

// Открытие заданного файла для чтения

HANDLE hSrc = CreateFile(szSrc, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

// Создание файла для записи

HANDLE hTgt = CreateFile(szTgt, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);

//Процедура обработки завершения записи в файл

VOID CALLBACK IoWriteComplete( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped )

{

printf("Запись закончена!"); CloseHandle(hTgt);

}

//Процедура обработки завершения чтения из файла

VOID CALLBACK IoReadComplete( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped

)

{

printf("Чтение закончено"); CloseHandle(hSrc);

}

int _tmain()

{

// Всегда будем пользоваться VirtualAlloc для буфера char* Buf = (char*) VirtualAlloc(NULL, BUFSIZE,

MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);

// Если файлы открыты

86

if ((hSrc != INVALID_HANDLE_VALUE) && (hTgt != INVALID_HANDLE_VALUE))

{

// читаем из исходного файла

BOOL fOK = ReadFileEx(hSrc, Buf, BUFSIZE, &oRead, IoReadComplete);

//Переходим в тревожное состояние

SleepEx(INFINITE, TRUE); SetFilePointer(hTgt, 1024+oRead.InternalHigh,

NULL, FILE_BEGIN); SetEndOfFile(hTgt);

//Записываем в целевой файл

WriteFileEx(hTgt, Buf, oRead.InternalHigh,

&oWrite, IoWriteComplete);

// Переходим в тревожное состояние

SleepEx(INFINITE, TRUE);

}

// Освобождаем буфер

VirtualFree(Buf, 0, MEM_RELEASE);

}

Порты завершения ввода-вывода

Основная проблема с оповестительным вводом-выводом состоит в невозможности обработки результатов запроса в контексте другого потока. Другие методы позволяют это делать, но это сопровождается существенными сложностями в программировании и отладке этих приложений. Кроме того, одновременная обработка несколькими потоками результатов операций ввода-вывода может привести к существенному падению производительности системы из-за необходимости частого контекстного переключения между работающими потоками. Для решения этих проблем разработан один из самых сложных объектов ядра – порт завершения ввода-

вывода (Input/Otput Completion Port).

Суть метода состоит в следующем. Каждое устройство ввода-вывода связывается с объектом ядра «порт завершения». Порт завершения держит «наготове» пул потоков, которые могут обработать результаты запроса. При

87

завершении операции ввода-вывода, еѐ результаты помещаются в очередь порта. Если число запущенных потоков меньше некоторого ограничения, которое задаѐтся при создании порта, порт ввода вывода активизирует один поток из пула и даѐт ему обработать операцию ввода-вывода. Система реализует LIFO принцип для активизации потоков пула, это значит, что следующий результат ввода-вывода будет обработан тем же потоком.

На сегодняшний день порт ввода-вывода реализует самый эффективный и масштабируемый способ организации асинхронных операций ввода-вывода в системах Windows NT.

Для организации работы с портом завершения ввода – вывода выполните следующие шаги:

1.Создайте объекты «файл» с флагом FILE_FLAG_OVERLAPPED.

2.Создайте объект «порт ввода-вывода» и свяжите его с объектами «файл».

3.Создайте пул потоков, ожидающих результаты ввода-вывода.

4.Используйте функции ReadFile и WriteFile для работы с устройством. Все результаты запросов будут попадать в очередь порта ввода-вывода и обрабатываться потоками из вашего пула.

Таблица 20. Функция Cr ea teIo Co ml et io nP o rt .

HANDLE CreateIoCompletionPort(

HANDLE

hfile,

HANDLE

hExistingCompPort,

ULONG_PTR CompKey,

DWORD

dwNumberOfConcurrentThreads);

HANDLE

hfile – описатель файла для ассоциации с портом

HANDLE

hExistingCompPort – описатель существующего порта для связывания с

ним файла.

 

ULONG_PTR CompKey – некоторое число, которое связано с файлом

DWORD dwNumberOfConcurrentThreads – максимальное число одновременно запущенных потоков. Если передать 0, это число устанавливается равным числу процессоров в системе.

Потоки в пуле должны «спать», пока не появится выполненный запрос ввода-вывода. Это достигается функцией GetQueuedCompletionStatus.

Таблица 21. Функция Ge t Q ue ued Co mp le tio n St at u s .

BOOL GetQueuedCompletionStatus(

HANDLE

hCompPort,

PDWORD

pdwNumBytes,

PULONG_PTR CompKey,

OVERLAPPED** ppOverlapped,

DWORD

dwMilliseconds);

HANDLE

hCompPort – описатель порта ввода-вывода

 

88

PDWORD pdwNumBytes – число переданных байтов PULONG_PTR CompKey – ключ операции ввода-вывода.

OVERLAPPED** ppOverlapped – структура OVERLAPPED, связанная с операцией. DWORD dwMilliseconds – как долго ждать появления результатов запроса.

Возвращаемые значения

TRUE, если появился результат операции за заданное время.

FALSE – в случае ошибки. Если выход по тайм-ауту, то GetLastError() вернѐт

WAIT_TIMEOUT.

Ещѐ одна полезная функция PostQueuedCompletionStatus

предназначена для симуляции завершения операции ввода-вывода. Еѐ удобно использовать для подачи команд в порт ввода-вывода, например, для завершения потоков в пуле.

Таблица 22. Функция P o st Co mp let io n Sta t u s .

BOOL PostQueuedCompletionStatus(

HANDLE hCompPort,

DWORD dwNumBytes,

ULONG_PTR CompKey,

OVERLAPPED* pOverlapped);

HANDLE

hCompPort – описатель порта ввода-вывода

DWORD

dwNumBytes – число переданных байт.

ULONG_PTR

CompKey – ключ операции ввода-вывода.

OVERLAPPED*

pOverlapped – Указатель на структуру OVERLAPPED

Пример 27. Использование порта завершения ввода-вывода.

#define BUFSIZE 4096

#define POOL 2 // Число потоков в пуле #define CK_READ 0 //Ключ: чтение закончено #define CK_WRITE 1 // Ключ: запись закончена #define CK_EXIT 0xFF // Ключ: выход из потока

//Исходный файл (должен существовать) const char szSrc[] = "e:\\Sample.txt";

//Конечный файл

const char szTgt[] = "e:\\New.txt";

OVERLAPPED ovr = {0};

// Всегда будем пользоваться VirtualAlloc для буфера char* Buf = (char*) VirtualAlloc(NULL, BUFSIZE,

MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// Открытие заданного файла для чтения

89

HANDLE hSrc = CreateFile(szSrc, GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

// Создание файла для записи

HANDLE hTgt = CreateFile(szTgt, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);

//Описатель порта ввода-вывода

HANDLE hio = NULL;

//Функция пула потоков обработки результатов вводавывода

DWORD __stdcall IoComplete(LPVOID)

{

BOOL fOK;

DWORD dwBytes, dwKey; LPOVERLAPPED pOvr;

for (;;)

{

// Ждѐм поступления в очередь результата ввода-вывода fOK = GetQueuedCompletionStatus(hio, &dwBytes,

&dwKey, &pOvr, INFINITE); if (fOK)

{

// Проверяем, что завершилось switch (dwKey)

{

//Завершилось чтение case CK_READ:

//Записываем результат чтения в целевой файл pOvr->Offset=1024;

WriteFile(hTgt, Buf, dwBytes, NULL, pOvr);

break;

// Завершилась запись case CK_WRITE:

//Помещаем в очередь сообщения команду на выход из //потока.

90