Демонстрационное приложение FileCopy
октября 12 2009 by admin in Обязательный материалFileCopy ('02 FileCopy.exe') демонстрирует применение порта завершения ввода-вывода (листинг 2-1; исходный текст и файлы ресурсов этого приложения см. в каталоге 02-FileCopy на прилагаемом компакт-диске). Эта программа просто копирует указанный пользователем файл в файл FileCopy.cpy.
Пользователь нажимает кнопку Pathname (Полный путь к файлу), чтобы выбрать копируемый файл, после чего обновляется содержимое полей Pathname и File Size (Размер файла). Когда пользователь нажимает кнопку Сору (Копировать), программа вызывает функцию FileCopy, которая и выполняет всю основную работу. Так что давайте сосредоточимся на функции FileCopy.
При подготовке к копированию FileCopy открывает исходный файл и определяет его размер в байтах. Я хочу, чтобы скорость копирования ошеломляла, поэтому я открываю файл, установив флаг FILE_FLAG_NO_BUFFERING, что дает мне прямой доступ к файлу без дополнительного копирования, которое имеет место при использовании кэша системы, «помогающего» обратиться к файлу. При прямом доступе к файлу на меня, конечно же, ложится дополнительная нагрузка: каждый раз придется указывать смещение, кратное размеру сектора диска, считывать и записывать данные порциями, кратными размеру сектора. Я решил обмениваться данными порциями по 64 Кбайт (определив соответствующее значение BUFFSIZE), что гарантирует кратность размеру сектора. Поэтому я округляю размер файла в большую сторону до значения, кратного 64 Кбайт. Заметьте: исходный файл открывается с флагом FILE_FLAG_OVERLAPPED, т. е. запросы ввода-вывода для этого файла должны выполняться асинхронно.
Выходной файл открывается аналогично: устанавливается как флаг FILE_ FLAG_NO_BUFFERING, так и флаг FILE_FLAG_OVERLAPPED. Кроме того, при создании выходного файла я передаю в качестве параметра hfileTemplate функции CreateFile описатель исходного файла, чтобы выходной файл имел те же атрибуты, что и исходный.
Сразу после открытия обоих файлов размер выходного файла устанавливается равным максимально возможному с помощью функций SetFilePointerEx и SetEndOJFile. Установить размер файла в этот момент исключительно важно, так как NTFS использует маркер верхней границы файла, указывающий точку, до которой записан файл. Если считывать данные за этим маркером система пернет нули. При записи за этим маркером сначала будет заполнено нулями пространство от старого значения маркера до нового, равного сумме старого значения и числа записываемых байт, а затем файл будет заполнен записываемыми данными. Такой алгоритм соответствует критериям безопасности С2, согласно которым на диске не должны сохраняться предыдущие данные. Если вы записываете данные на раздел в формате NTFS, начиная с конца существующего файла, для чего требуется сдвиг маркера верхней границы файла, NTFS вынуждена будет выполнять синхронную операцию, даже если запрашивался асинхронный ввод-вывод. Если в функции FileCopy не установить размер выходного файла, ни один из перекрывающихся запросов ввода-вывода не будет выполнен асинхронно.
Теперь, когда файлы открыты и готовы для обработки, FileCopy создает порт завершения ввода-вывода. Чтобы облегчить работу с портом завершения ввода-вывода, я создал СЮСР — небольшой класс C++, который является простейшей оболочкой для функций порта завершения ввода-вывода. (См. этот класс в файле IOCP.h в приложении Б.) Функция FileCopy создает порт завершения ввода-вывода, создавая экземпляр класса СЮСР с именем iocp.
Порт завершения ввода-вывода ставится в соответствие исходному и выходному файлам вызовом метода AssociateDevice класса СЮСР. Когда устанавливается соответствие между устройством и портом завершения, устройству назначается ключ завершения. При завершении запроса ввода-вывода для исходного файла ключ завершения CK_READ указывает, что должна была завершиться операция чтения. В свою очередь, когда завершается запрос ввода-вывода для выходного файла, ключ завершения CK_WRITE указывает, что должна была завершиться операция записи.
Теперь мы готовы инициализировать совокупность запросов ввода-вывода (структуры OVERLAPPED) и их буферы памяти. FileCopy управляет четырьмя (MAX_PENDING_JO_REQS) запросами ввода-вывода, которые одновременно ожидают выполнения. В своем приложении вы можете при необходимости динамически увеличивать или уменьшать это число запросов ввода-вывода. В программе FileCopy класс CIOReq инкапсулирует единственный запрос ввода-вывода. Как видите, этот класс C++ является производным от структуры OVERLAPPED, но содержит и дополнительную смысловую информацию. File-Copy выделяет массив объектов CIOReq и вызывает метод AllocBuffer, ставя в ' соответствие каждому объекту запроса ввода-вывода буфер с размером BUFF-SIZE. Буфер выделяется функцией VirtualAlloc, которая гарантирует, что буфер будет начинаться с адреса, кратного размеру дискового сектора, т. е. отвечать требованиям флага FILE__FLAG_NO_BUFFERING.
Чтобы выдать начальные запросы чтения исходного файла, я делаю небольшой трюк: помещаю 4 уведомления о завершении ввода-вывода с ключом CK_WRITE в порт завершения. В основном цикле поток приступает к ожиданию в порте, но сразу же продолжает выполнение, считая, что завершилась операция записи. В итоге поток выдает запрос чтения исходного файла, т. е. действительно начинается копирование файла.
Основной цикл прерывается, когда не остается ожидающих выполнения запросов ввода-вывода. Пока такие запросы остаются, внутри цикла производится ожидание в порте завершения ввода-вывода с помощью метода GetStatus класса СЮСР (который в свою очередь обращается к функции GetQueued-CompletionStatus). Ожидание продолжается, пока не выполнятся запросы в порте завершения ввода-вывода. При возврате из GetQueuedCompletionStatus проверяется ключ завершения СотрКеу. Значение ключа CK_READ показывает, что завершился запрос ввода-вывода, относящийся к исходному файлу, a CK_WRITE — что завершился запрос, относящийся к выходному файлу. Если исходный файл не прочитан до конца, я вызываю метод Read класса CIOReq, продолжая чтение исходного файла.
Если ожидающих выполнения запросов ввода-вывода не осталось, цикл прерывается, и освобождаются ресурсы путем закрытия исходного и выходного файлов. Прежде чем выйти из функции FileCopy, выполняется еще одна важная задача: размер выходного файла изменяется так, чтобы он совпадал с размером исходного. Для этого выходной файл повторно открывается со сброшенным флагом FILE_FLAG_NO_BUFFERING — при этом не требуется, чтобы выполнялись операции с целыми секторами. Это позволяет уменьшить размер выходного файла.