Операции с файлами – это то, что рано или поздно приходится делать практически во всех программах, и всегда это вызывает массу проблем.
В Windows многие из этих проблем решаются очень изящно – с помощью проецируемых в память файлов (memory-mapped files).
Как и виртуальная память, проецируемые файлы позволяют резервировать регион адресного пространства и передавать ему физическую память.
Различие между этими механизмами состоит в том, что в последнем случае физическая память не выделяется из страничного файла, а берется из файла, уже находящегося на диске.
Как только файл спроецирован в память, к нему можно обращаться так, будто он целиком в нее загружен.
Проецируемые файлы применяются для:
загрузки и выполнения EXE- и DLL-файлов. Это позволяет существенно экономить как на размере страничного файла, так и на времени, необходимом для подготовки приложения к выполнению;
доступа к файлу данных, размещенному на диске. Это позволяет обойтись без операций файлового ввода-вывода и буферизации его содержимого;
разделения данных между несколькими процессами, выполняемыми на одной машине (в Windows есть и другие методы для совместного доступа разных процессов к одним данным — но все они так или иначе реализованы на основе проецируемых в память файлов).
Этап 1: создание или открытие объекта ядра «файл»
psa – указатель на структуру, содержащую дескриптор защиты (по умолчанию NULL)
dwCreationDisposition – определяет режим работы функции CreateFile. Может принимать следующие значения:
Значение |
Описание |
CREATE_NEW |
Создает новый файл. Если файл с таким именем существует, функция даст ошибку. |
CREATE_ALWAYS |
Создает новый файл независимо от того, существует ли файл с таким же именем. |
OPEN_EXISTING |
Открывает существующий файл. Если файла нет - ошибка. |
OPEN_ ALWAYS |
Открывает существующий файл. Если файла нет - создает новый. |
TRUNCATE_ EXISTING |
Открывает существующий файл и обрезает его длину до 0. |
dwFlagsAndAttributes – атрибуты файлов и флаги устройств. Используйте значение FILE_ATTRIBUTE_NORMAL
hTemplateFile – используйте значение NULL.
Этап 2: создание объекта ядра «проекция файла»
Вызвав CreateFile, Вы указали операционной системе, где находится физическая память для проекции файла на жестком диске в сети, на CD-ROM или в другом месте. Теперь сообщите системе, какой обьем физической памяти нужен проекции файла Для этого вызовите функцию CreateFileMapping:
HANDLE CreateFileMapping( HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWOPD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCSTR pszName);
Функция создает объекта ядра "проекция файла" и возвращает его описатель.
Если это не удалось, возвращает NULL.
hFile – идентифицирует описатель файла, проецируемою на адресное пространство процесса. Этот описатель Вы получили после вызова CreateFile.
Параметр psa – указатель на структуру SECURITY_ATTRIBUTES, которая относится к объекту ядра "проекция файла", для установки защиты по умолчанию ему присваивается NULL.
В fdwProteсt надо указать желательные атрибуты защиты. Обычно используется один из перечисленных в следующей таблице:
Атрибут защиты |
Описание |
PAGE_READONLY |
Отобразив объект «проекция файла» на адресное пространство, можно считывать данные из файла. При этом Вы должны были передать в CreateFile флаг GENERIC_READ. |
PAGE_READWRITE |
Отобразив объект «проекция файла» на адресное пространство, можно считывать данные из файла и записывать их. При этом Вы должны были передать в CreateFile комбинацию флагов GENERIC_READ | GENERIC_WRITE. |
PAGE_WRITECOPY |
Отобразив объект "проекция файла" на адресное пространство, можно считывать данные из файла и записывать их. Запись приведет к созданию закрытой копии страницы. При этом Вы должны были передать в CreateFile либо GENERIC_READ, либо GENERIC_READ | GENERIC_WRITE |
dwMaximumSizeHigh, dwMaximumSizeLow – размер доступной физической памяти, иначе говоря, максимальный размер файла, проецируемого на адресное пространство.
Win32 позволяет работать с файлами, размеры которых выражается 64-х разрядным числом (до 18 экзабайтов ).
Поэтому dwMaximumSizeLow отражает младшие 32 бита, dwMaximumSizeHigh – старшие.
Для отражения текущего состояния файла укажите в обоих параметрах 0. В этом случае Вы не сможете дописывать в файл.
pszName – имя объекта. Обычно NULL.
Этап 3: проецирование файловых данных на адресное пространство процесса
Когда объект "проекция файла“ создан, нужно, чтобы система, зарезервировав регион адресного пространства под данные файла, передала их как физическую память, отображенную на регион. Это делает функция MapViewOfFile:
PVOID MapViewOfFile(HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);
Параметр hFileMappingObject идентифицирует описатель объекта "проекция файла", возвращаемый предшествующим вызовом либо CreateFileMapping, либо OpenFileMapping.
Параметр dwDesiredAccess идентифицирует вид доступа к данным. Придется опять указывать, как именно мы хотим обращаться к файловым данным. Можно задать одно из четырех значений, описанных в следующей таблице:
Значение |
Описание |
FILE_MAP_WRITE |
Файловые данные можно считывать и записывать. Вы должны были передать функции CreateFileMapping атрибут PAGE_READWRITE |
FILE MAP_READ |
Файловые данные можно только считывать. Вы должны были вызвать CreateFileMapping с любым из следующих атрибутов PAGE_READONLY, PAGE_READWRITE или PAGE_WRITECOPY |
FILE_MAP_ALL_ACCESS |
То же, что и FILE_MAP_WRITE |
FILE_MAP_COPY |
Файловые данные можно считывать и записывать, но запись приводит к созданию закрытой копии страницы. Вы должны были вызвать CrealeFileMapping с любым из следующих атрибутов PAGE_READONIY, PAGE_READWRITE или РАСЕ_WRITECOPY |
Проецируя на адресное пространство процесса представление файла, нужно сделать две вещи:
Во-первых, нужно сообщить системе, какой байт файла данных считать в представлении первым Для этого предназначены параметры dwFileOffsetHigh и dwFile OffsetLow. Поскольку Windows поддерживает файлы длиной до 16 экзабайтов, приходится определять смещение в файле как 64 разрядное число. Старшие 32 бита передаются в параметре dwFileOffsetHigh, а младшие 32 бита — в параметре dwFileOffsetLow. Смещение в файле должно быть кратно гранулярности выделения памяти в данной системе.
Во-вторых, нужно указать размер представления, т.e. сколько байтов файла данных должно быть спроецировано на адресное пространство. Размер указывается в параметре dwNumberOfBytesToMap. Если этот параметр равен 0, система попытается спроецировать представление, начиная с указанного смещения и до конца файла.
Этап 4: отключение файла данных от адресного пространства процесса
Когда необходимость в данных файла, спроецированного на адресное пространство, отпадает, необходимо освободить регион
BOOL UnmapViewOfFile(PVOID pvBaseAddress);
lpBaseAddress - базовый адрес региона.
Так как повторный вызов MapViewOfFile приводит к резервированию нового региона, но не освобождению старого, необходимо вызывать UnMapViewOfFile после каждого вызова MapViewOfFile.
Для повышения производительности ОС буферизует страницы данных и не сохраняет их немедленно в дисковом образе файла. При необходимости можно заставить ОС записать все измененные данные на диск
BOOL FlushViewOfFile(PVOID pvAddress, SIZE_T dwNumberOfBytesToFlush);
lpBaseAddress – базовый адрес региона.
dwNumberOfBytesToFlush – кол-во байтов, переписываемых на диск.
Этапы 5 и 6: закрытие объектов «проекция файла» и «файл»
Закончив работу с любым открытым объектом ядра, нужно его закрыть, иначе в процессе начнется утечка ресурсов.
По завершении процесса система автоматически закроет объекты, оставленные открытыми.
Но, если процесс по работает еще какое-то время, может накопиться слишком много незакрытых описателей.
Для закрытия объектов «проекция файла» и «файл» дважды вызовите функцию CloseHandle:
HANDLE hFile = CreateFile(...);
HANDLE hFileMapping = CreateFileMapping(hFile,...);
PVOID pvFilfi = MapViewOfFile(hFileMapping, );
// работаем с файлом, спроецированным в память
UnmapViewOfFile(pvFile);
CloseHandle(hFileMapping);
CloseHandle(hFile);