
- •1. Проецируемые в память файлы
- •2. Проецирование в память exe- и dll-файлов
- •2.1. Статические данные разделяются несколькими экземплярами exe или dll
- •2.2. Использование общего раздела в приложениях для межпроцессного взаимодействия
- •3. Использование проецируемых в память файлов
- •3.1. Создание/открытие объекта ядра «файл»
- •3.2. Создание объекта ядра – «проекция файла»
- •3.3. Проецирование файловых данных на адресное пространство процесса
- •3.4. Отключение файла данных от адресного пространства процесса
- •3.5. Закрытие объектов «проекция файла» и «файл»
- •4. Файлы, проецируемые на физическую память из страничного файла
- •5. Порядок выполнения работы
- •6. Контрольные вопросы
2.2. Использование общего раздела в приложениях для межпроцессного взаимодействия
По умолчанию для большей безопасности глобальные и статические данные не разделяются несколькими проекциями одного и того же EXE или DLL. Но иногда удобнее, чтобы несколько проекций EXE разделяли единственный экземпляр переменной. Например, в Windows не так-то просто определить, запущено ли несколько экземпляров приложения. Если бы была переменная, доступная всем экземплярам приложения, она могла бы отражать число этих экземпляров. Тогда при запуске нового экземпляра приложения его поток просто проверил бы значение глобальной переменной (обновленное другим экземпляром приложения) и, будь оно больше 1, сообщил бы пользователю, что запустить можно лишь один экземпляр; после чего эта копия приложения была бы завершена.
Любой образ EXE- или DLL-файла состоит из группы разделов. По соглашению имя каждого стандартного раздела начинается с точки. Например, при компиляции программы весь код помещается в раздел .text, неинициализированные данные - в раздел .bss, а инициализированные — в раздел .data.
С каждым разделом связана одна из комбинаций атрибутов, перечисленных в следующей таблице.
Таблица 2.1. Атрибуты разделов
Атрибут |
Описание |
READ |
Разрешает чтение из раздела |
WRITE |
Разрешает запись в раздел |
EXECUTE |
Содержимое раздела можно исполнять |
SHARED |
Раздел доступен нескольким экземплярам приложения (этот атрибут отключает механизм копирования при записи) |
Некоторые из часто встречающихся разделов перечислены в таблице ниже:
Таблица 2.2. Типы разделов
Имя раздела |
Описание |
bss |
Неинициализированные данные |
CRT |
Неизменяемые данные библиотеки С |
data |
Инициализированные данные |
.debug |
Отладочная информация |
.didat |
Таблица имен для отложенного импорта (delay imported names table) |
edata |
Таблица экспортируемых имен |
idata |
Таблица импортируемых имен |
.rdata |
Неизменяемые данные периода выполнения |
.reloc |
Настроечная информация — таблица переадресации (relocation table) |
.rsrc |
Ресурсы |
.text |
Код ЕХЕ или DLL |
.tls |
Локальная память потока |
.xdata |
Таблица для обработки исключений |
Кроме стандартных разделов, генерируемых компилятором и компоновщиком, можно создавать свои разделы в EXE- или DLL-файле, используя директиву компилятора:
#pragma data_seg("имя_раздела")
Например, можно создать раздел Shared, в котором содержится единственная переменная типа LONG:
#pragma data_seg("Shared") LONG g_lInstanceCount = 0; #pragma data_seg()
Обрабатывая этот код, компилятор создаст раздел Shared и поместит в него все инициализированные переменные, встретившиеся после директивы #pragma. В приведенном примере в этом разделе находится переменная g_lInstanceCount.. Директива #pragma data_seg() сообщает компилятору, что следующие за ней переменные нужно вновь помещать в стандартный раздел данных, а не в Shared. Важно помнить, что компилятор помещает в новый раздел только инициализированные переменные. Если из предыдущего фрагмента кода исключить инициализацию переменной, она будет включена в другой раздел:
#pragma data_seg("Shared")
LONG g_lInslanceCount; #pragma data_seg()
Однако в компиляторе Microsoft Visual C++ 6.0 предусмотрен спецификатор allocate, который позволяет помещать неинициализированные данные в любой раздел. Взгляните на этот код:
// создаем раздел Shared и заставляем компилятор // поместить в него инициализированные данные #pragma data_seg("Shared")
// инициализированная переменная, по умолчанию помещается в раздел Shared int а = 0;
// неинициализированная переменная, по умолчанию помещается в другой раздел int b;
// сообщаем компилятору прекратить включение инициализированных данных // в раздел Shared #pragma data_seg()
// инициализированная переменная, принудительно помещается в раздел Shared __declspec(allocate("Shared")) int с = 0;
// неинициализированная переменная, принудительно помещается в раздел Shared __declspec(allocate("Shared")) int d;
// инициализированная переменная, по умолчанию помещается в другой раздел int e = 0;
// неинициализированная переменная, по умолчанию помещается в другой раздел int f;
Чтобы спецификатор allocate работал корректно, сначала должен быть создан соответствующий раздел. Так что, ели убрать из предыдущего фрагмента кода первую строку #pragma data_seg, пример не будет компилироваться.
Чаще всего переменные помещают в собственные разделы, намереваясь сделать их разделяемыми между несколькими проекциями EXE или DLL. По умолчанию каждая проекция получает свой набор переменных. Но можно сгруппировать в отдельном разделе переменные, которые должны быть доступны всем проекциям EXE или DLL; тогда система не станет создавать новые экземпляры этих переменных для каждой проекции EXE или DLL.
Чтобы переменные стали разделяемыми, одного указания компилятору выделить их в какой-то раздел мало. Надо также сообщить компоновщику, что переменные в этом разделе должны быть общими. Для этого предназначен ключ /SECTION компоновщика
/SECTION:имя, атрибуты
За двоеточием укажите имя раздела, атрибуты которого необходимо изменить. В примере нужно изменить атрибуты раздела Shared, поэтому ключ должен выглядеть так:
/SECTION:Shared, RWS
После запятой задаются требуемые атрибуты. При этом используются такие сокращения R (READ), W (WRITE), E (EXECUTE) и S(SHARED). В данном случае указано, что раздел Shared должен быть «читаемым», «записываемым» и «разделяемым». Если необходимо изменить атрибуты более чем у одного раздела, указывайте ключ /SECTION для каждого такого раздела.
Соответствующие директивы для компоновщика можно вставлять прямо в исходный код:
#pragma comment(linker, /SECTION Shared, RWS )
Хотя создавать общие разделы можно, Microsoft не рекомендует это делать. Во первых, разделение памяти таким способом может нарушить защиту. Во вторых, наличие общих переменных означает, что ошибка в одном приложении повлияет на другое, так как этот блок данных не удастся защитить от случайной записи.
Пример объявления разделяемой переменной:
// указываем компилятору поместить эту инициализированную переменную // в раздел Shared, чтобы она стала доступной всем экземплярам программы
#pragma data_seg("Shared") volatile LONG g_lApplicationInstances = 0; #pragma data_seg()
// указываем компоновщику, что раздел Shared должен быть // читаемым, записываемым и разделяемым #pragma comment(linker, "/Section.Shared,RWS")