- •1. Проецируемые в память файлы
- •2. Проецирование в память exe- и dll-файлов
- •2.1. Статические данные разделяются несколькими экземплярами exe или dll
- •2.2. Использование общего раздела в приложениях для межпроцессного взаимодействия
- •3. Использование проецируемых в память файлов
- •3.1. Создание/открытие объекта ядра «файл»
- •3.2. Создание объекта ядра – «проекция файла»
- •3.3. Проецирование файловых данных на адресное пространство процесса
- •3.5. Закрытие объектов «проекция файла» и «файл»
- •3.4. Отключение файла данных от адресного пространства процесса
- •4. Файлы, проецируемые на физическую память из страничного файла
- •5. Порядок выполнения работы
- •6. Контрольные вопросы
2.1. Статические данные разделяются несколькими экземплярами exe или dll
Когда создается новый процесс для уже выполняемого приложения, система просто открывает другое проецируемое в память представление (view) объекта «проекция файла» (file-mapping object), идентифицирующего образ исполняемого файла, и создает новые объекты «процесс» и «поток» (для первичного потока). Этим объектам присваиваются идентификаторы процесса и потока. С помощью проецируемых в память файлов несколько одновременно выполняемых экземпляров приложения может совместно использовать один и тот же код, загруженный в оперативную память. Здесь возникает небольшая проблема. Процессы используют линейное (flat) адресное пространство. При компиляции и компоновке программы весь ее код и данные объединяются в нечто, так сказать, большое и цельное. Данные, конечно, отделены от кода, но только в том смысле, что они расположены вслед за кодом в ЕХЕ-файле. На самом деле содержимое файла разбито на отдельные разделы (sections). Код находится в одном разделе, а глобальные переменные — в другом. Разделы выравниваются по границам страниц. Приложение определяет размер страницы через функцию GetSystemInfo. В EXE- или DLL-файле раздел кода обычно предшествует разделу данных.
Вот упрощенная иллюстрация того, как код и данные приложения загружаются в виртуальную память, а затем отображаются на адресное пространство процесса:
Рисунок 2.1. Адресное пространство одного экземпляра программы
Теперь допустим, что запущен второй экземпляр программы. Система проецирует страницы виртуальной памяти, содержащие код и данные файла, на адресное пространство второго экземпляра приложения:
Рисунок 2.2. Адресное пространство двух экземпляров программы
Если один экземпляр приложения модифицирует какие-либо глобальные переменные, размещенные на странице данных, содержимое памяти изменяется для всех экземпляров этого приложения. Такое изменение могло бы привести к катастрофическим последствиям и поэтому недопустимо. Система предотвращает подобные ситуации, применяя механизм копирования при записи. Всякий раз, когда программа пытается записывать что-то в файл, спроецированный в память, система перехватывает эту попытку, выделяет новый блок памяти, копирует в него нужную программе страницу и после этого разрешает запись в новый блок памяти. Благодаря этому работа остальных экземпляров программы не нарушается. Вот что получится, когда первый экземпляр программы попытается изменить какую-нибудь глобальную переменную на второй странице данных:
Рисунок 2.1. Адресное пространство двух экземпляров программы при изменении глобальных данных одним из экземпляров программы
Система выделяет новую страницу и копирует на нее содержимое страницы данных 2. Адресное пространство первого экземпляра изменяется так, чтобы отобразить новую страницу данных на тот же участок, что и исходную. Теперь процесс может изменить глобальную переменную, не затрагивая данные другого экземпляра.
Аналогичная цепочка событий происходит и при отладке приложения. Например, запустив несколько экземпляров программы, необходимо отладить только один из них. Вызвав отладчик, ставится в строке исходного кода точку прерывания. Отладчик модифицирует код, заменяя одну из команд на языке ассемблера другой — заставляющей активизировать сам отладчик. После модификации кода все экземпляры программы, доходя до исполнения измененной команды, приводили бы к его активизации. Чтобы этого избежать, система вновь использует копирование при записи. Обнаружив попытку отладчика изменить код, она выделяет новый блок памяти, копирует туда нужную страницу и позволяет отладчику модифицировать код на этой копии.
