Концепция механизма отображения файлов в память
Прежде чем перейти к описанию динамически подключаемых библиотек, рассмотрим механизм, который позволяет динамически выполнять это подключение. Этот механизм называется отображением содержимого файла (file mapping) в виртуальную память процесса.
Операционная система создает файлы подкачки с виртуальными страницами, которые система отображает в адресные пространства процессов. В операционных системах Windows реализован механизм, который позволяет отображать в адресное пространство процесса не только содержимое файлов подкачки, но и содержимое обычных файлов. То есть в этом случае файл или его часть рассматривается как набор виртуальных страниц процесса, которые имеют последовательные логические адреса. Файл, отображенный в адресное пространство процесса, называется представлением или видом файла (file view). После отображения файла в адресное пространство процесса доступ к виду может осуществляться с помощью указателя, как к обычным данным в адресном пространстве процесса.
Несколько процессов могут одновременно отображать один и тот же файл в свое адресное пространство. В этом случае операционная система обеспечивает согласованность содержимого файла для всех процессов, если доступ к этим данным осуществляется как к области виртуальной памяти процесса. То есть для доступа к файлу, который отображен в память, не используется функция writeFiie. Такая согласованность данных, хранящихся в файле, отображенном в память несколькими процессами, называется когерентностью данных. Однако следует отметить, что когерентность данных для файла, отображенного в память, не поддерживается в том случае, если этот файл отображается в адресное пространство процессов, которые выполняются на других компьютерах в локальной сети.
Из вышесказанного можно сделать вывод, что основным назначением механизма отображения файлов в память является загрузка программы (файла с расширением ехе) на выполнение, в адресном пространстве процесса, и динамическое подключение библиотек функций во время выполнения этой программы.
Кроме того, механизм отображения файлов в память позволяет осуществлять обмен данными между процессами, принимая во внимание то, что система обеспечивает когерентность данных в файле, отображаемом в память.
Теперь кратко опишем общую последовательность действий, которые необходимо выполнить для работы с отображаемым в память файлом. Эти действия могут быть разбиты на следующие шаги:
□ открыть файл, который будет отображаться в память;
□ создать объект ядра, который выполняет отображение файла;
□ отобразить файл или его часть в адресное пространство процесса;
□ выполнить необходимую работу с видом файла;
□ отменить отображение файла;
□ закрыть объект ядра для отображения файла;
□ закрыть файл, который отображался в память.
Создание и открытие объекта, отображающего файл
Если в память отображается существующий файл, то первым делом этот файл должен быть открыт для доступа, используя функцию CreateFile. Это делается для того, чтобы получить дескриптор файла, т. к. в дальнейшем этот дескриптор используется при создании объекта, отображающего файл в память процесса. Если отображаемый файл используется просто для обмена данными между процессами, то создавать для этого специальный файл на диске не обязательно. Для этого можно использовать файлы подкачки операционной системы.
После того как файл открыт, создается объект, отображающий этот файл в память. Под объектом, отображающим файл в память, можно понимать объект ядра операционной системы, который выполняет отображение файла в адресное пространство процесса. Можно также представить, что этот объект позволяет рассматривать файл, отображаемый в память, как файл подкачки. Для создания этого объекта используется функция CreateFileMapping, которая имеет следующий прототип:
HANDLE CreateFileMapping(
HANDLE hFile, // дескриптор файла
LPSECURITY_ATTRIBUTES, lpAttributes // атрибуты защиты
DWORD flProtect, // флаги доступа к файлу
DWORD dwMaximumSizeHigh, // старшее двойное слово размера объекта
DWORD dwMaximumSizeLow, // младшее двойное слово размера объекта
LPCTSTR lpName // имя объекта отображения
);
В случае успешного завершения эта функция возвращает дескриптор объекта, отображающего файл в память, а в случае неудачи — null. Параметры этой функции имеют следующее назначение.
Параметр hFile должен содержать дескриптор открытого файла, для которого будет создаваться объект, отображающий этот файл в память процесса.
Параметр lpAttributes, как обычно, указывает на атрибуты защиты для объекта, отображающего файл в память. В этом разделе этот параметр будет всегда устанавливаться в null, задавая, тем самым, атрибуты защиты по умолчанию. То есть в этом случае объект отображения не является наследуемым и принадлежит создавшему его пользователю.
Параметр f lProtect содержит флаги, которые задают режимы доступа к виду файла в памяти процесса. Этот параметр может принимать одно из следующих значений:
□ page_readonly — из вида файла можно только читать данные;
□ page_readwrite — разрешает чтение и запись данных в вид файла;
□ page_writecopy — разрешает чтение и запись данных в вид файла, но при записи создается новая копия вида файла.
Как можно видеть, значения этого параметра совпадают со значениями соответствующего параметра функции virtuaiAiioc, которая распределяет виртуальную память процессу. Отметим, что режимы доступа к объекту, отображающему файл в память, должны соответствовать режимам доступа к файлу, для которого создается этот объект отображения.
Кроме того, в параметре flProtect может быть установлена любая комбинация флагов, которые определяют атрибуты секций исполняемых файлов, заданных в переносимом формате (portable executable files). Эти флаги рассматриваться не будут.
Параметры dwMaximumSizeHigh и dwMaximumSizeLow определяют соответственно значение старшей и младшей частей, которые в совокупности задают размер объекта, отображающего файл в память. Если эти значения установлены в 0, то объект, отображающий файл в память, имеет размер, равный размеру файла. Отметим, что если размер этого объекта будет меньше размера файла, то система не сможет отобразить весь файл в память. Если же размер объекта, отображающего файл, больше чем размер файла, то размер файла увеличивается до размера объекта.
Последний параметр lpName используется для задания имени объекта, отображающего файл в память. Как обычно, это имя используется для доступа к одному и тому же объекту в разных процессах. Если процесс пытается получить доступ к уже созданному объекту, отображающему файл, то флаги доступа, установленные в параметре fiProtect, должны соответствовать флагам доступа, уже установленным в существующем объекте, отображающем файл.
После завершения работы с объектом, отображающим файл в память, его дескриптор нужно закрыть, используя функцию CloseHandle.