Память для 32-разрядных процессоров 80x86 подразделяется на байты (8 бит), слова (16 бит), двойные слова (32 бит). Слова записываются в двух смежных байтах, начиная с младшего. Адресом слова является адрес его младшего байта. Двойные слова записываются в четырех смежных байтах, опять-таки начиная с младшего байта, адрес которого и является адресом двойного слова.
Более крупными единицами являются страницы и сегменты. Память может логически организовываться в виде одного или множества сегментов переменной длины (в реальном режиме — фиксированной). Сегменты могут выгружаться на диск и по мере необходимости с него подкачиваться в физическую память. Кроме сегментации, в защищенном режиме возможно разбиение (Paging) логической памяти на страницы размером 4 Кбайт, каждая из которых может отображаться на любую область физической памяти. Начиная с 5-го поколения, появилась возможность увеличения размера страницы до 4 Мбайт. Сегментация и разбиение на страницы могут применяться в любых сочетаниях. Сегментация является средством организации логической памяти, используемым на прикладном уровне. Разбиение на страницы применяется на системном уровне для управления физической памятью.
Применительно к памяти различают три адресных пространства: логическое, линейное и физическое. Основным режимом работы 32-разрядных процессоров считается защищенный режим, в котором работают все механизмы преобразования адресных пространств (рис. 6.7).
Рисунок 6.7 – Формирование адреса памяти 32-разрядных процессоров в защищенном режиме
Логический адрес, также называемый виртуальным, состоит из селектора сегмента (в реальном режиме – просто адреса сегмента) и эффективного адреса называемого также смещением (offset). Эффективный адрес формируется суммированием компонентов base, index, displacement с учетом масштаба scale. Поскольку каждая задача может иметь до 16 К селекторов, а смещение, ограниченное размером сегмента, может достигать 4 Гбайт, логическое адресное пространство для каждой задачи может достигать 64 Тбайт. Все это пространство виртуальной памяти в принципе доступно программисту (при условии поддержки со стороны операционной системы). Блок сегментации транслирует логическое адресное пространство в 32-битное пространство линейных адресов. Линейный адрес образуется сложением базового адреса сегмента с эффективным адресом. Базовый адрес сегмента в реальном режиме образуется умножением содержимого используемого сегментного регистра на 16 (как и в 8086). В защищенном режиме базовый адрес загружается из дескриптора, хранящегося в таблице, по селектору, загруженному в используемый сегментный регистр. 32-битный физический адрес памяти образуется после преобразования линейного адреса блоком страничной переадресации. Он выводится на внешнюю шину адреса процессора. В простейшем случае (при отключенном блоке страничной переадресации) физический адрес совпадает с линейным. Включенный блок страничной переадресации осуществляет трансляцию линейного адреса в физический страницами размером 4 Кбайт (для старших поколений процессоров также возможны страницы размером 2 или 4 Мбайт). Блок обеспечивает расширение разрядности физического адреса процессоров шестого поколения до 36 бит. Блок переадресации может включаться только в защищенном режиме. Для обращения к памяти процессор (совместно с внешней схемой) формирует шинные сигналы MEMWR# (Memory Write) и MEMRD# (Memory Read) для операций записи и чтения соответственно. Шина адреса разрядностью 32 бита позволяет адресовать 4 Гбайт физической памяти, но в реальном режиме доступен только 1 Мбайт, начинающийся с младших адресов. В реальном режиме адресации памяти обеспечивается совместимость с процессором 8086, который своей 16-разрядной адресной шиной охватывает пространство физической памяти в 1 Мбайт. Для совместимости с 80286 32-разрядные процессоры повторяют его ошибку, связанную с переносом, возникающим при сложении адреса сегмента с эффективным адресом. При вычислении физического адреса (рис. 6.8) возможно возникновение переноса, который вызовет появление единицы на линии А20 шины адреса. Максимальное значение адреса в реальном режиме 1OFFEF достигается при Seg=FFFFh и E4=FFFFh. Для обеспечения полной программной совместимости с 8086 в PC используется вентиль Gate À20, принудительно обнуляющий бит А20 системной шины адреса. Вентиль в PC управляется через программно-управляемый бит контроллера клавиатуры 8042 или более быстрым способом (Gate À20 Fast Control), определяемым чипсетом системной платы. В реальном режиме размер сегмента фиксирован — как и у 8086, он составляет 64Кбайт (FFFFh). Попытка использования эффективного адреса, выходящего за границу сегмента, при 32-битной адресации вызывает исключение #GP. При 16-битной адресации при вычислении эффективного адреса возможный перенос в разряд А16 игнорируется и сегмент «сворачивается в кольцо» (как и в 8086). Средства контроля следят и за переходом через границу сегмента во время обращения по «приграничному» адресу. При попытке адресации к слову, имеющему смещение FFFFh, или двойному слову со смещением FFFDh-FFFFh (их старшие байты выходят за границу сегмента), или выполнения инструкции, хотя бы один байт которой не умещается в данном сегменте, процессор вырабатывает исключение #GP. При попытке выполнения инструкции сопроцессора (ESCAPE) с операндом памяти, не умещающимся в сегменте, вырабатывается исключение 9 — Processor Extension Segment Overrrun Interrupt (только для 80386).
Рисунок 6.8 – Формирование физического адреса памяти процессором 8086/8088
Система команд 32-разрядных процессоров предусматривает 11 режимов адресации. При этом только в двух случаях операнды не связаны с памятью. Это операнд-содержимое регистра, которое берется из любого 8-, 16- или 32-битного регистра процессора, и непосредственный операнд (8, 16 или 32 бит), который содержится в самой команде. Остальные девять режимов (табл. 3) так или иначе обращаются к памяти.
При обращении к памяти эффективный адрес вычисляется с использованием следующих компонентов:
• Смещение (Displacement или Disp) — 8-, 16- или 32-битное число, включенное в команду.
• База (Base) — содержимое базового регистра. Обычно используется для указания на начало некоторого массива.
• Индекс (Index) — содержимое индексного регистра. Обычно используется для выбора элемента массива.
• Масштаб (Scale) — множитель (1, 2, 4 или 8), указанный в коде инструкции. Этот элемент используется для указания размера элемента массива, доступен только при 32-битной адресации. Эффективный адрес вычисляется по формуле
ЕА = Base + Index х Scale + Disp
Отдельные слагаемые в этой формуле могут отсутствовать. Возможные режимы адресации приведены в табл. 6.1.
Процессор может работать с 32- или 16-битной адресацией, 16-битная адресация работает так же, как и в процессорах 8086 и 80286, при этом в качестве компонентов адреса используются младшие 16 бит соответствующих регистров. При 32-битной адресации используются расширенные 32-разрядные регистры и применяются дополнительные режимы с масштабированием индекса. Различия 16- и 32-битных режимов адресации приведены в табл. 6.2.
В реальном режиме по умолчанию используется 16-битная адресация, но с помощью префикса изменения разрядности адреса (Address Length Prefix) для текущей инструкции можно переключиться на 32-битную. При этом появляются дополнительные возможности адресации (масштабирование), но вычисляемое значение эффективного адреса все равно не может преодолеть 64-килобайтный барьер — при попытке обратиться по такому эффективному адресу генерируется исключение #GP General Protection Fault.
В защищенном режиме адресация по умолчанию определяется битом D дескриптора используемого кодового сегмента: при D=0 — 16 бит, при D=1 — 32 бита. Префикс разрядности адреса переключает разрядность для текущей инструкции на противоположную.
Таблица 6.1 Режимы адресации памяти 32-разрядных процессоров
Режим |
Адрес |
Прямая адресация (Direct Mode) |
EA=Disp |
Косвенная регистровая адресация(Register Indirect Mode) |
EA=Base |
Базовая адресация (Base Mode) |
EA=Base+Disp |
Индексная адресация (Index Mode) |
EA=Index+Disp |
Масштабированная индексная адресация (Scaled Index Mode) |
EA=Scale*Index+Disp1 |
Базово-индексная адресация (Based-Index Mode) |
EA=Base+Index |
Масштабированная базово-индексная адресация (Based Scaled Index Mode) |
EA=Base+Scale*Index1 |
Базово-индексная адресация со смещением (Based-Index Mode with Displacement) |
EA=Base+Index+Disp |
Масштабированная базово-индексная адресация со смещением (Based Scaled Index Mode with Displacement) |
EA=Base+Scale*Index+Disp1 |
1 Масштабирование индекса возможно только при 32-битной адресации
Таблица 6.2 Различия режимов адресации
Компонент |
16-битная адресация |
32-битная адресация |
Базовый регистр |
BX или BP |
Любой 32-битный РОН |
Индексный регистр |
SI или DI |
Любой 32-битный РОН, кроме ESP |
Масштаб |
Нет (всегда 1) |
1, 2, 4 или 8 |
Смещение |
0, 8 или 16 бит |
0, 8 или 32 бит |
При обращениях к памяти использование сегментных регистров по умолчанию определяется типом обращения (табл. 5). Для большинства типов обращения на время текущей инструкции при необходимости возможно использование альтернативного сегментного регистра, на что указывает префикс замены сегмента (CS:, DS:, ES:, SS:, FS: или GS:) перед кодом инструкции.
Таблица 5 Использование сегментных регистров при обращении к памяти
Тип обращения к памяти |
Сегментный регистр | |
По умолчанию |
альтернативный | |
Выборка команд |
CS |
Нет |
Стековые операции |
SS |
Нет |
Строка-приемник |
ES |
Нет |
Любые другие ссылки на память, кроме тех, что используют в качестве базового регистр BP,EBP,ESP |
DS |
Cs,ss,es,fs,gs |
Ссылки на память, использующие в качестве базового регистр BP,EBP,ESP |
SS |
CS,DS,ES,FS,GS |
Физическое адресное пространство процессора i386 составляет 4 Гбайта, что определяется 32-разрядной шиной адреса. Физическая память является линейной с адресами от 00000000 до FFFFFFFF в шестнадцатеричном представлении. Виртуальный адрес, используемый в программе, представляет собой пару - номер сегмента и смещение внутри сегмента. Смещение хранится в соответствующем поле команды, а номер сегмента - в одном из шести сегментных регистров процессора (CS, SS, DS, ES, FS или GS), каждый из которых является 16-битным. Средства сегментации образуют верхний уровень средств управления виртуальной памятью процессора i386, а средства страничной организации - нижний уровень. Средства страничной организации могут быть как включены, так и выключены (за счет установки определенного бита в управляющем регистре процессора), и в зависимости от этого изменяется смысл преобразования виртуального адреса, которое выполняют средства сегментации. Сначала рассмотрим случай работы средств сегментации при отключенном механизме управления страницами.
Рис. 7.1. Поддержка сегментов
На рисунке 7.1 показано виртуальное адресное пространство процессора i386 при отключенном механизме управления страницами. 32-битное смещение определяет размер виртуального сегмента в 232=4 Гбайта, а количество сегментов определяется размером поля, отведенного в сегментном регистре под номер сегмента. На рисунке 7.2,а показана структура данных в сегментном регистре. Эта структура называется селектором, так как предназначена для выбора дескриптора определенного сегмента из таблиц дескрипторов сегментов. Дескриптор сегмента описывает все характеристики сегмента, необходимые для проверки правильности доступа к нему и нахождения его в физическом адресном пространстве. Процессор i386 поддерживает две таблицы дескрипторов сегментов - глобальную (Global Descriptor Table, GDT) и локальную (Local Descriptor Table, LDT). Глобальная таблица предназначена для описания сегментов операционной системы и сегментов межзадачного взаимодействия, то есть сегментов, которые в принципе могут использоваться всеми процессами, а локальная таблица - для сегментов отдельных задач. Таблица GDT одна, а таблиц LDT должно быть столько, сколько в системе выполняется задач. При этом активной в каждый момент времени может быть только одна из таблиц LDT.
Рис. 7.2. Форматы селектора и дескрипторов данных и кода:
а -
формат селектора; б - формат регистра GDTR;
в - формат дескриптора сегмента
данных или кода
Из рисунка 7.2 видно, что селектор состоит из трех полей - 13-битного поля индекса (номера сегмента) в таблицах GDT и LDT, 1-битного поля - указателя типа используемой таблицы дескрипторов и двухбитного поля текущих прав доступа задачи - CPL. Разрядность поля индекса определяет максимальное число глобальных и локальных сегментов задачи - по 8K (213) сегментов каждого типа, всего 16 K. С учетом максимального размера сегмента - 4 Гбайта - каждая задача при чисто сегментной организации виртуальной памяти работает в виртуальном адресном пространстве в 64 Тбайта.
Теперь проследим, как виртуальное пространство отображается на физическое пространство размером в 4 Гбайта при чисто сегментном механизме отображения. Итак, когда задаче необходимо получить доступ к ячейке физической памяти, то для выбора дескриптора виртуального сегмента используется значение селектора из соответствующего (в зависимости от команды и стадии ее выполнения - выборка кода команды или данных) сегментного регистра процессора. Значение поля типа таблицы указывает на то, какую таблицу нужно использовать - GDT или LDT. Рассмотрим сначала случай использования таблицы GDT. Для хранения таблиц GDT и LDT используется оперативная память (использование быстрой ассоциативной памяти процессора для хранения элементов этих таблиц рассмотрим позже). Для того, чтобы процессор смог найти в физической памяти таблицу GDT, ее полный 32-битный физический адрес (адрес начала таблицы), а также размер (поле в 16 бит) хранятся в специальном регистре процессора GDTR (рисунок 7.2, б). Каждый дескриптор в таблицах GDT и LDT имеет размер 8 байт, поэтому максимальный размер этих таблиц - 64 К (8(8 К дескрипторов). Для извлечения нужного дескриптора из таблицы процессор складывает базовый адрес таблицы GDT из регистра GDTR со сдвинутым на 3 разряда влево (умножение на 8, в соответствии с числом байтов в элементе таблицы GDT) значением поля индекса из сегментного регистра и получает физический линейный адрес нужного дескриптора в физической памяти. Таблица GDT постоянно присутствует в физической памяти, поэтому процессор извлекает по этому адресу нужный дескриптор сегмента и помещает его во внутренний (программно недоступный) регистр процессора. (Таких регистров шесть и каждый из них соответствует определенному сегментному регистру, что значительно ускоряет работу процессора).
Дескриптор виртуального сегмента (рисунок 7.2 в) состоит из нескольких полей, основными из которых являются поле базы - базового 32-разрядного физического адреса начала сегмента, поле размера сегмента и поле прав доступа к сегменту - DPL (Descriptor Privilege Level). Сначала процессор определяет правильность адреса, сравнивая смещение и размер сегмента (в случае выхода за границы сегмента происходит прерывание типа исключение - exсeption). Потом процессор проверяет права доступа задачи к данному сегменту, сравнивая значения полей CPL селектора и DPL дескриптора сегмента. В процессоре i386 мандатный способ определения прав доступа (называемый также механизмом колец защиты), при котором имеется несколько уровней прав доступа, и объекты какого-либо уровня имеют доступ ко всем объектам равного уровня или более низких уровней, но не имеет доступа к объектам более высоких уровней. В процессоре i386 существует четыре уровня прав доступа - от 0-го, который является самым высоким, до 3-го - самого низкого. Очевидно, что операционная система может использовать механизм уровней защиты по своему усмотрению. Однако предполагается, что нулевой уровень будет использован для ядра операционной системы, а третий уровень - для прикладных программ, промежуточные уровни - для утилит и подсистем операционной системы, менее привилегированных, чем ядро.
Таким образом, доступ к виртуальному сегменту считается законным, если уровень прав селектора CPL выше или равен уровню прав сегмента DPL (CPL ( DPL). При нарушении прав доступа происходит прерывание, как и в случае несоблюдения границ сегмента. Далее проверяется наличие сегмента в физической памяти по значению бита P дескриптора, и если сегмент отсутствует в физической памяти, то происходит прерывание. При наличии сегмента в памяти вычисляется физический линейный адрес путем сложения базы сегмента и смещения и производится доступ к элементу физической памяти по этому адресу.
В случае, когда селектор указывает на таблицу LDT, виртуальный адрес преобразуется в физический аналогичным образом, но для доступа к самой таблице LDT добавляется еще один этап, так как в процессоре регистр LDTR указывает на размещение таблицы LDT не прямо, а косвенно. Сам регистр LDTR имеет размер 16 бит и содержит селектор дескриптора таблицы GDT, который описывает расположение этой таблицы в физической памяти. Поэтому при доступе к элементу физической памяти через таблицу LDT происходит двукратное преобразование виртуального адреса в физический, причем оба раза по описанной выше схеме. Сначала по значению селектора LDTR определяется физический адрес дескриптора из таблицы GDT, описывающего начало расположения таблицы LDT в физической памяти, а затем с помощью селектора задачи вычисляется смещение в таблице LDT и определяется физический адрес нужного дескриптора. Далее процесс аналогичен преобразованию виртуального адреса с помощью таблицы GDT.
Рис. 7.3. Типы дескрипторов
Дескриптор сегмента содержит еще несколько полей. Однобитное поле G определяет единицу измерения размера сегмента, при G = 0 размер определяется в байтах, и тогда сегмент не может быть больше 64 К, а при G = 1 размер определяется в 4К-байтных страницах, при этом максимальный размер сегмента достигает указанных 4 Гбайт. Поле D определяет тип адресации сегмента: при D = 0 сегмент является 16-битным (для режима эмуляции 16-битных процессоров i86 и i286), а при D = 1 сегмент является 32-битным. Кроме этого в дескрипторе имеется поле типа сегмента, которое в свою очередь делится на несколько полей (рисунок 7.3). Поле S определяет, является ли сегмент системным (S = 1) или пользовательским (S = 0). В свою очередь пользовательские сегменты делятся на сегменты данных (E=0) и сегменты кода (E=1). Для сегмента данных определяются однобитные поля:
ED - направления распространения сегмента (ED = 0 для обычного сегмента данных, распространяющегося в сторону увеличения адресов, ED = 1 для стекового сегмента данных, распространяющегося в сторону уменьшения адресов),
W - поле разрешения записи в сегмент (при W=1 запись разрешена, при W=0 - запрещена),
A - поле доступа к сегменту (1 означает, что после очистки этого поля к сегменту было обращение по чтению или записи, это поле может использоваться операционной системой в ее стратегии замены страниц в оперативной памяти).
Для сегмента кода используются однобитные признаки:
A - имеет смысл, аналогичный полю A сегмента данных,
R - разрешает или запрещает чтение из кодового сегмента,
C - бит подчинения, разрешает или запрещает вызов данного кодового сегмента из другого кодового сегмента с более низкими правами доступа.
В процессоре i386 существует большое количество системных сегментов, к которым в частности относятся системные сегменты типа LDT, шлюзы вызова подпрограмм и задач и сегменты состояния задачи TSS.
Таким образом, для использования чисто сегментного механизма процессора i386 операционной системе необходимо сформировать таблицы GDT и LDT, загрузить их в память (для начала достаточно загрузить только таблицу GDT), загрузить указатели на эти таблицы в регистры GDTR и LDTR и выключить страничную поддержку. Если же операционная система не хочет использовать сегментную организацию виртуальной памяти, то ей достаточно создать таблицу дескрипторов из одного входа (дескриптора) и загрузить базовые значения сегмента в дескриптор. Виртуальное адресное пространство задачи будет состоять из одного сегмента длиной максимум в 4 Гбайта.
При включенной системе управления страницами работает как описанный выше сегментный механизм, так и механизм управления страницами, однако при этом смысл работы сегментного механизма меняется. В этом случае виртуальное адресное пространство задачи имеет размер в 4 Гбайта, в котором размещаются все сегменты (рисунок 7.4). По прежнему селектор задачи определяет номер виртуального сегмента, а смещение в команде задачи - смещение внутри этого сегмента. Так как теперь все сегменты разделяют одно адресное пространство, то возможно их наложение, но процессор не контролирует такие ситуации, оставляя эту проблему операционной системе. Первый этап преобразования виртуального адреса, связанный с преобразованием смещения и селектора с использованием таблиц GDT и LDT, содержащих дескрипторы сегментов, в точности совпадает с этапом преобразования этих данных при отключенном механизме управления страницами. Все структуры данных этих таблиц такие же. Однако, если раньше дескриптор сегмента содержал его базовый адрес в физическом адресном пространстве, и при сложении его со смещением из команды программы получался линейный искомый адрес в физической памяти, то теперь дескриптор содержит базовый адрес сегмента в виртуальном адресном пространстве. Поэтому в результате его сложения со смещением получается линейный виртуальный адрес, который на втором этапе (страничном) преобразуется в номер физической страницы. Для реализации механизма управления страницами как физическое, так и виртуальное адресное пространства разбиты на страницы размером 4 К. Всего в этих адресных пространствах насчитывается 1 М страниц. Несмотря на наличие нескольких виртуальных сегментов, все виртуальное адресное пространство задачи имеет общее разбиение на страницы, так что нумерация виртуальных страниц сквозная.
Линейный виртуальный адрес содержит в своих старших 20 разрядах номер виртуальной страницы, а в младших 12 разрядах смещение внутри страницы. Для отображения виртуальной страницы в физическую достаточно построить таблицу страниц, каждый элемент которой - дескриптор виртуальной страницы - содержал бы номер соответствующей ей физической страницы и ее атрибуты. В процессоре i386 так и сделано, и структура дескриптора страницы показана на рисунке 7.5. 20-ти разрядов номера страницы достаточно для определения физического адреса начала страницы, так как при ее фиксированном размере 4 К младшие 12 разрядов этого адреса всегда равны нулю. Дескриптор страницы также содержит следующие поля, близкие по смыслу соответствующим полям дескриптора сегмента:
P - бит присутствия страницы в физической памяти,
W - бит разрешения записи в страницу,
U - бит пользователь/супервизор
A - признак того, был ли доступ к странице,
D - признак модификации содержимого страницы,
PWT и PCD - управляют механизмом кэширования страниц (введены, начиная с процессора i486),
AVL - резерв для нужд операционной системы (available for use).
Рис. 7.4. Сегментно-страничный механизм
Рис. 7.5. Формат дескриптора страницы
При небольшом размере страницы процессора i386 относительно размеров адресных пространств, таблица страниц должна занимать в памяти весьма значительное место - 4 байта ( 1М = 4 Мбайта. Это слишком много для нынешних моделей персональных компьютеров, поэтому в процессоре i386 используется деление всей таблицы страниц на разделы по 1024 дескриптора. Размер раздела выбран так, чтобы один раздел занимал одну физическую страницу (1024 ( 4 байта = 4 Кбайта). Всего получается 1024 раздела (1024 ( 1024 = 1М). Для того, чтобы не хранить все разделы таблицы страниц одновременно в физической памяти, используется каталог разделов таблицы страниц, который использует такие же по структуре дескрипторы страниц, что и в таблице страниц. Поэтому для хранения информации о дескрипторах 1024 разделов необходима память 4 К, т.е. одна физическая страница. Совокупность дескрипторов, описывающих состояние и характеристики виртуальных страниц разделов таблицы страниц, называется каталогом разделов или таблиц. Виртуальная страница, хранящая содержимое каталога, всегда находится в физической памяти, и номер ее физической страницы указан в специальном управляющем регистре CR3 процессора (точнее, в одном из полей этого регистра).
Преобразование линейного виртуального адреса в физический происходит следующим образом (рисунок 7.6). Поле номера виртуальной страницы (старшие 20 разрядов) делится на две равные части по 10 разрядов - поле номера раздела и поле номера страницы в разделе. С помощью номера физической страницы, хранящей каталог и смещения в этой странице, задаваемого полем номера раздела, процессор находит дескриптор виртуальной страницы раздела. В соответствии с атрибутами этого дескриптора определяются права доступа к этой странице, а также наличие ее в физической памяти. В случае ее отсутствия происходит страничное прерывание, и операционная система должна в этом случае переместить ее в память. После того, как нужная страница находится в памяти, для определения адреса элемента данных используется смещение, определяемое полем номера страницы линейного виртуального адреса.
Таким образом, при доступе к странице в процессоре используется двухуровневая схема адресации страниц, что замедляет преобразование, но позволяет использовать страничный механизм и для хранения самой таблицы страниц, и существенно уменьшить объем физической памяти для ее хранения. Для ускорения страничных преобразований в блоке управления страницами используется ассоциативная память, в которой кэшируется 32 комбинации "номер виртуальной страницы - номер физической страницы". Эта специальная кэш-память (дополнительная по отношению к 8 Кбайтному кэшу данных процессоров i486 и Pentium) значительно ускоряет преобразование адресов, так как в случае попадания в кэш длительный процесс, описанный выше, исключается.
Рис. 7.6. Преобразование линейного виртуального адреса в физический адрес
Организация виртуальной памяти в процессоре i386 позволяет защитить адресные пространства различных процессов за счет двух механизмов:
1) Изоляция адресных пространств процессов в физической памяти путем назначения им различных физических страниц или сегментов (если страничный механизм отключен).
2) Защита сегментов от несанкционированного доступа с помощью привилегий четырех уровней.
Составной частью ядра операционной системы является VMM. Приложения не могут получить к VMM прямой доступ, поэтому для управления памятью им предоставляются различные программные интерфейсы (API). Их архитектура приведена на рис. 1.
Одни интерфейсы построены на использовании других. Их взаимосвязь изображена на рисунке стрелками. Ниже приведен список интерфейсов с комментариями:
Virtual Memory API - набор функций, позволяющих приложению работать с виртуальным адресным пространством. Приложение может назначать физические страницы блоку адресов и освобождать их, а также устанавливать атрибуты защиты (см. врезку "Virtual Memory API");
Memory Mapped File API - набор функций использования файлов, отображаемых в память. Этот новый с точки зрения классического устройства ОС механизм предоставляется Win32 API для работы с файлами и взаимодействия процессов между собой;
Heap Memory API - набор функций для управления динамически распределяемыми областями памяти (кучами). Интерфейс построен с помощью Virtual Memory API;
Local, Global Memory API - программный интерфейс для работы с памятью, совместимый с 16-разрядной Windows (лучше его не использовать);
CRT Memory API - функции стандартной библиотеки времени исполнения языка Cи (C Run Time library).
Два последних набора функций в данной статье не рассматриваются.
Кучи (heaps) - это динамически распределяемые области данных. При порождении процесса ему предоставляется куча размером 1 Мбайт по умолчанию. Ее размер может изменяться параметром /HEAP при построении исполняемого модуля. Функции библиотеки времени исполнения компилятора (malloc(), free() и т. д.) используют возможности куч.
Для работы с кучей предназначены следующие функции:
HANDLE GetProcessHeap( VOID ) - для получения дескриптора кучи по умолчанию;
LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, DWORD dwSize ) - выделяющая блок памяти заданного размера из кучи и возвращающая указатель на этот блок;
LPVOID HeapReAlloc( HANDLE hHeap, DWORD dwFlags, LPVOID lpOldBlock, DWORD dwSize) - изменяющая размер выделенного блока памяти, при этом она может перемещать блок, если нет достаточного места для простого расширения;
BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ) - освобождает выделенный блок памяти кучи.
Иногда имеет смысл пользоваться дополнительными кучами, создание которых производится функцией HANDLE HeapCreate(DWORD dwFlags, DWORD dwInitialSize, DWORD dwMaximumSize). Целесообразно использовать дополнительные кучи для защиты друг от друга различных структур данных, для повышения эффективности управления памятью и др. В системах со страничной организацией отсутствует проблема фрагментации физической памяти, однако существует проблема фрагментации адресного пространства. В 4-Гбайт адресном пространстве эта проблема не актуальна, но она имеет значение в куче размером 1 Мбайт. Если элементы какой-либо структуры имеют один размер, а элементы другой структуры - другой, то полезно размещать эти структуры в разных кучах. Кроме того, дополнительные кучи могут быть применены и для уменьшения рабочего множества процесса. В соответствии с принципом локальности работа с разными структурами чаще всего происходит не одновременно. Границы элементов разных структур не выравниваются на границу страницы, поэтому обращение к элементам одной структуры вызывает подкачку всей страницы, а значит, и элементов другой структуры. Это увеличивает рабочее множество процесса.
Виртуальные адреса, которые использует процесс, никак не связаны с реальным положением объектов в физической памяти. Взамен этого операционная система хранит для каждого процесса карту страниц, которая используется для того, чтобы переводить виртуальные адреса процесса в соостветствующие адреса физического пространства.
Виртуальное адресное пространство делится на части следующим образом:
Windows NT Server Enterprise Edition/Windows
2000 Advanced Server:
3 Гб нижней памяти (от $00000000 до $BFFFFFFF)
доступны процессу и 1 Гб верхней памяти (от $C0000000 до $FFFFFFFF)
зарезервирован системой.
Windows NT/2000:
2 Гб нижней памяти
(от $00000000 до $7FFFFFFF) доступны процессу и 2 Гб верхней памяти (от
$80000000 до $FFFFFFFF) зарезервировано системой.
Windows 95/98
В Windows 95/98 память
разбита следующим образом:
Диапазон |
Способ использования |
0 ($00000000) - 64Kб ($0000FFFF) |
Не доступна для записи. При помощи этой памяти Windows 95/98 способна использовать некоторые пережитки MS DOS. Эта память скрывается от процесса. |
64Kб ($00010000) - |
Зерезервирована для совместимости с MS DOS. Эта память полностью доступна для чтения/записи процессом. Тем не менее эта область может содержать некоторые связанные с MS DOS структуры или код, поэтому процесс не может читать и писать в этот регион так, как ему заблагорассудится. Эта память скрывается процессом. |
4Мб ($00400000) - |
Диапазон доступен для кода программы и пользовательских данных. Пользовательские данные доступны как для чтения, так и для записи. Код доступен только для выполнения. Эта память скрывается процессом. |
2Гб ($80000000) - |
Область, доступная для чтения и записи всеми процессами. В эту область загружаются системные DLL и и другие данные. |
3Гб ($C0000000) - |
Системная область, доступная для чтения и записи. В этой области содержиться низкоуровневый системный код, поэтому запись в этот регион может привести к повреждению системы потенциально с катастрофическими последствиями. |
Список функций
Ниже прдставлены функции для работы с виртуальной памятью. Описание их работы можно найти в разделе "работа с виртуальной памятью".
Функция |
Описание |
Возвращает адреса страниц, в которые была произведена запись. | |
Возвращает информацию о том, как системой в настоящее время используется как физическая, так и виртуальная память. | |
Возвращает информацию о том, как системой в настоящее время используется как физическая, так и виртуальная память. | |
|
|
Сбрасывает состояние слежения за записью для указанного региона виртуальной памяти. | |
Резервирует или выделяет физическую память для указанного диапазона страниц в виртуальном адресном простанстве вызывающего процесса. | |
|
|
Резервирует или выделяет физическую память для указанного диапазона страниц в виртуальном адресном простанстве вызывающего процесса. | |
|
|
Освобождает или передает физиескую память системе для указанного диапазона страниц в виртуальном адресном простанстве вызывающего процесса. | |
|
|
Освобождает или передает физиескую память системе для указанного диапазона страниц в виртуальном адресном простанстве вызывающего процесса. | |
|
|
Фиксирует положение указанного региона виртуального адресного пространства процесса в физической памяти. | |
|
|
Изменяет адрибуты защиты для страниц, под которыми расположена физическая память, в виртуальном адресном пространстве вызывающего процесса. | |
|
|
Изменяет адрибуты защиты для страниц, под которыми расположена физическая память, в виртуальном адресном пространстве вызывающего процесса. | |
|
|
Получает информацию о диапазоне страниц в виртуальном адресном пространстве вызывающего процесса. | |
|
|
Получает информацию о диапазоне страниц в виртуальном адресном пространстве вызывающего процесса. | |
|
|
Снимает фиксацию указанного региона виртуального адресного пространства процесса в физической памяти. |
Блок адресов в адресном пространстве процесса может находиться в одном из трех состояний:
- выделен (committed) - блоку адресов назначена физическая память либо часть файла подкачки;
- зарезервирован (reserved) - блок адресов помечен как занятый, но физическая память не распределена;
- свободен (free) - блок адресов не выделен и не зарезервирован.
Резервирование и выделение памяти производится блоками, начальные адреса которых должны быть выровнены на границу 64 Кбайт (округляется вниз), а размер кратен размеру страницы (округляется вверх). При выделении память обнуляется.
Для резервирования региона памяти в адресном пространстве процесса или ее выделения используется функция VirtualAlloc(), а для освобождения - функция VirtualFree():
LPVOID VirtualAlloc(
LPVOID lpAddress,
DWORD dwSize,
DWORD flAllocationType,
DWORD flProtect);
Эта функция возвращает адрес выделенного региона, а в случае неудачи возвращает NULL. Параметры функции:
lpAddress - адрес, по которому надо зарезервировать или выделить память. Если этот параметр равен NULL, то система самостоятельно выбирает место в адресном пространстве процесса;
dwSize - размер выделяемого региона;
flAllocationType - тип распределения памяти;
flProtect - тип защиты доступа выделяемого региона:
PAGE_READONLY - допускается только чтение;
PAGE_READWRITE - допускается чтение и запись;
PAGE_EXECUTE - допускается только выполнение;
PAGE_EXECUTE_READ - допускается исполнение и чтение;
PAGE_EXECUTE_READWRITE - допускается выполнение, чтение и запись;
PAGE_GUARD - дополнительный флаг защиты, который комбинируется с другими флагами. При первом обращении к странице этот флаг сбрасывается и возникает исключение STATUS_GUARD_PAGE. Этот флаг используется для контроля размеров стека с возможностью его динамического расширения;
PAGE_NOCACHE - запрещает кэширование страниц. Может быть полезен при разработке драйверов устройств (например, данные в видеобуфер должны переписываться сразу, без кэширования).
BOOL VirtualFree(
LPVOID lpAddress,
DWORD dwSize,
DWORD dwFreeType);
Возвращает TRUE в случае успеха и FALSE в случае неудачи. Параметры:
lpAddress - адрес региона, который надо освободить;
dwSize - размер освобождаемого региона;
dwFreeType - тип освобождения.
Параметр flAllocationType может принимать следующие значения:
MEM_RESERVE - резервирует блок адресов без выделения памяти;
MEM_COMMIT - отображает ранее зарезервированный блок адресов на физическую память или файл подкачки, выделяя при этом память. Может комбинироваться с флагом MEM_RESERVE для одновременного резервирования и выделения;
MEM_TOP_DOWN - выделяет память по наибольшему возможному адресу. Имеет смысл только при lpAddress = NULL. В Windows 95 игнорируется.
MEM_DECOMMIT - освободить выделенную память;
MEM_RELEASE - освободить зарезервированный регион. При использовании этого флага параметр dwSize должен быть равен нулю.
Выделенные страницы можно заблокировать в памяти, т. е. запретить их вытеснение в файл подкачки. Такие страницы остаются в составе рабочего множества процесса до того момента, как будут разблокированы. Для этих целей служит пара функций VirtualLock() и VirtualUnlock(). Процессу не разрешается блокировать более 30 страниц. Для настройки рабочего множества процесса может использоваться и функция SetProcessWorkingSetSize() [8]. Формально она не входит в состав Virtual Memory API, но тесно с ним связана. Например, использование этой функции снимет барьер 30 страниц для функции VirtualLock().
Для изменения атрибутов защиты регионов используются функции VirtualProtect() и VirtualProtectEx(). Причем первая позволяет изменять атрибуты защиты в адресном пространстве текущего процесса, а вторая - произвольного.
Функции VirtualQuery() и VirtualQueryEx() позволяют определить статус указанного региона адресов.
GlobalMemoryStatus
Функция GlobalMemoryStatus позволяет получить информацию про текущее использование системой как физической, так и виртуальной памяти.
Для того, чтобы получить информацию про неиспользуемое адресное пространство процесса, а также в том случае, если на компьютере установлено более 4 Гб оперативной памяти, следует выполнить вызов функции GlobalMemoryStatusEx.
procedure GlobalMemoryStatus(
var lpBuffer: TMemoryStatus); // структура статуса памяти
stdcall;
Параметры
lpBuffer [out]
Переменная типа TMemoryStatus в которой сохранится информация о текущей доступной памяти.
Возвращаемое значение
Функция не имеет возвращаемого значения
Описание
Вы должны использовать функцию GetMemoryStatus для того, чтобы определить, какое количество памяти может выделить ваше приложение без заметного ущерба для других приложений.
Информация, возвращаемая функцией GetMemoryStatus, постоянно изменяется. Нет никакой гарантии, что два последовательных вызова этой функции вернут одинаковые значения.
Если оперативная память компьютера превышает 4 Гб, то функция GetMemoryStatus вернет искаженную информацию. На платформе Windows 2000 возвращаемым значением будет -1, что сигнализирует о переполении. Более ранние версии Windows NT вернут в этом случае значение, которое в действительности будет остатком от деления общего количества памяти на 4 Гб. Для этой цели в Windows 2000 необходимо использовать функцию GlobalMemoryStatusEx.
На компьютерах архитектуры Intel x86, на которых установлено от 2 до 4 Гб оперативной памяти, функция GlobalMemoryStatus всегда вернет в поле dwTotalPhys записи TMemoryStatus значение 2 Гб. Точно так же, если свободная память находится в диапазоне от 2 до 4 Гб, то значение поля dwAvailPhys записи TMemoryStatus будет округлено вниз до 2
Структура TMemoryStatus позволяет получить информацию про текущее состояние физической и виртуальной памяти. Функция GlobalMemoryStatus позволяет получить информацию в данной структуре TMemoryStatus.
TMemoryStatus = record
dwLength: DWORD;
dwMemoryLoad: DWORD;
dwTotalPhys: DWORD;
dwAvailPhys: DWORD;
dwTotalPageFile: DWORD;
dwAvailPageFile: DWORD;
dwTotalVirtual: DWORD;
dwAvailVirtual: DWORD;
end;
Описание
В структуре TMemoryStatus отображается информация о памяти на момент вызова. Она также отображает состояние файла подкачки на момент вызова. Надо учитывать то обстоятельство, чтооперационная система может увеличитьразмер файла подкачки до размера, указанного администратором.
Если оперативная память компьютера превышает 4 Гб, то функция GetMemoryStatus вернет искаженную информацию. На платформе Windows 2000 возвращаемым значением будет -1, что сигнализирует об переполении. Более ранние версии Windows NT вернут в этом случае значение, которое в действительности будет остатком от деления общего количества памяти на 4 Гб. Для этой цели в Windows 2000 необходимо использовать функцию GlobalMemoryStatusEx
Поля
dwLength
Размер в байтах структуры
TMemoryStatus. Вы не должны устанавливать это поле перед вызовом GlobalMemoryStatus,
поскольку функция сделает это сама.1
dwMemoryLoad
Windows NT 4 и более
ранние версии: показывает приблизительную загрузку последних 1000 страниц в
памяти, которые использовались.
Windows 2000: показывает загрузку
всех старниц памяти, которые использовались.
dwTotalPhys
Общий размер в байтах
физической памяти.
dwAvailPhys
Размер в байтах доступной
физической памяти.
dwTotalPageFile
Размер в байтах файла
подкачки. Заметьте, что это значение не отображает реальный размер, который
может занмать файл поодкачки на диске.
dwAvailPageFile
Количество свободного
пространства в файле подкачки. Операционная система может расширять время от
времени файл подкачки. Поле dwAvailPageFile показывает только разность между
используемой памятью и общим текущим размером файла подкачки, но при этом не
показывается максимально возможный размер файла подкачки.
dwTotalVirtual
Указывает на общий
размер виртуального адресного пространства, доступный процессу.
dwAvailVirtual
Указывает на
незарезервированные фрагменты виртуального адресного пространства, доступные
вызывающему процессу.
Функция GlobalMemoryStatus позволяет получить информацию про текущее использование системой как физической, так и виртуальной памяти.
function GlobalMemoryStatusEx(
var lpBuffer: TMemoryStatusEx // структура статуса памяти
): Boolean; stdcall;
Параметры
lpBuffer [in/out]
Переменная типа TMemoryStatusEx
в которой сохранится информация о текущей доступной памяти.
Возвращаемое значение
В случае успешного завершения функция возвращает True
В случае ошибки функция возвращает значение False. Для получения расширенной информации об ошибке следует сделать вызов GetLastError.
Описание
Вы должны использовать функцию GetMemoryStatusEx для того, чтобы определить какое количество памяти может выделить ваше приложение без заметного ущерба для других приложений.
Информация, возвращаемая функцией GetMemoryStatusEx, постоянно изменяется. Нет никакой гарантии, что два последовательных вызова этой функции вернут одинаковые значения.
Структура TMemoryStatusEx позволяет получить информацию про текущее состояние физической и виртуальной памяти, включая расширенную память. Функция GlobalMemoryStatusEx позволяет получить информацию в данной структуре TMemoryStatusEx.
TMemoryStatusEx = record
dwLength: DWORD;
dwMemoryLoad: DWORD;
ullTotalPhys: Int64;
ullAvailPhys: Int64;
ullTotalPageFile: Int64;
ullAvailPageFile: Int64;
ullTotalVirtual: Int64;
ullAvailVirtual: Int64;
ullAvailExtendedVirtual: Int64;
end;
Поля
dwLength
Размер в байтах структуры
TMemoryStatusEx. Вы должны устанавить это поле перед вызовом GlobalMemoryStatusEx.
dwMemoryLoad
Возвращает число в
диапазоне от 0 от 100, общая идея которого состоит в оценке степени
использвования памяти. Число 0 показывает, что память не используется. Число 100
показывает, что память полностью задействована.
ullTotalPhys
Общий размер в байтах
физической памяти.
ullAvailPhys
Размер в байтах доступной
физической памяти.
ullTotalPageFile
Размер в байтах файла
подкачки. Заметьте, что это значение не отображает реальный размер, который
может занмать файл поодкачки на диске.
ullAvailPageFile
Количество свободного
пространства в файле подкачки. Операционная система может расширять время от
времени файл подкачки. Поле ullAvailPageFile показывает только разность между
используемой памятью и общим текущим размером файла подкачки, но при этом не
показывается максимально возможный размер файла подкачки.
ullTotalVirtual
Указывает на общий
размер виртуального адресного пространства, доступный процессу.
ullAvailVirtual
Указывает на
незарезервированные фрагменты виртуального адресного пространства, доступные
вызывающему процессу.
ullAvailExtendedVirtual
Показывает
размер в байтах незарезервированной памяти в расширенной части виртуального
адресного пространства вызывающего процесса.
Описание
В структуре TMemoryStatus отображается информация о памяти на момент вызова. Она также отображает состояние файла подкачки на момент вызова. Надо учитывать то обстоятельство, чтооперационная система может увеличитьразмер файла подкачки до размера, указанного администратором.
Проблема нехватки оперативной памяти знакома каждому пользователю. Исчерпав память физическую, Windows начинает загружать программы используя файл подкачки, тем самым резко снижая производительность системы. Специалисты говорят, что для Windows NT такого понятия, как "необходимое количество памяти" не существует вообще. Windows NT загрузит всю память каким-нибудь кэшем и мусором, а потом пожалуется на нехватку, несмотря на то, что у вас 512 Мб ОЗУ!!!Вот и проблемка обрисовалась... Будем её лечить народными средствами.
План действий
Решать проблему
будем средствами Windows API. Функции для работы с памятью
существуют различные.
Но нам потребуются только три:
GlobalMemoryStatus, AllocMem и
SysFreeMem.
Теоретически, план действий таков:
1. Выделить
блок системной памяти размером с ОЗУ.
2. Подождать завершения процесса и
сразу же освободить выделенный блок памяти.
3. Получить статистику
4. Если
всё сработает, то прирост ОЗУ будет 40%.
Реализация
procedure
FreeMem;
var tm:TMemoryStatus;
v:Pointer;
begin
// Получим информацию о размере
ОЗУ
GlobalMemoryStatus(tm);
Screen.Cursor:=crHourGlass;
//
Выделим блок памяти
v:=AllocMem(tm.dwTotalPhys);
// Ждем завершения
процесса
Application.ProcessMessages;
// Освобождаем
блок
SysFreeMem(v);
Screen.Cursor:=crDefault;
end;
Файлы, отображаемые в память, - это один из самых замечательных сервисов, которые Win32 предоставляет программисту. Его существование стирает для программиста грань между оперативной и дисковой памятью. Действительно, с точки зрения классической теории кэш, оперативная память и дисковое пространство - это три вида памяти, отличающиеся скоростью доступа и размером. Но если заботу о перемещении данных между кэшем и оперативной памятью берут на себя процессор и операционная система, то перемещение данных между оперативной памятью и диском обычно выполняет прикладной процесс с использованием функций read() и write(). Win32 действует иначе: операционная система берет на себя заботу о перемещении страниц адресного пространства процесса, находящихся в файле подкачки, причем в качестве файла подкачки может быть использован любой файл. Иначе говоря, страницы виртуальной памяти любого процесса могут быть помечены как выгруженные, а в качестве места, куда они выгружены, может быть указан файл. Теперь при обращении к такой странице VMM произведет ее загрузку, используя стандартный механизм свопинга. Это позволяет работать с произвольным файлом как с регионом памяти. Данный механизм имеет в Win32 три применения:
- для запуска исполняемых файлов (EXE) и динамически связываемых библиотек (DLL);
- для работы с файлами;
- для совместного использования одной области данных двумя процессами.
Запуск на исполнение EXE-модуля происходит следующим образом. EXE-файл отображается на память, и при этом он не переписывается в файл подкачки. Просто элементы каталога и таблиц страниц настраиваются так, чтобы они указывали на EXE-файл, лежащий на диске. Затем передается управление на точку входа программы. При этом возникает исключение, обрабатывая которое стандартным образом, VMM загружает в память требуемую страницу, после чего программа начинает исполняться. Такой механизм существенно ускоряет процедуру запуска программ, так как загрузка страниц EXE-модуля происходит по мере необходимости. По сути, как ни парадоксально это звучит, программа сначала начинает исполняться, а потом загружается в память. Если программа записана на дискете, то перед началом исполнения она переписывается в файл подкачки. Именно поэтому на запуск программы с дискеты уходит значительно больше времени.
Рассмотрим механизм запуска программы на выполнение более подробно. При исполнении функции CreateProcess система обращается к VMM для выполнения следующих действий:
Создать адресное пространство процесса.
Зарезервировать в адресном пространстве процесса регион размером, достаточным для размещения исполняемого файла. Начальный адрес региона берется из заголовка EXE-модуля. Обычно он равен 0x00400000, но может быть изменен при построении файла заданием параметра /BASE компоновщика.
Отобразить исполняемый файл на зарезервированное адресное пространство. Тем самым VMM распределяет физические страницы не из файла подкачки, а непосредственно из EXE-модуля.
Отобразить в адресное пространство процесса необходимые ему динамически связываемые библиотеки. Информация о необходимых библиотеках читается из заголовка EXE-модуля. Желательное расположение региона адресов описано внутри отображаемых библиотек. Visual C++, например, по умолчанию устанавливает для своей библиотеки адрес 0x10000000. Этот адрес может тоже изменяться параметром /BASE компоновщика. Если при загрузке выясняется, что данный регион занят, то система попытается переместить библиотеку в другой регион адресов, согласуя это действие с настроечной информацией, содержащейся в DLL-модуле. Однако эта операция снижает эффективность системы, и кроме того, если при компоновке библиотеки настроечная информация удалена (параметр/FIXED), то загрузка становится вообще невозможной. Интересно, что все стандартные библиотеки Windows имеют фиксированный адрес загрузки, и каждая свой собственный.
При одновременном запуске нескольких приложений Win32 отображает один и тот же исполняемый файл и библиотеки на адресные пространства различных процессов. При этом возникает проблема независимого использования процессами статических переменных и областей данных. Кроме того, изменение данных исполняющейся программой не должно приводить к изменению EXE-файла. А ведь он является файлом подкачки и, значит, вытесняемые страницы должны попадать именно в него.
Мы уже обсуждали выше, что Win32, используя технологию lazy evaluation, откладывает решение этой проблемы на максимально возможный срок. Все страницы адресного пространства процесса, на которые отображен EXE-файл, получают атрибут защиты PAGE_WRITECOPY. При попытке записи в такую страницу возникает исключение нарушения защиты, и VMM копирует страницу для обратившегося процесса. В дальнейшем эта страница всегда будет выгружаться в файл подкачки. После копирования происходит повторный старт команды, вызвавшей исключение.
Отображение файла данных в адресное пространство процесса предоставляет мощный механизм работы с файлами - программа может работать с файлом, как с массивом ячеек памяти. Само проецирование файла в память выполняется в три этапа:
Создается объект ядра "файл". В более ранней терминологии это называлось операцией открытия файла. Для создания объекта "файл" используется функция CreateFile(), аналогичная функции open() из CRT-библиотеки.
С помощью функции CreateFileMapping() создается объект ядра "отображаемый файл". При этом используется дескриптор файла (handle), возвращенный функцией CreateFile(). Теперь файл готов к отображению.
Функцией MapViewOfFile() производится отображение объекта "отображаемый файл" или его части в адресное пространство процесса.
Для открепления файла от адресного пространства процесса используется функция UnmapViewOfFile(), а для уничтожения объектов "файл" и "отображаемый файл" - функция CloseHandle.
Общая методика работы с отображаемыми файлами такова:
HANDLE hFile, hFileMapping;
PVOID pMassive;
hFile = CreateFile( оFile Nameп, ... );
hFileMapping = CreateFileMapping( hFile, ... );
CloseHandle( hFile ) ;
pMassive = MapViewOfFile( hFileMapping, ... );
/* Здесь производится работа с массивом
pMassive */
UnmapViewOfFile( pMassive );
Два процесса могут совместно использовать объект "отображаемый файл". При этом с помощью функции MapViewOfFile() каждый процесс отображает этот объект в свое адресное пространство и применяет эту часть адресного пространства как совместно используемую область данных. Общий механизм таков: один процесс создает объект "проецируемый файл" с помощью функции CreateFileMapping() и порождает другой процесс, передавая ему в наследство дескриптор (handle) этого объекта. Дочерний процесс может пользоваться этим дескриптором наравне с родительским. Проблема состоит только в том, как сообщить дочернему процессу, какой из переданных ему в наследство дескрипторов принадлежит "отображаемому файлу". Это можно сделать любым способом - например, передав параметры при запуске процесса через переменные среды, послав сообщения главному окну процесса и т. д.
Общая область данных может быть создана не только путем проецирования файла, но и путем проецирования части файла подкачки. Для этого в функцию CreateFileMapping() необходимо передать в качестве параметра не дескриптор ранее открытого файла, а константу 1. В этом случае необходимо задать размеры выделяемой области. Кроме того, в параметре lpName можно задать имя глобального объекта в системе. Если это имя задается в системе впервые, то процессу выделяется новая область данных, а если имя было уже задано, то именованная область данных предоставляется для совместного использования.
Если один процесс изменяет совместно используемую область данных, то она изменяется и для другого разделяющего ее процесса. Операционная система обеспечивает когерентность совместно используемой области данных для всех процессов, но для этого процессы должны работать с объектом "отображаемый файл", а не с самим файлом (рис. 2).