Московский физико-технический институт
Опубликован: 12.12.2007 | Доступ: свободный | Студентов: 5497 / 1832 | Оценка: 4.34 / 4.14 | Длительность: 13:57:00
ISBN: 978-5-94774-827-7
Лекция 9:

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

< Лекция 8 || Лекция 9: 123 || Лекция 10 >

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

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

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

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

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

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

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

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

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

В 32-битных системах процессор может сгенерировать 32-битный адрес. Это означает, что каждому процессу выделяется диапазон виртуальных адресов от 0x00000000 до 0xFFFFFFFF. Эти 4 Гб адресов система делит примерно пополам, и для кода и данных пользовательского режима отводятся 2 Гб в нижней части памяти. Если быть более точным, то речь идет об виртуальных адресах, начиная с 0x00010000 и кончая 07FFEFFFF (см. [ Руссинович ] ). Таким образом, система управления памятью позволяет пользовательской программе с помощью Win32 API записать нужный байт в любую виртуальную ячейку из этого диапазона адресов. Адреса верхней части виртуальной памяти используется для кода и данных режима ядра и других системных нужд.

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

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

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

Совокупность регионов описывается структурой VAD, которая организована в виде двоичного дерева и хранится в PCB (структура EPROCESS, см. "Реализация процессов и потоков" ) процесса. Для вновь создаваемого региона можно запросить любой диапазон адресов с учетом уже существующих регионов. Первые регионы для кода, стека, стандартной кучи процесса и ряд других создает операционная система в момент загрузки процесса. Последующие регионы приложение создает самостоятельно (см. рис. 9.4).

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

Рис. 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).

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

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

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

< Лекция 8 || Лекция 9: 123 || Лекция 10 >
Ирина Оленина
Ирина Оленина
Николай Сергеев
Николай Сергеев

Здравствуйте! Интересует следующий момент. Как осуществляется контроль доступа по тому или иному адресу с точки зрения обработки процессом кода процесса. Насколько я понял, есть два способа: задание через атрибуты сегмента (чтение, запись, исполнение), либо через атрибуты PDE/PTE (чтение, запись). Но как следует из многочисленных источников, эти механизмы в ОС Windows почти не задействованы. Там ключевую роль играет менеджер памяти, задающий регионы, назначающий им атрибуты (PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_NOACCESS, PAGE_GUARD: их гораздо больше, чем можно было бы задать для сегмента памяти) и контролирующий доступ к этим регионам. Непонятно, на каком этапе может включаться в работу этот менеджер памяти? Поскольку процессор может встретить инструкцию: записать такие данные по такому адресу (даже, если этот адрес относится к региону, выделенному менеджером памяти с атрибутом, например, PAGE_READONLY) и ничего не мешает ему это выполнить. Таким образом, менеджер памяти остается в стороне не участвует в процессе...