Основы организации операционных систем Microsoft Windows

Введение. Виртуальное адресное пространство процесса

Разбить на страницы
Показывать лекцию целиком

Введение

В компьютерах фон-неймановской архитектуры выполняемые программы вместе с обрабатываемыми ими данными должны находиться в оперативной памяти. Операционной системе приходится заниматься управлением памятью, то есть решать задачу распределения памяти между пользовательскими процессами и компонентами ОС. Часть ОС, которая отвечает за управление памятью, называется менеджером памяти.

Для описания системы управления памятью активно используются понятия физической и логической (виртуальной) памяти.

Физическая память является аппаратным запоминающим устройством компьютера. Менеджер памяти имеет дело с двумя уровнями физической памяти: оперативной (основной, первичной) и внешней, или вторичной. Оперативная память изготавливается с применением полупроводниковых технологий и теряет свое содержимое при отключении питания. Вторичная память (это, главным образом, диски) характеризуется гораздо более медленным доступом, однако имеет большую емкость и является энергонезависимой. Она используется в качестве расширения основной памяти. Обычно информация, хранимая в оперативной памяти, за исключением самых последних изменений, хранится также во внешней памяти. Если процессор не обнаруживает нужную информацию в оперативной памяти, он начинает искать ее во вторичной. Когда нужная информация найдена во внешней памяти, она переносится в оперативную память. Менеджер памяти старается по возможности снизить частоту обращений к вторичной памяти (свойство локальности или локализации обращений). В результате эффективное время доступа к памяти оказывается близким к времени доступа к оперативной памяти и составляет несколько десятков наносекунд.

Оперативная память представляет собой упорядоченный массив однобайтовых ячеек, каждая из которых имеет свой уникальный адрес (номер). Типовые операции - чтение и запись байта в ячейку с нужным номером. Обмен с внешней памятью обычно осуществляется блоками фиксированного размера. Совокупность адресов в физической памяти называется физическим адресным пространством.

К сожалению, многие термины, относящиеся к системе управления памятью, как и в информатике вообще, перегружены. Поэтому в дальнейшем термин "физическая память" будет относиться именно к оперативной памяти, а использование внешней памяти (дисковой, файлов выгрузки) будет оговариваться отдельно.

Логическая память - абстракция, отражающая взгляд пользователя на то, как организованы его программы и хранятся данные. С точки зрения пользователя его выполняемая программа (процесс) представляет собой совокупность блоков переменного размера, содержащих однородную информацию (данные, код, стек и т.д.). Обычно такие модули называют ). Адрес при этом перестает быть линейным и состоит из нескольких компонентов, например, номера сегмента и смещения внутри сегмента. Кроме того, с сегментами принято связывать атрибуты: права доступа или типы операций, которые разрешается производить с данными, хранящимися в сегменте.

(рис 9.1) Расположение сегментов процессов в памяти компьютера

Логические адреса внутри сегментов могут быть сформированы на этапе компиляции. При этом символические имена связываются с перемещаемыми адресами (такими, как n байт от начала модуля). Другим примером логического адреса может быть адрес, полученный программой в результате операции выделения области памяти (allocation). Иногда говорят, что логический адрес - это адрес, который генерирует процессор. Совокупность всех логических адресов называется логическим (виртуальным) адресным пространством.

Связывание адресов

Будучи виртуальной (абстрактной) машиной, ОС должна привести в соответствие взгляд пользователя на организацию его программы с реальным хранением информации в физической памяти. Эта проблема традиционно называется проблемой связывания логического и физического адресов (см. рис 9.2). Также употребляются термины привязка адреса, трансляция адреса, разрешение адреса и т.д. В ОС Windows это делается на этапе выполнения, то есть в момент обращения к логическому адресу менеджер памяти находит его вызовы в физической памяти.

(рис 9.2) Формирование логического адреса и связывание логического адреса с физическим

В современных вычислительных системах типичной является ситуация, когда объем логической памяти существенно превышает объем оперативной. В этом случае логический адрес может быть связан с адресом во внешней памяти.

Рассмотрим теперь алгоритмы и структуры данных, используемые для описания логической и физической памяти ОС Windows, а также применяемую схему связывания адресов. В каком-то смысле виртуальная память представляет собой интерфейс системы управления памятью, а ее отображение в физическую память и управление физической памятью относятся к особенностям реализации.

Общее описание виртуальной сегментно-страничной памяти ОС Windows

Размер пользовательского процесса ограничен объемом логического адресного пространства. Характерный размер логической памяти определяется разрядностью архитектуры и составляет для современных систем 232 (в недалеком будущем 264) байт. Эта величина обычно существенно превышает объем оперативной памяти, поэтому часть пользовательского процесса прозрачным образом может быть размещена во внешней памяти. Поэтому у пользователя создается иллюзия того, что он имеет дело с виртуальной памятью, отличной от реальной, размер которой потенциально больше, чем размер оперативной памяти. В дальнейшем наряду с термином "логическая память" будет употребляться термин "виртуальная память".

Для определения схемы виртуальной памяти, реализованной в ОС Windows, лучше всего подходит термин "сегментно-страничная виртуальная память". Подробное описание сегментно-страничной модели можно найти в . Для нее характерно представление адресного пространства процесса в виде набора сегментов переменного размера, содержащих однородную информацию (данные, текст программы, стек, сегмент разделяемой памяти и др.). Для удобства отображения на физическую память каждый сегмент делится на страницы - блоки фиксированного размера, при этом физическая память делится на блоки того же размера - страничные кадры (фреймы). Функция связывания логического адреса с физическим возлагается на таблицу страниц, которая каждой логической странице сегмента ставит в соответствие страничный кадр. В тех случаях, когда для нужной страницы не находится места в оперативной памяти (page fault), она подкачивается с диска. Заметим, что в каноническом виде данной схемы каждый сегмент процесса находится в отдельном логическом адресном пространстве и использует свою собственную таблицу страниц. Последнее обстоятельство, в силу сложной организации и большого объема таблицы страниц, имеет следствием тот факт, что реальные системы редко придерживаются канонической формы.

Сегментно-страничная модель памяти, реализованная в ОС Windows, также имеет свою специфику. Например, аппаратная поддержка сегментации, предлагаемая архитектурой Intel, используется в минимальной степени, а такие фрагменты адресного пространства процесса, как код, данные и др., описываются при помощи специальных структур данных и называются регионами (regions).

Одна из задач, которая решается при этом, - избежать появления в системе большого количества таблиц страниц за счет организации неперекрывающихся регионов в одном виртуальном пространстве, для описания которого хватает одной таблицы страниц. Таким образом, одна таблица страниц будет отводиться для всех сегментов памяти процесса. То, как это делается можно увидеть на рис 9.3. Задействовано всего четыре аппаратных сегмента с номерами селекторов 08, 10, 1b и 23. Первый используется для адресации кода ОС и имеет атрибуты RE, второй с атрибутами RW - для данных и стека ОС, третий с атрибутами RE - для кода пользовательского процесса, а четвертый с атрибутами RW - для данных и стека пользовательского процесса. Первые два сегмента недоступны для непривилегированного режима работы процессора.

При этом все организовано так, чтобы используемые виртуальные адреса внутри сегментов не перекрывались. В результате получается плоское 32-разрядное пространство, отображаемое на физическую память при помощи одной двухуровневой таблицы страниц.

(рис 9.3) Образование неперекрывающихся регионов (программных сегментов) в линейном виртуальном адресном пространстве процесса

Любопытно, что наличие у аппаратного сегмента атрибута не является препятствием для нецелевого использования хранимой в сегменте информации. Например, код процесса, находящийся в сегменте 1b, может быть доступен через 23-й сегмент с атрибутами RW. Собственно защита регионов организована на уровне их описателей, которые хранятся в таблице описателей VAD (virtual address descriptors) в адресном пространстве процесса. Таким образом, аппаратная поддержка сегментации обеспечивает лишь минимальную защиту - невозможность доступа к данным ОС из непривилегированного режима. Можно сказать, что в ОС Windows осуществляется программная поддержка сегментации (в данном случае регионов). Между прочим, многие другие ОС (например, Linux) ведут себя аналогично. Программная поддержка сегментов более универсальна и способствует большей переносимости кода. В дальнейшем для обозначения непрерывного фрагмента виртуального адресного пространства, содержащего однородную информацию, будет использоваться термин "регион".

Таблица страниц ставит в соответствие виртуальной странице номер страничного кадра в оперативной памяти. Для описания совокупности занятых и свободных страничных кадров ОС Windows использует базу данных PFN (page frame number). В силу несоответствия размеров виртуальной и оперативной памяти достаточно типичной является ситуация отсутствия нужной страницы в оперативной памяти (page fault). К счастью, копии всех задействованных виртуальных страниц хранятся на диске (так называемые теневые страницы). В случае обращения к отсутствующей странице ОС должна разыскать соответствующую теневую страницу и организовать ее подкачку с диска.

Учет совокупности теневых страниц сопряжен с трудностями, которые обусловлены разреженностью используемых виртуальных адресов. Так, например, неизменяемые страницы кода программы берутся непосредственно из выполняемых файлов (техника - отображение файла, содержащего код программы, в память). Страницы, подверженные изменениям, периодически записываются в специальные файлы выгрузки.

Таким образом, деятельность системы управления памятью сводится к созданию регионов (программных сегментов) в виртуальном адресном пространстве, выделения для них места в физической памяти (частично в оперативной памяти и частично на диске) и прозрачное перенаправление обращений к виртуальным адресам к их аналогам в физической памяти. Регионы создаются операционной системой. Иногда это происходит по инициативе пользовательской программы (например, в результате вызова функций VirtualAlloc, CreateFileMapping, CreateHeap и др.). Существенная часть деятельности менеджера памяти связана с оптимизацией. В частности, много усилий затрачивается на сокращение количества обращений к внешней памяти

Перейдем теперь к более детальному рассмотрению описанной схемы. Вначале изучим виртуальное адресное пространство процесса, затем посмотрим, как ключевая информация размещается в физической памяти. Проблема связывания адресов решается, главным образом, за счет аппаратных средств архитектуры, поэтому эти вопросы будут затрагиваться по мере необходимости.

Инструментальные средства наблюдения за работой менеджера памяти

Из инструментальных средств Windows, описанных в лекции 2, для лучшего практического ознакомления с деятельностью по управлению памятью в работе будут активно использоваться вкладки "Процессы и "Быстродействие" диспетчера задач, а также разнообразные счетчики производительности, за поведением которых можно следить из оснастки "Производительность" ("Системный монитор") административной консоли панели управления.

Наиболее интересную информацию содержат счетчики, относящиеся к конкретному процессу: количество байт виртуальной памяти, файла подкачки, ошибок страницы, рабочего множества и т.д., и общесистемные счетчики: количество байт выделенной виртуальной памяти, элементов таблицы страниц и др.

Полезными оказываются и некоторые общедоступные утилиты, например, утилита наблюдения за ошибками страниц pfmon.

Авторы книги отдельные аспекты функционирования менеджера памяти иллюстрируют с помощью отладчика.

Виртуальное адресное пространство процесса

В 32-битных системах процессор может сгенерировать 32-битный адрес. Это означает, что каждому процессу выделяется диапазон виртуальных адресов от ). Таким образом, система управления памятью позволяет пользовательской программе с помощью Win32 API записать нужный байт в любую виртуальную ячейку из этого диапазона адресов. Адреса верхней части виртуальной памяти используется для кода и данных режима ядра и других системных нужд.

По умолчанию адресное пространство каждого процесса изолировано. Данные двух разных процессов, записанные по одному и тому же виртуальному адресу, оказываются в разных страницах физической памяти при помощи корректной работы системы трансляции адреса. В ряде случаев изоляция может быть частично снята (файлы, отображаемые в память; разделяемая память). Разумеется, в подобных случаях нужно отдельно обеспечить контроль доступа к области памяти, для чего создается отдельный объект (объект-секция или объект-раздел, section object), включающий атрибуты защиты. Ниже будет также приведен пример контроля процессом памяти другого процесса - прием, которым активно пользуются отладчики.

Регионы в виртуальном адресном пространстве

Вначале все виртуальное адресное пространство процесса свободно. Оно начинает заполняться по мере выполнения программы. Чтобы воспользоваться какой-либо частью этого пространства, в нем нужно создать регион, зарезервировав определенный диапазон адресов. Такая схема позволяет поддерживать разреженные адресные пространства.

Совокупность регионов описывается структурой VAD, которая организована в виде двоичного дерева и хранится в PCB (структура EPROCESS, см. ).

(рис 9.4) Совокупность регионов в пользовательской части (нижние 2 Гб) виртуального адресного пространства процесса

Создание (резервирование) региона и передача ему физической памяти

Для создания региона явным образом обычно используется функция VirtualAlloc (вызов ряда Win32 функций, таких, как CreateFileMapping или CreateHeap, также имеет следствием создание региона). В процессе создания региона выделяют два этапа: резервирование региона и передачу ему физической памяти (commit). Оба этапа выполняются в результате вызова VirtualAlloc и могут быть объединены. В итоге каждая виртуальная страница может оказаться в одном из трех состояний: свободная (free), зарезервированная (reserve) и переданная (committed)

Резервирование региона - быстрая операция, а в последующей передаче памяти не всегда возникает необходимость; таким образом, двухэтапный процесс выделения позволяет повысить эффективность системы управления памятью.

Резервирование региона предполагает выравнивание начала региона с учетом гранулярности памяти (обычно это 64 Кб). Кроме того, размер региона должен быть кратен объему страницы (4Кб для x86 процессора. Узнать размер страницы можно при помощи функции GetSystemInfo ). В случае успешного резервирования происходит коррекция дерева VAD.

Для создаваемого региона можно указать конкретный диапазон виртуальных адресов. Если этого не делать, то система просматривает виртуальное адресное пространство процесса, пытаясь найти непрерывную незарезервированную область нужного размера.

Чтобы использовать зарезервированный регион, ему нужно передать, то есть реально выделить, физическую память (физическую или внешнюю по усмотрению ОС). Нет необходимости передавать физическую память всему региону целиком. Более того, это рекомендуется делать поэтапно, по мере необходимости. Так обеспечивается экономия физической памяти. Например, сложные приложения, работающие с большими массивами данных, используют следующую стратегию. Вначале физическая память не передается. Как только происходит обращение к виртуальному адресу, под который не выделена память, она тут же выделяется. Как правило, подобные ситуации обрабатываются при помощи структурной обработки исключений.

Прогон программы выделения памяти при помощи функции Virtual Alloc

В программе, листинг которой приведен ниже, осуществляется резервирование региона размером 16 страниц виртуальной памяти и затем передача физической памяти его четвертой страницы. В верхнюю часть переданной памяти записывается строка, которая затем распечатывается. Легко убедиться, что выход за пределы переданной памяти при помощи переменной Shift приводит к ошибке исполнения. В конце регион освобождается при помощи функции VirtualFree.

#include <windows.h>
#include <stdio.h>

void main(void)
{
PVOID pMem = NULL;
char * String;
char * pMemCommited;
int nPageSize = 4096;
int Shift = 4080;

pMem =  VirtualAlloc(0, nPageSize*16, MEM_RESERVE, PAGE_READWRITE);
pMemCommited = (char *)pMem + 3 * nPageSize;
VirtualAlloc((PVOID) pMemCommited, nPageSize, MEM_COMMIT, PAGE_READWRITE);

String = pMemCommited + Shift;

sprintf(String,"Hello, world");
printf("%s\n", String);

VirtualFree(String, 0, MEM_RELEASE);
}

Прогон программы, демонстрирующей выделение больших массивов памяти

Рассмотрим следующий пример DemoVM.c. В программе в несколько этапов по нажатию клавиши "Enter" выделяются и передаются регионам большие массивы физической памяти. Необходимо осуществить наблюдение за выделением памяти процессу при помощи счетчика "Байт виртуальной памяти, выделенной процессу".

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
long SizeCommit = 0;
int nPages = 200;

SizeCommit = nPages * nPageSize;

getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();
}

Из текста программы видно, что каждый раз по нажатию клавиши "Enter" процессу передается 200 страниц (819200 байт) виртуальной памяти. Это легко проверить по соответствующему приращению счетчика "Байт виртуальной памяти" (см. рис 9.5).

(рис 9.5) Поведение счетчика "Байт виртуальной памяти", выделенной процессу DemoVM.

В качестве самостоятельного упражнения можно рекомендовать прогон данной программы с другими параметрами.

Регион куча

Как уже говорилось, в ряде случаев система сама резервирует регионы в адресном пространстве процесса. Примерами таких регионов могут служить регион куча и регион стека потока.

Куча (heap) - зарезервированный регион размером в одну и более страниц, который рекомендуется использовать для хранения множества небольших порций данных. В отличие от функции VirtualAlloc правила гранулярности выделения памяти при этом соблюдать не обязательно.

Передачей памяти кучам, а также учетом свободной и занятой памяти в куче занимается специальный диспетчер куч (heap manager). Эта деятельность не документирована.

Стандартная куча процесса размером 1 Мб (эту величину можно изменить) резервируется в момент создания процесса. Обычно куча динамически меняет свой размер (флаг growable ). Стандартную кучу процесса используют не только приложения, но и некоторые Win32-функции. Для использования стандартной кучи необходимо получить ее описатель при помощи функции GetProcessHeap.

При желании процесс может создать дополнительные кучи при помощи функции HeapCreate (обратная операция HeapDestroy ). Прикладная программа выделяет память в куче с помощью функции HeapAlloc, а освобождает при помощи HeapFree.

Поскольку кучами могут пользоваться все потоки процесса, по умолчанию организуется синхронизация (флаг SERIALIZE ), которую, хотя это и не рекомендуется, для повышения быстродействия можно отменить. Задачу синхронизации доступа потоков к куче можно также решить, создавая каждому потоку свою собственную кучу.

Прогон программы выделения памяти в стандартной куче

#include <windows.h>
#include <stdio.h>

void main(void)
{
 HANDLE hHeap;
 long Size = 1024;
 long Shift = 1017;
 char * pHeap;
 char * String;

 hHeap = GetProcessHeap();

 pHeap  = (char *) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, Size);
 if(pHeap == NULL) { printf("HeapAlloc error\n"); return; }

 String = pHeap + Shift;
 sprintf(String, "Hello, world");
 printf("Heap contents string: %s\n", String);

 HeapFree(hHeap,0,pHeap);
}

В приведенной программе происходит выделение массива памяти в стандартной куче процесса. Далее туда записывается текстовая строка, которая затем выводится на экран. Если возникает ситуация выхода за пределы выделенной памяти, которую легко смоделировать, увеличивая значение параметра Shift, - возникает ошибка исполнения.

В качестве самостоятельного упражнения можно рекомендовать наблюдение за наращиванием объема переданной в куче памяти при помощи счетчиков производительности. Объем виртуальной памяти процесса должен начать расти в случае выхода за пределы начального размера кучи (1 Мб) и при образовании дополнительных куч.

Регион стека потока. Сторожевые страницы

Для поддержки функционирования стека потока также резервируется соответствующий регион. Стек - динамическая структура. Принято, чтобы стек увеличивал свой размер в сторону уменьшения адресов. Сколько страниц памяти потребуется стеку потока, заранее не известно. Поэтому в ОС Windows организована поэтапная, по мере необходимости, передача физической памяти стеку при помощи механизма так называемых "сторожевых" страниц (guard page). Обращение к сторожевой странице имеет следствием уведомление системы об этом (исключительная ситуация 0x80000001 ), после чего флаг PAGE GUARD сбрасывается и со страницей можно работать как с обычной страницей преданной памяти. Сторожевая страница служит ловушкой для перехвата ссылок за ее пределы.

При создании потока для его стека резервируется регион размером 1 Мб (по умолчанию), и ему передается 2 страницы памяти (эти параметры могут быть изменены). Нижняя страница является сторожевой. Как только верхняя страница оказалась заполненной и произошло обращение к нижней странице, это замечается системой и региону передается еще одна страница, теперь уже она становится сторожевой. Вследствие такой тактики самая нижняя переданная региону стека страница всегда остается сторожевой и ее задача - просигнализировать системе о том, что объем переданной стеку памяти нужно увеличить.

Прогон программы, моделирующей обращение к сторожевым страницам

#include <windows.h>
#include <stdio.h>

void main(void)
{
PVOID pMem = NULL;
char * String;
char * pMemCommited;
int nPageSize = 4096;
int Shift = 4000;

pMem =  VirtualAlloc(0, nPageSize*16, 
MEM_RESERVE |MEM_COMMIT, PAGE_READWRITE| PAGE_GUARD );
pMemCommited = (char *)pMem + 3 * nPageSize;

String = pMemCommited + Shift;
__try {
sprintf(String,"Hello, world");
printf("Before exception number 0x80000001 string: %s \n", String);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
sprintf(String,"Hello, world");
printf("After exception number 0x80000001 string: %s \n", String);
}
VirtualFree(String, 0, MEM_RELEASE);
}

В приведенной программе происходит передача памяти региону и установка флага PAGE_GUARD для его страниц. Кроме того, используется структурная обработка исключений. В случае попытки записи текстовой строки на сторожевую страницу (в блоке try ) возникает исключительная ситуация exception 0x80000001. При повторной попытке записи на эту страницу (блок except ) подобная ситуация уже не возникает.

Написание, компиляция и прогон программы, моделирующей рост стека при помощи механизма сторожевых страниц

В качестве самостоятельного упражнения рекомендуется написать программу, в которой сторожевая страница все время находится на границе фрагмента переданной памяти. В случае обращения к сторожевой странице объем переданной процессу памяти нужно увеличить, а сторожевую страницу переместить.

Регион файла, отображаемого в память

Техника файлов, проецируемых в память (см. рис 9.4), активно используется новейшими ОС. Она позволяет пользователю решать такие задачи, как работа с данными файла при помощи операций копирования и перемещения байтов в памяти или организация совместного доступа к областям памяти. Этот механизм также активно используется самой операционной системой, например, для загрузки в память исполняемых модулей, динамических библиотек и отображения файла в буфер кэша для осуществления операций стандартного ввода-вывода.

Отображение файла в память означает резервирование региона нужного размера и передача ему соответствующего объема физической памяти (передавать память здесь тоже можно поэтапно). Однако, в отличие от обычных регионов, выгрузка фрагментов оперативной памяти во внешнюю память будет при этом осуществляться не в системную область внешней памяти ( pagefile.sys ), а непосредственно в отображаемый файл. Отображение может быть выполнено на конкретный диапазон виртуальных адресов, если это не противоречит местоположению уже существующих регионов виртуальной памяти.

Для отображения файла в память используется функция CreateFileMapping, а для получения указателя на отображенную область - функция MapViewOfFile. Успешное выполнение обеих операций позволяет прикладной программе работать с этой областью как с любым другим фрагментом выделенной памяти, в частности, изменять ее содержимое. В связи с этим возникает проблема соответствия (когерентности) содержимого региона и файла на диске. Операционная система старается обеспечить когерентность, однако в распоряжении пользователя есть возможность в любой момент сбросить содержимое памяти на диск при помощи функции FlushViewOfFile.

Другой интересный момент связан с тем, что система рассматривает проецируемый в память файл как объект многоцелевого назначения. Поэтому отображение файла в память сопровождается созданием сопутствующего объекта ядра (в данном случае это объект-секция) с именем в пространстве объектов, по которому он может быть доступен другим процессам, счетчикам ссылок с атрибутами защиты. Как обычно ссылка на объект хранится в таблице описателей каждого процесса, имеющего к объекту доступ.

Прогон программы, демонстрирующей отображение файла в память

#include <windows.h>
#include <stdio.h>

void main(void){

HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile( "MyFile.txt",                       // имя файла
                    GENERIC_READ | GENERIC_WRITE,       // режим доступа
                    FILE_SHARE_READ| FILE_SHARE_WRITE,  // совместный доступ                                
                    NULL,                               // защита по умолчанию                 
	              CREATE_ALWAYS,                      // способ создания
                    FILE_ATTRIBUTE_NORMAL,              // атрибуты файла
                    NULL);                              // файл атрибутов 
 
if (hFile == INVALID_HANDLE_VALUE)  printf("Could not open file\n");   

hMapFile = CreateFileMapping(hFile,    // описатель отображаемого файла 
    NULL,                              // атрибуты защиты по умолчанию
    PAGE_READWRITE,                    // режим доступа 
    0,                                 // старшее двойное слово размера буфера
    20,                                // младшее двойное слово размера буфера
   "MyFileObject");                    // имя объекта
 
if (hMapFile == NULL) { 
    printf("Could not create file-mapping object.\n"); return;
} 

lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла
 
if (lpMapAddress == NULL) { 
    printf("Could not map view of file.\n"); return;
} 

String = (char *)lpMapAddress;
sprintf(String, "Hello, world");
printf("%s\n", String);

if (!UnmapViewOfFile(lpMapAddress)) printf("Could not unmap view of file.\n"); 
}

Приведенная программа демонстрирует этапы создания файла, проецирования его в память, изменения его содержимого и отображения на диск.

Написание, компиляция и прогон программы, демонстрирующей различные аспекты проецирования файла в память

На основе предыдущей программы рекомендуется написать программу отображения файла в память с промежуточной выгрузкой файла на диск при помощи функции FlushViewOfFile. Рассмотрите различные варианты существования файла и его размеров до и после отображения.

Решение задачи совместного доступа к памяти будет приведено ниже.

Здесь мы временно прекратим изучение виртуальной памяти процесса. Основным итогом изложенного можно считать знакомство с возможностями ОС создавать в ней разнообразные регионы. В качестве самостоятельного упражнения можно рекомендовать анализ состояния виртуального адресного пространства при помощи функций ). Теперь перейдем к рассмотрению других аспектов функционирования менеджера памяти: структуре физической памяти и особенностям трансляции адреса.

Заключение

Система управления памятью является одной из наиболее важных в составе ОС. Традиционная схема предполагает связывание виртуального и физического адреса на стадии исполнения программы. Для управления виртуальным адресным пространством в нем принято организовывать сегменты (регионы), для описания которых используются структуры данных VAD (virtual address descriptors). Для создания региона и передачи ему физической памяти можно использовать функцию VirtualAlloc. Описана техника использования таких регионов, как куча процесса, стек потока и регион файла, отображаемого в память.

Страницы:

Введение

В компьютерах фон-неймановской архитектуры выполняемые программы вместе с обрабатываемыми ими данными должны находиться в оперативной памяти. Операционной системе приходится заниматься управлением памятью, то есть решать задачу распределения памяти между пользовательскими процессами и компонентами ОС. Часть ОС, которая отвечает за управление памятью, называется менеджером памяти.

Для описания системы управления памятью активно используются понятия физической и логической (виртуальной) памяти.

Физическая память является аппаратным запоминающим устройством компьютера. Менеджер памяти имеет дело с двумя уровнями физической памяти: оперативной (основной, первичной) и внешней, или вторичной. Оперативная память изготавливается с применением полупроводниковых технологий и теряет свое содержимое при отключении питания. Вторичная память (это, главным образом, диски) характеризуется гораздо более медленным доступом, однако имеет большую емкость и является энергонезависимой. Она используется в качестве расширения основной памяти. Обычно информация, хранимая в оперативной памяти, за исключением самых последних изменений, хранится также во внешней памяти. Если процессор не обнаруживает нужную информацию в оперативной памяти, он начинает искать ее во вторичной. Когда нужная информация найдена во внешней памяти, она переносится в оперативную память. Менеджер памяти старается по возможности снизить частоту обращений к вторичной памяти (свойство локальности или локализации обращений). В результате эффективное время доступа к памяти оказывается близким к времени доступа к оперативной памяти и составляет несколько десятков наносекунд.

Оперативная память представляет собой упорядоченный массив однобайтовых ячеек, каждая из которых имеет свой уникальный адрес (номер). Типовые операции - чтение и запись байта в ячейку с нужным номером. Обмен с внешней памятью обычно осуществляется блоками фиксированного размера. Совокупность адресов в физической памяти называется физическим адресным пространством.

К сожалению, многие термины, относящиеся к системе управления памятью, как и в информатике вообще, перегружены. Поэтому в дальнейшем термин "физическая память" будет относиться именно к оперативной памяти, а использование внешней памяти (дисковой, файлов выгрузки) будет оговариваться отдельно.

Логическая память - абстракция, отражающая взгляд пользователя на то, как организованы его программы и хранятся данные. С точки зрения пользователя его выполняемая программа (процесс) представляет собой совокупность блоков переменного размера, содержащих однородную информацию (данные, код, стек и т.д.). Обычно такие модули называют ). Адрес при этом перестает быть линейным и состоит из нескольких компонентов, например, номера сегмента и смещения внутри сегмента. Кроме того, с сегментами принято связывать атрибуты: права доступа или типы операций, которые разрешается производить с данными, хранящимися в сегменте.

(рис 9.1) Расположение сегментов процессов в памяти компьютера

Логические адреса внутри сегментов могут быть сформированы на этапе компиляции. При этом символические имена связываются с перемещаемыми адресами (такими, как n байт от начала модуля). Другим примером логического адреса может быть адрес, полученный программой в результате операции выделения области памяти (allocation). Иногда говорят, что логический адрес - это адрес, который генерирует процессор. Совокупность всех логических адресов называется логическим (виртуальным) адресным пространством.

Связывание адресов

Будучи виртуальной (абстрактной) машиной, ОС должна привести в соответствие взгляд пользователя на организацию его программы с реальным хранением информации в физической памяти. Эта проблема традиционно называется проблемой связывания логического и физического адресов (см. рис 9.2). Также употребляются термины привязка адреса, трансляция адреса, разрешение адреса и т.д. В ОС Windows это делается на этапе выполнения, то есть в момент обращения к логическому адресу менеджер памяти находит его вызовы в физической памяти.

(рис 9.2) Формирование логического адреса и связывание логического адреса с физическим

В современных вычислительных системах типичной является ситуация, когда объем логической памяти существенно превышает объем оперативной. В этом случае логический адрес может быть связан с адресом во внешней памяти.

Рассмотрим теперь алгоритмы и структуры данных, используемые для описания логической и физической памяти ОС Windows, а также применяемую схему связывания адресов. В каком-то смысле виртуальная память представляет собой интерфейс системы управления памятью, а ее отображение в физическую память и управление физической памятью относятся к особенностям реализации.

Общее описание виртуальной сегментно-страничной памяти ОС Windows

Размер пользовательского процесса ограничен объемом логического адресного пространства. Характерный размер логической памяти определяется разрядностью архитектуры и составляет для современных систем 232 (в недалеком будущем 264) байт. Эта величина обычно существенно превышает объем оперативной памяти, поэтому часть пользовательского процесса прозрачным образом может быть размещена во внешней памяти. Поэтому у пользователя создается иллюзия того, что он имеет дело с виртуальной памятью, отличной от реальной, размер которой потенциально больше, чем размер оперативной памяти. В дальнейшем наряду с термином "логическая память" будет употребляться термин "виртуальная память".

Для определения схемы виртуальной памяти, реализованной в ОС Windows, лучше всего подходит термин "сегментно-страничная виртуальная память". Подробное описание сегментно-страничной модели можно найти в . Для нее характерно представление адресного пространства процесса в виде набора сегментов переменного размера, содержащих однородную информацию (данные, текст программы, стек, сегмент разделяемой памяти и др.). Для удобства отображения на физическую память каждый сегмент делится на страницы - блоки фиксированного размера, при этом физическая память делится на блоки того же размера - страничные кадры (фреймы). Функция связывания логического адреса с физическим возлагается на таблицу страниц, которая каждой логической странице сегмента ставит в соответствие страничный кадр. В тех случаях, когда для нужной страницы не находится места в оперативной памяти (page fault), она подкачивается с диска. Заметим, что в каноническом виде данной схемы каждый сегмент процесса находится в отдельном логическом адресном пространстве и использует свою собственную таблицу страниц. Последнее обстоятельство, в силу сложной организации и большого объема таблицы страниц, имеет следствием тот факт, что реальные системы редко придерживаются канонической формы.

Сегментно-страничная модель памяти, реализованная в ОС Windows, также имеет свою специфику. Например, аппаратная поддержка сегментации, предлагаемая архитектурой Intel, используется в минимальной степени, а такие фрагменты адресного пространства процесса, как код, данные и др., описываются при помощи специальных структур данных и называются регионами (regions).

Одна из задач, которая решается при этом, - избежать появления в системе большого количества таблиц страниц за счет организации неперекрывающихся регионов в одном виртуальном пространстве, для описания которого хватает одной таблицы страниц. Таким образом, одна таблица страниц будет отводиться для всех сегментов памяти процесса. То, как это делается можно увидеть на рис 9.3. Задействовано всего четыре аппаратных сегмента с номерами селекторов 08, 10, 1b и 23. Первый используется для адресации кода ОС и имеет атрибуты RE, второй с атрибутами RW - для данных и стека ОС, третий с атрибутами RE - для кода пользовательского процесса, а четвертый с атрибутами RW - для данных и стека пользовательского процесса. Первые два сегмента недоступны для непривилегированного режима работы процессора.

При этом все организовано так, чтобы используемые виртуальные адреса внутри сегментов не перекрывались. В результате получается плоское 32-разрядное пространство, отображаемое на физическую память при помощи одной двухуровневой таблицы страниц.

(рис 9.3) Образование неперекрывающихся регионов (программных сегментов) в линейном виртуальном адресном пространстве процесса

Любопытно, что наличие у аппаратного сегмента атрибута не является препятствием для нецелевого использования хранимой в сегменте информации. Например, код процесса, находящийся в сегменте 1b, может быть доступен через 23-й сегмент с атрибутами RW. Собственно защита регионов организована на уровне их описателей, которые хранятся в таблице описателей VAD (virtual address descriptors) в адресном пространстве процесса. Таким образом, аппаратная поддержка сегментации обеспечивает лишь минимальную защиту - невозможность доступа к данным ОС из непривилегированного режима. Можно сказать, что в ОС Windows осуществляется программная поддержка сегментации (в данном случае регионов). Между прочим, многие другие ОС (например, Linux) ведут себя аналогично. Программная поддержка сегментов более универсальна и способствует большей переносимости кода. В дальнейшем для обозначения непрерывного фрагмента виртуального адресного пространства, содержащего однородную информацию, будет использоваться термин "регион".

Таблица страниц ставит в соответствие виртуальной странице номер страничного кадра в оперативной памяти. Для описания совокупности занятых и свободных страничных кадров ОС Windows использует базу данных PFN (page frame number). В силу несоответствия размеров виртуальной и оперативной памяти достаточно типичной является ситуация отсутствия нужной страницы в оперативной памяти (page fault). К счастью, копии всех задействованных виртуальных страниц хранятся на диске (так называемые теневые страницы). В случае обращения к отсутствующей странице ОС должна разыскать соответствующую теневую страницу и организовать ее подкачку с диска.

Учет совокупности теневых страниц сопряжен с трудностями, которые обусловлены разреженностью используемых виртуальных адресов. Так, например, неизменяемые страницы кода программы берутся непосредственно из выполняемых файлов (техника - отображение файла, содержащего код программы, в память). Страницы, подверженные изменениям, периодически записываются в специальные файлы выгрузки.

Таким образом, деятельность системы управления памятью сводится к созданию регионов (программных сегментов) в виртуальном адресном пространстве, выделения для них места в физической памяти (частично в оперативной памяти и частично на диске) и прозрачное перенаправление обращений к виртуальным адресам к их аналогам в физической памяти. Регионы создаются операционной системой. Иногда это происходит по инициативе пользовательской программы (например, в результате вызова функций VirtualAlloc, CreateFileMapping, CreateHeap и др.). Существенная часть деятельности менеджера памяти связана с оптимизацией. В частности, много усилий затрачивается на сокращение количества обращений к внешней памяти

Перейдем теперь к более детальному рассмотрению описанной схемы. Вначале изучим виртуальное адресное пространство процесса, затем посмотрим, как ключевая информация размещается в физической памяти. Проблема связывания адресов решается, главным образом, за счет аппаратных средств архитектуры, поэтому эти вопросы будут затрагиваться по мере необходимости.

Инструментальные средства наблюдения за работой менеджера памяти

Из инструментальных средств Windows, описанных в лекции 2, для лучшего практического ознакомления с деятельностью по управлению памятью в работе будут активно использоваться вкладки "Процессы и "Быстродействие" диспетчера задач, а также разнообразные счетчики производительности, за поведением которых можно следить из оснастки "Производительность" ("Системный монитор") административной консоли панели управления.

Наиболее интересную информацию содержат счетчики, относящиеся к конкретному процессу: количество байт виртуальной памяти, файла подкачки, ошибок страницы, рабочего множества и т.д., и общесистемные счетчики: количество байт выделенной виртуальной памяти, элементов таблицы страниц и др.

Полезными оказываются и некоторые общедоступные утилиты, например, утилита наблюдения за ошибками страниц pfmon.

Авторы книги отдельные аспекты функционирования менеджера памяти иллюстрируют с помощью отладчика.

Виртуальное адресное пространство процесса

В 32-битных системах процессор может сгенерировать 32-битный адрес. Это означает, что каждому процессу выделяется диапазон виртуальных адресов от ). Таким образом, система управления памятью позволяет пользовательской программе с помощью Win32 API записать нужный байт в любую виртуальную ячейку из этого диапазона адресов. Адреса верхней части виртуальной памяти используется для кода и данных режима ядра и других системных нужд.

По умолчанию адресное пространство каждого процесса изолировано. Данные двух разных процессов, записанные по одному и тому же виртуальному адресу, оказываются в разных страницах физической памяти при помощи корректной работы системы трансляции адреса. В ряде случаев изоляция может быть частично снята (файлы, отображаемые в память; разделяемая память). Разумеется, в подобных случаях нужно отдельно обеспечить контроль доступа к области памяти, для чего создается отдельный объект (объект-секция или объект-раздел, section object), включающий атрибуты защиты. Ниже будет также приведен пример контроля процессом памяти другого процесса - прием, которым активно пользуются отладчики.

Регионы в виртуальном адресном пространстве

Вначале все виртуальное адресное пространство процесса свободно. Оно начинает заполняться по мере выполнения программы. Чтобы воспользоваться какой-либо частью этого пространства, в нем нужно создать регион, зарезервировав определенный диапазон адресов. Такая схема позволяет поддерживать разреженные адресные пространства.

Совокупность регионов описывается структурой VAD, которая организована в виде двоичного дерева и хранится в PCB (структура EPROCESS, см. ).

(рис 9.4) Совокупность регионов в пользовательской части (нижние 2 Гб) виртуального адресного пространства процесса

Создание (резервирование) региона и передача ему физической памяти

Для создания региона явным образом обычно используется функция VirtualAlloc (вызов ряда Win32 функций, таких, как CreateFileMapping или CreateHeap, также имеет следствием создание региона). В процессе создания региона выделяют два этапа: резервирование региона и передачу ему физической памяти (commit). Оба этапа выполняются в результате вызова VirtualAlloc и могут быть объединены. В итоге каждая виртуальная страница может оказаться в одном из трех состояний: свободная (free), зарезервированная (reserve) и переданная (committed)

Резервирование региона - быстрая операция, а в последующей передаче памяти не всегда возникает необходимость; таким образом, двухэтапный процесс выделения позволяет повысить эффективность системы управления памятью.

Резервирование региона предполагает выравнивание начала региона с учетом гранулярности памяти (обычно это 64 Кб). Кроме того, размер региона должен быть кратен объему страницы (4Кб для x86 процессора. Узнать размер страницы можно при помощи функции GetSystemInfo ). В случае успешного резервирования происходит коррекция дерева VAD.

Для создаваемого региона можно указать конкретный диапазон виртуальных адресов. Если этого не делать, то система просматривает виртуальное адресное пространство процесса, пытаясь найти непрерывную незарезервированную область нужного размера.

Чтобы использовать зарезервированный регион, ему нужно передать, то есть реально выделить, физическую память (физическую или внешнюю по усмотрению ОС). Нет необходимости передавать физическую память всему региону целиком. Более того, это рекомендуется делать поэтапно, по мере необходимости. Так обеспечивается экономия физической памяти. Например, сложные приложения, работающие с большими массивами данных, используют следующую стратегию. Вначале физическая память не передается. Как только происходит обращение к виртуальному адресу, под который не выделена память, она тут же выделяется. Как правило, подобные ситуации обрабатываются при помощи структурной обработки исключений.

Прогон программы выделения памяти при помощи функции Virtual Alloc

В программе, листинг которой приведен ниже, осуществляется резервирование региона размером 16 страниц виртуальной памяти и затем передача физической памяти его четвертой страницы. В верхнюю часть переданной памяти записывается строка, которая затем распечатывается. Легко убедиться, что выход за пределы переданной памяти при помощи переменной Shift приводит к ошибке исполнения. В конце регион освобождается при помощи функции VirtualFree.

#include <windows.h>
#include <stdio.h>

void main(void)
{
PVOID pMem = NULL;
char * String;
char * pMemCommited;
int nPageSize = 4096;
int Shift = 4080;

pMem =  VirtualAlloc(0, nPageSize*16, MEM_RESERVE, PAGE_READWRITE);
pMemCommited = (char *)pMem + 3 * nPageSize;
VirtualAlloc((PVOID) pMemCommited, nPageSize, MEM_COMMIT, PAGE_READWRITE);

String = pMemCommited + Shift;

sprintf(String,"Hello, world");
printf("%s\n", String);

VirtualFree(String, 0, MEM_RELEASE);
}

Прогон программы, демонстрирующей выделение больших массивов памяти

Рассмотрим следующий пример DemoVM.c. В программе в несколько этапов по нажатию клавиши "Enter" выделяются и передаются регионам большие массивы физической памяти. Необходимо осуществить наблюдение за выделением памяти процессу при помощи счетчика "Байт виртуальной памяти, выделенной процессу".

#include <windows.h>
#include <stdio.h>

void main(void)
{

PVOID pMem = NULL;
int nPageSize = 4096;
long SizeCommit = 0;
int nPages = 200;

SizeCommit = nPages * nPageSize;

getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();

pMem =  VirtualAlloc(0, SizeCommit, 
MEM_RESERVE| MEM_COMMIT, PAGE_READWRITE);
if(pMem == NULL) printf("VirtualAlloc Error\n");
getchar();
}

Из текста программы видно, что каждый раз по нажатию клавиши "Enter" процессу передается 200 страниц (819200 байт) виртуальной памяти. Это легко проверить по соответствующему приращению счетчика "Байт виртуальной памяти" (см. рис 9.5).

(рис 9.5) Поведение счетчика "Байт виртуальной памяти", выделенной процессу DemoVM.

В качестве самостоятельного упражнения можно рекомендовать прогон данной программы с другими параметрами.

Регион куча

Как уже говорилось, в ряде случаев система сама резервирует регионы в адресном пространстве процесса. Примерами таких регионов могут служить регион куча и регион стека потока.

Куча (heap) - зарезервированный регион размером в одну и более страниц, который рекомендуется использовать для хранения множества небольших порций данных. В отличие от функции VirtualAlloc правила гранулярности выделения памяти при этом соблюдать не обязательно.

Передачей памяти кучам, а также учетом свободной и занятой памяти в куче занимается специальный диспетчер куч (heap manager). Эта деятельность не документирована.

Стандартная куча процесса размером 1 Мб (эту величину можно изменить) резервируется в момент создания процесса. Обычно куча динамически меняет свой размер (флаг growable ). Стандартную кучу процесса используют не только приложения, но и некоторые Win32-функции. Для использования стандартной кучи необходимо получить ее описатель при помощи функции GetProcessHeap.

При желании процесс может создать дополнительные кучи при помощи функции HeapCreate (обратная операция HeapDestroy ). Прикладная программа выделяет память в куче с помощью функции HeapAlloc, а освобождает при помощи HeapFree.

Поскольку кучами могут пользоваться все потоки процесса, по умолчанию организуется синхронизация (флаг SERIALIZE ), которую, хотя это и не рекомендуется, для повышения быстродействия можно отменить. Задачу синхронизации доступа потоков к куче можно также решить, создавая каждому потоку свою собственную кучу.

Прогон программы выделения памяти в стандартной куче

#include <windows.h>
#include <stdio.h>

void main(void)
{
 HANDLE hHeap;
 long Size = 1024;
 long Shift = 1017;
 char * pHeap;
 char * String;

 hHeap = GetProcessHeap();

 pHeap  = (char *) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, Size);
 if(pHeap == NULL) { printf("HeapAlloc error\n"); return; }

 String = pHeap + Shift;
 sprintf(String, "Hello, world");
 printf("Heap contents string: %s\n", String);

 HeapFree(hHeap,0,pHeap);
}

В приведенной программе происходит выделение массива памяти в стандартной куче процесса. Далее туда записывается текстовая строка, которая затем выводится на экран. Если возникает ситуация выхода за пределы выделенной памяти, которую легко смоделировать, увеличивая значение параметра Shift, - возникает ошибка исполнения.

В качестве самостоятельного упражнения можно рекомендовать наблюдение за наращиванием объема переданной в куче памяти при помощи счетчиков производительности. Объем виртуальной памяти процесса должен начать расти в случае выхода за пределы начального размера кучи (1 Мб) и при образовании дополнительных куч.

Регион стека потока. Сторожевые страницы

Для поддержки функционирования стека потока также резервируется соответствующий регион. Стек - динамическая структура. Принято, чтобы стек увеличивал свой размер в сторону уменьшения адресов. Сколько страниц памяти потребуется стеку потока, заранее не известно. Поэтому в ОС Windows организована поэтапная, по мере необходимости, передача физической памяти стеку при помощи механизма так называемых "сторожевых" страниц (guard page). Обращение к сторожевой странице имеет следствием уведомление системы об этом (исключительная ситуация 0x80000001 ), после чего флаг PAGE GUARD сбрасывается и со страницей можно работать как с обычной страницей преданной памяти. Сторожевая страница служит ловушкой для перехвата ссылок за ее пределы.

При создании потока для его стека резервируется регион размером 1 Мб (по умолчанию), и ему передается 2 страницы памяти (эти параметры могут быть изменены). Нижняя страница является сторожевой. Как только верхняя страница оказалась заполненной и произошло обращение к нижней странице, это замечается системой и региону передается еще одна страница, теперь уже она становится сторожевой. Вследствие такой тактики самая нижняя переданная региону стека страница всегда остается сторожевой и ее задача - просигнализировать системе о том, что объем переданной стеку памяти нужно увеличить.

Прогон программы, моделирующей обращение к сторожевым страницам

#include <windows.h>
#include <stdio.h>

void main(void)
{
PVOID pMem = NULL;
char * String;
char * pMemCommited;
int nPageSize = 4096;
int Shift = 4000;

pMem =  VirtualAlloc(0, nPageSize*16, 
MEM_RESERVE |MEM_COMMIT, PAGE_READWRITE| PAGE_GUARD );
pMemCommited = (char *)pMem + 3 * nPageSize;

String = pMemCommited + Shift;
__try {
sprintf(String,"Hello, world");
printf("Before exception number 0x80000001 string: %s \n", String);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
sprintf(String,"Hello, world");
printf("After exception number 0x80000001 string: %s \n", String);
}
VirtualFree(String, 0, MEM_RELEASE);
}

В приведенной программе происходит передача памяти региону и установка флага PAGE_GUARD для его страниц. Кроме того, используется структурная обработка исключений. В случае попытки записи текстовой строки на сторожевую страницу (в блоке try ) возникает исключительная ситуация exception 0x80000001. При повторной попытке записи на эту страницу (блок except ) подобная ситуация уже не возникает.

Написание, компиляция и прогон программы, моделирующей рост стека при помощи механизма сторожевых страниц

В качестве самостоятельного упражнения рекомендуется написать программу, в которой сторожевая страница все время находится на границе фрагмента переданной памяти. В случае обращения к сторожевой странице объем переданной процессу памяти нужно увеличить, а сторожевую страницу переместить.

Регион файла, отображаемого в память

Техника файлов, проецируемых в память (см. рис 9.4), активно используется новейшими ОС. Она позволяет пользователю решать такие задачи, как работа с данными файла при помощи операций копирования и перемещения байтов в памяти или организация совместного доступа к областям памяти. Этот механизм также активно используется самой операционной системой, например, для загрузки в память исполняемых модулей, динамических библиотек и отображения файла в буфер кэша для осуществления операций стандартного ввода-вывода.

Отображение файла в память означает резервирование региона нужного размера и передача ему соответствующего объема физической памяти (передавать память здесь тоже можно поэтапно). Однако, в отличие от обычных регионов, выгрузка фрагментов оперативной памяти во внешнюю память будет при этом осуществляться не в системную область внешней памяти ( pagefile.sys ), а непосредственно в отображаемый файл. Отображение может быть выполнено на конкретный диапазон виртуальных адресов, если это не противоречит местоположению уже существующих регионов виртуальной памяти.

Для отображения файла в память используется функция CreateFileMapping, а для получения указателя на отображенную область - функция MapViewOfFile. Успешное выполнение обеих операций позволяет прикладной программе работать с этой областью как с любым другим фрагментом выделенной памяти, в частности, изменять ее содержимое. В связи с этим возникает проблема соответствия (когерентности) содержимого региона и файла на диске. Операционная система старается обеспечить когерентность, однако в распоряжении пользователя есть возможность в любой момент сбросить содержимое памяти на диск при помощи функции FlushViewOfFile.

Другой интересный момент связан с тем, что система рассматривает проецируемый в память файл как объект многоцелевого назначения. Поэтому отображение файла в память сопровождается созданием сопутствующего объекта ядра (в данном случае это объект-секция) с именем в пространстве объектов, по которому он может быть доступен другим процессам, счетчикам ссылок с атрибутами защиты. Как обычно ссылка на объект хранится в таблице описателей каждого процесса, имеющего к объекту доступ.

Прогон программы, демонстрирующей отображение файла в память

#include <windows.h>
#include <stdio.h>

void main(void){

HANDLE hMapFile;
LPVOID lpMapAddress;
HANDLE hFile; 
char * String;
 
hFile = CreateFile( "MyFile.txt",                       // имя файла
                    GENERIC_READ | GENERIC_WRITE,       // режим доступа
                    FILE_SHARE_READ| FILE_SHARE_WRITE,  // совместный доступ                                
                    NULL,                               // защита по умолчанию                 
	              CREATE_ALWAYS,                      // способ создания
                    FILE_ATTRIBUTE_NORMAL,              // атрибуты файла
                    NULL);                              // файл атрибутов 
 
if (hFile == INVALID_HANDLE_VALUE)  printf("Could not open file\n");   

hMapFile = CreateFileMapping(hFile,    // описатель отображаемого файла 
    NULL,                              // атрибуты защиты по умолчанию
    PAGE_READWRITE,                    // режим доступа 
    0,                                 // старшее двойное слово размера буфера
    20,                                // младшее двойное слово размера буфера
   "MyFileObject");                    // имя объекта
 
if (hMapFile == NULL) { 
    printf("Could not create file-mapping object.\n"); return;
} 

lpMapAddress = MapViewOfFile(hMapFile,  // описатель отображаемого файла
    FILE_MAP_ALL_ACCESS,                // режимы доступа
    0, 0,                               // отображение файла с начала
    0);                                 // отображение целого файла
 
if (lpMapAddress == NULL) { 
    printf("Could not map view of file.\n"); return;
} 

String = (char *)lpMapAddress;
sprintf(String, "Hello, world");
printf("%s\n", String);

if (!UnmapViewOfFile(lpMapAddress)) printf("Could not unmap view of file.\n"); 
}

Приведенная программа демонстрирует этапы создания файла, проецирования его в память, изменения его содержимого и отображения на диск.

Написание, компиляция и прогон программы, демонстрирующей различные аспекты проецирования файла в память

На основе предыдущей программы рекомендуется написать программу отображения файла в память с промежуточной выгрузкой файла на диск при помощи функции FlushViewOfFile. Рассмотрите различные варианты существования файла и его размеров до и после отображения.

Решение задачи совместного доступа к памяти будет приведено ниже.

Здесь мы временно прекратим изучение виртуальной памяти процесса. Основным итогом изложенного можно считать знакомство с возможностями ОС создавать в ней разнообразные регионы. В качестве самостоятельного упражнения можно рекомендовать анализ состояния виртуального адресного пространства при помощи функций ). Теперь перейдем к рассмотрению других аспектов функционирования менеджера памяти: структуре физической памяти и особенностям трансляции адреса.

Заключение

Система управления памятью является одной из наиболее важных в составе ОС. Традиционная схема предполагает связывание виртуального и физического адреса на стадии исполнения программы. Для управления виртуальным адресным пространством в нем принято организовывать сегменты (регионы), для описания которых используются структуры данных VAD (virtual address descriptors). Для создания региона и передачи ему физической памяти можно использовать функцию VirtualAlloc. Описана техника использования таких регионов, как куча процесса, стек потока и регион файла, отображаемого в память.

Вернуться к учебному плану