В компьютерах
Для описания системы управления памятью активно используются понятия физической и логической (виртуальной) памяти.
Физическая память является аппаратным запоминающим устройством компьютера. Менеджер памяти имеет дело с двумя уровнями физической памяти: оперативной (основной, первичной) и внешней, или вторичной. Оперативная память изготавливается с применением полупроводниковых технологий и теряет свое содержимое при отключении питания. Вторичная память (это, главным образом, диски) характеризуется гораздо более медленным доступом, однако имеет большую емкость и является энергонезависимой. Она используется в качестве расширения основной памяти. Обычно информация, хранимая в оперативной памяти, за исключением самых последних изменений, хранится также во внешней памяти. Если процессор не обнаруживает нужную информацию в оперативной памяти, он начинает искать ее во вторичной. Когда нужная информация найдена во внешней памяти, она переносится в оперативную память. Менеджер памяти старается по возможности снизить частоту обращений к вторичной памяти (свойство локальности или локализации обращений). В результате эффективное время доступа к памяти оказывается близким к времени доступа к оперативной памяти и составляет несколько десятков наносекунд.
Оперативная память представляет собой
К сожалению, многие термины, относящиеся к системе управления памятью, как и в информатике вообще, перегружены. Поэтому в дальнейшем термин "физическая память" будет относиться именно к оперативной памяти, а использование внешней памяти (дисковой, файлов выгрузки) будет оговариваться отдельно.
(рис 9.1) Расположение сегментов процессов в памяти компьютераЛогические адреса внутри сегментов могут быть сформированы на этапе компиляции. При этом символические имена связываются с перемещаемыми адресами (такими, как n байт от начала модуля). Другим примером логического адреса может быть адрес, полученный программой в результате операции выделения области памяти (allocation). Иногда говорят, что логический адрес - это адрес, который генерирует процессор. Совокупность всех логических адресов называется логическим (виртуальным) адресным пространством.
Будучи виртуальной (абстрактной) машиной, ОС должна привести в соответствие взгляд пользователя на организацию его программы с реальным хранением информации в физической памяти. Эта проблема традиционно называется проблемой связывания логического и физического адресов (см. рис 9.2). Также употребляются термины привязка адреса, трансляция адреса, разрешение адреса и т.д. В ОС Windows это делается на этапе выполнения, то есть в момент обращения к логическому адресу менеджер памяти находит его вызовы в физической памяти.
(рис 9.2) Формирование логического адреса и связывание логического адреса с физическим В современных вычислительных системах типичной является ситуация, когда объем логической памяти существенно превышает объем оперативной. В этом случае логический адрес может быть связан с адресом во внешней памяти.
Рассмотрим теперь алгоритмы и структуры данных, используемые для описания логической и физической памяти ОС Windows, а также применяемую схему связывания адресов. В каком-то смысле виртуальная память представляет собой интерфейс системы управления памятью, а ее отображение в физическую память и управление физической памятью относятся к особенностям реализации.
Размер пользовательского процесса ограничен объемом логического адресного пространства. Характерный размер логической памяти определяется разрядностью архитектуры и составляет для современных систем 232 (в недалеком будущем 264) байт. Эта величина обычно существенно превышает объем оперативной памяти, поэтому часть пользовательского процесса прозрачным образом может быть размещена во внешней памяти. Поэтому у пользователя создается иллюзия того, что он имеет дело с виртуальной памятью, отличной от реальной, размер которой потенциально больше, чем размер оперативной памяти. В дальнейшем наряду с термином "
Для определения схемы виртуальной памяти, реализованной в ОС Windows, лучше всего подходит термин "сегментно-
Сегментно-
Одна из задач, которая решается при этом, - избежать появления в системе большого количества таблиц страниц за счет организации неперекрывающихся регионов в одном виртуальном пространстве, для описания которого хватает одной таблицы страниц. Таким образом, одна таблица страниц будет отводиться для всех сегментов памяти процесса. То, как это делается можно увидеть на рис 9.3. Задействовано всего четыре аппаратных сегмента с номерами селекторов 08, 10, 1b и 23. Первый используется для адресации кода ОС и имеет атрибуты RE, второй с атрибутами RW - для данных и стека ОС, третий с атрибутами RE - для кода пользовательского процесса, а четвертый с атрибутами RW - для данных и стека пользовательского процесса. Первые два сегмента недоступны для непривилегированного режима работы процессора.
При этом все организовано так, чтобы используемые виртуальные адреса внутри сегментов не перекрывались. В результате получается плоское 32-разрядное пространство, отображаемое на физическую память при помощи одной двухуровневой таблицы страниц.
(рис 9.3) Образование неперекрывающихся регионов (программных сегментов) в линейном виртуальном адресном пространстве процессаЛюбопытно, что наличие у аппаратного сегмента атрибута не является препятствием для нецелевого использования хранимой в сегменте информации. Например, код процесса, находящийся в сегменте 1b, может быть доступен через 23-й сегмент с атрибутами RW. Собственно защита регионов организована на уровне их описателей, которые хранятся в таблице описателей
Таблица страниц ставит в соответствие
Учет совокупности теневых страниц сопряжен с трудностями, которые обусловлены разреженностью используемых виртуальных адресов. Так, например, неизменяемые страницы кода программы берутся непосредственно из выполняемых файлов (техника - отображение файла, содержащего код программы, в память). Страницы, подверженные изменениям, периодически записываются в специальные файлы выгрузки.
Таким образом, деятельность системы управления памятью сводится к созданию регионов (программных сегментов) в виртуальном адресном пространстве, выделения для них места в физической памяти (частично в оперативной памяти и частично на диске) и прозрачное перенаправление обращений к виртуальным адресам к их аналогам в физической памяти. Регионы создаются операционной системой. Иногда это происходит по инициативе пользовательской программы (например, в результате вызова функций VirtualAlloc, CreateFileMapping, CreateHeap и др.). Существенная часть деятельности менеджера памяти связана с оптимизацией. В частности, много усилий затрачивается на сокращение количества обращений к внешней памяти
Перейдем теперь к более детальному рассмотрению описанной схемы. Вначале изучим виртуальное
Из инструментальных средств Windows, описанных в лекции 2, для лучшего практического ознакомления с деятельностью по управлению памятью в работе будут активно использоваться вкладки "Процессы и "Быстродействие" диспетчера задач, а также разнообразные счетчики производительности, за поведением которых можно следить из оснастки "Производительность" ("Системный монитор") административной консоли панели управления.
Наиболее интересную информацию содержат счетчики, относящиеся к конкретному процессу: количество байт виртуальной памяти, файла подкачки, ошибок страницы, рабочего множества и т.д., и общесистемные счетчики: количество байт выделенной виртуальной памяти, элементов таблицы страниц и др.
Полезными оказываются и некоторые общедоступные утилиты, например, утилита наблюдения за ошибками страниц pfmon.
Авторы книги отдельные аспекты функционирования менеджера памяти иллюстрируют с помощью отладчика.
В 32-битных системах процессор может сгенерировать 32-битный адрес. Это означает, что каждому процессу выделяется диапазон виртуальных адресов от ). Таким образом, система управления памятью позволяет пользовательской программе с помощью Win32 API записать нужный байт в любую виртуальную ячейку из этого диапазона адресов. Адреса верхней части виртуальной памяти используется для кода и данных режима ядра и других системных нужд.
По умолчанию адресное пространство каждого процесса изолировано. Данные двух разных процессов, записанные по одному и тому же виртуальному адресу, оказываются в разных страницах физической памяти
при помощи корректной работы системы трансляции адреса. В ряде случаев изоляция может быть частично снята (файлы, отображаемые в память;
Вначале все виртуальное
Совокупность регионов описывается структурой
(рис 9.4) Совокупность регионов в пользовательской части (нижние 2 Гб) виртуального адресного пространства процесса
Для создания региона явным образом обычно используется функция VirtualAlloc (вызов ряда Win32 функций, таких, как CreateFileMapping или CreateHeap, также имеет следствием создание региона). В процессе создания региона выделяют два этапа: резервирование региона и передачу ему физической памяти (commit). Оба этапа выполняются в результате вызова VirtualAlloc и могут быть объединены. В итоге каждая виртуальная страница может оказаться в одном из трех состояний: свободная (free), зарезервированная (reserve) и переданная (committed)
Резервирование региона - быстрая операция, а в последующей передаче памяти не всегда возникает необходимость; таким образом, двухэтапный процесс выделения позволяет повысить эффективность системы управления памятью.
Резервирование региона предполагает выравнивание начала региона с учетом GetSystemInfo ). В случае успешного резервирования происходит коррекция дерева
Для создаваемого региона можно указать конкретный диапазон виртуальных адресов. Если этого не делать, то система просматривает виртуальное
Чтобы использовать зарезервированный регион, ему нужно передать, то есть реально выделить, физическую память (физическую или внешнюю по усмотрению ОС). Нет необходимости передавать физическую память всему региону целиком. Более того, это рекомендуется делать поэтапно, по мере необходимости. Так обеспечивается экономия физической памяти. Например, сложные приложения, работающие с большими массивами данных, используют следующую стратегию. Вначале физическая память не передается. Как только происходит обращение к виртуальному адресу, под который не выделена память, она тут же выделяется. Как правило, подобные ситуации обрабатываются при помощи структурной обработки исключений.
В программе, листинг которой приведен ниже, осуществляется резервирование региона размером 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 правила
Передачей памяти кучам, а также учетом свободной и занятой памяти в куче занимается специальный диспетчер куч (
Стандартная куча процесса размером 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 организована поэтапная, по мере необходимости, передача физической памяти стеку при помощи механизма так называемых "сторожевых" страниц (0x80000001 ), после чего флаг PAGE сбрасывается и со страницей можно работать как с обычной страницей преданной памяти. Сторожевая страница служит ловушкой для перехвата ссылок за ее пределы.
При создании потока для его стека резервируется регион размером 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. Рассмотрите различные варианты существования файла и его размеров до и после отображения.
Решение задачи совместного доступа к памяти будет приведено ниже.
Здесь мы временно прекратим изучение виртуальной памяти процесса. Основным итогом изложенного можно считать знакомство с возможностями ОС создавать в ней разнообразные регионы. В качестве самостоятельного упражнения можно рекомендовать анализ состояния виртуального адресного пространства при помощи функций ). Теперь перейдем к рассмотрению других аспектов функционирования менеджера памяти: структуре физической памяти и особенностям трансляции адреса.
Система управления памятью является одной из наиболее важных в составе ОС. Традиционная схема предполагает связывание виртуального и физического адреса на стадии исполнения программы. Для управления виртуальным адресным пространством в нем принято организовывать сегменты (регионы), для описания которых используются структуры данных VirtualAlloc. Описана техника использования таких регионов, как куча процесса, стек потока и регион файла, отображаемого в память.
В компьютерах
Для описания системы управления памятью активно используются понятия физической и логической (виртуальной) памяти.
Физическая память является аппаратным запоминающим устройством компьютера. Менеджер памяти имеет дело с двумя уровнями физической памяти: оперативной (основной, первичной) и внешней, или вторичной. Оперативная память изготавливается с применением полупроводниковых технологий и теряет свое содержимое при отключении питания. Вторичная память (это, главным образом, диски) характеризуется гораздо более медленным доступом, однако имеет большую емкость и является энергонезависимой. Она используется в качестве расширения основной памяти. Обычно информация, хранимая в оперативной памяти, за исключением самых последних изменений, хранится также во внешней памяти. Если процессор не обнаруживает нужную информацию в оперативной памяти, он начинает искать ее во вторичной. Когда нужная информация найдена во внешней памяти, она переносится в оперативную память. Менеджер памяти старается по возможности снизить частоту обращений к вторичной памяти (свойство локальности или локализации обращений). В результате эффективное время доступа к памяти оказывается близким к времени доступа к оперативной памяти и составляет несколько десятков наносекунд.
Оперативная память представляет собой
К сожалению, многие термины, относящиеся к системе управления памятью, как и в информатике вообще, перегружены. Поэтому в дальнейшем термин "физическая память" будет относиться именно к оперативной памяти, а использование внешней памяти (дисковой, файлов выгрузки) будет оговариваться отдельно.
(рис 9.1) Расположение сегментов процессов в памяти компьютераЛогические адреса внутри сегментов могут быть сформированы на этапе компиляции. При этом символические имена связываются с перемещаемыми адресами (такими, как n байт от начала модуля). Другим примером логического адреса может быть адрес, полученный программой в результате операции выделения области памяти (allocation). Иногда говорят, что логический адрес - это адрес, который генерирует процессор. Совокупность всех логических адресов называется логическим (виртуальным) адресным пространством.
Будучи виртуальной (абстрактной) машиной, ОС должна привести в соответствие взгляд пользователя на организацию его программы с реальным хранением информации в физической памяти. Эта проблема традиционно называется проблемой связывания логического и физического адресов (см. рис 9.2). Также употребляются термины привязка адреса, трансляция адреса, разрешение адреса и т.д. В ОС Windows это делается на этапе выполнения, то есть в момент обращения к логическому адресу менеджер памяти находит его вызовы в физической памяти.
(рис 9.2) Формирование логического адреса и связывание логического адреса с физическим В современных вычислительных системах типичной является ситуация, когда объем логической памяти существенно превышает объем оперативной. В этом случае логический адрес может быть связан с адресом во внешней памяти.
Рассмотрим теперь алгоритмы и структуры данных, используемые для описания логической и физической памяти ОС Windows, а также применяемую схему связывания адресов. В каком-то смысле виртуальная память представляет собой интерфейс системы управления памятью, а ее отображение в физическую память и управление физической памятью относятся к особенностям реализации.
Размер пользовательского процесса ограничен объемом логического адресного пространства. Характерный размер логической памяти определяется разрядностью архитектуры и составляет для современных систем 232 (в недалеком будущем 264) байт. Эта величина обычно существенно превышает объем оперативной памяти, поэтому часть пользовательского процесса прозрачным образом может быть размещена во внешней памяти. Поэтому у пользователя создается иллюзия того, что он имеет дело с виртуальной памятью, отличной от реальной, размер которой потенциально больше, чем размер оперативной памяти. В дальнейшем наряду с термином "
Для определения схемы виртуальной памяти, реализованной в ОС Windows, лучше всего подходит термин "сегментно-
Сегментно-
Одна из задач, которая решается при этом, - избежать появления в системе большого количества таблиц страниц за счет организации неперекрывающихся регионов в одном виртуальном пространстве, для описания которого хватает одной таблицы страниц. Таким образом, одна таблица страниц будет отводиться для всех сегментов памяти процесса. То, как это делается можно увидеть на рис 9.3. Задействовано всего четыре аппаратных сегмента с номерами селекторов 08, 10, 1b и 23. Первый используется для адресации кода ОС и имеет атрибуты RE, второй с атрибутами RW - для данных и стека ОС, третий с атрибутами RE - для кода пользовательского процесса, а четвертый с атрибутами RW - для данных и стека пользовательского процесса. Первые два сегмента недоступны для непривилегированного режима работы процессора.
При этом все организовано так, чтобы используемые виртуальные адреса внутри сегментов не перекрывались. В результате получается плоское 32-разрядное пространство, отображаемое на физическую память при помощи одной двухуровневой таблицы страниц.
(рис 9.3) Образование неперекрывающихся регионов (программных сегментов) в линейном виртуальном адресном пространстве процессаЛюбопытно, что наличие у аппаратного сегмента атрибута не является препятствием для нецелевого использования хранимой в сегменте информации. Например, код процесса, находящийся в сегменте 1b, может быть доступен через 23-й сегмент с атрибутами RW. Собственно защита регионов организована на уровне их описателей, которые хранятся в таблице описателей
Таблица страниц ставит в соответствие
Учет совокупности теневых страниц сопряжен с трудностями, которые обусловлены разреженностью используемых виртуальных адресов. Так, например, неизменяемые страницы кода программы берутся непосредственно из выполняемых файлов (техника - отображение файла, содержащего код программы, в память). Страницы, подверженные изменениям, периодически записываются в специальные файлы выгрузки.
Таким образом, деятельность системы управления памятью сводится к созданию регионов (программных сегментов) в виртуальном адресном пространстве, выделения для них места в физической памяти (частично в оперативной памяти и частично на диске) и прозрачное перенаправление обращений к виртуальным адресам к их аналогам в физической памяти. Регионы создаются операционной системой. Иногда это происходит по инициативе пользовательской программы (например, в результате вызова функций VirtualAlloc, CreateFileMapping, CreateHeap и др.). Существенная часть деятельности менеджера памяти связана с оптимизацией. В частности, много усилий затрачивается на сокращение количества обращений к внешней памяти
Перейдем теперь к более детальному рассмотрению описанной схемы. Вначале изучим виртуальное
Из инструментальных средств Windows, описанных в лекции 2, для лучшего практического ознакомления с деятельностью по управлению памятью в работе будут активно использоваться вкладки "Процессы и "Быстродействие" диспетчера задач, а также разнообразные счетчики производительности, за поведением которых можно следить из оснастки "Производительность" ("Системный монитор") административной консоли панели управления.
Наиболее интересную информацию содержат счетчики, относящиеся к конкретному процессу: количество байт виртуальной памяти, файла подкачки, ошибок страницы, рабочего множества и т.д., и общесистемные счетчики: количество байт выделенной виртуальной памяти, элементов таблицы страниц и др.
Полезными оказываются и некоторые общедоступные утилиты, например, утилита наблюдения за ошибками страниц pfmon.
Авторы книги отдельные аспекты функционирования менеджера памяти иллюстрируют с помощью отладчика.
В 32-битных системах процессор может сгенерировать 32-битный адрес. Это означает, что каждому процессу выделяется диапазон виртуальных адресов от ). Таким образом, система управления памятью позволяет пользовательской программе с помощью Win32 API записать нужный байт в любую виртуальную ячейку из этого диапазона адресов. Адреса верхней части виртуальной памяти используется для кода и данных режима ядра и других системных нужд.
По умолчанию адресное пространство каждого процесса изолировано. Данные двух разных процессов, записанные по одному и тому же виртуальному адресу, оказываются в разных страницах физической памяти
при помощи корректной работы системы трансляции адреса. В ряде случаев изоляция может быть частично снята (файлы, отображаемые в память;
Вначале все виртуальное
Совокупность регионов описывается структурой
(рис 9.4) Совокупность регионов в пользовательской части (нижние 2 Гб) виртуального адресного пространства процесса
Для создания региона явным образом обычно используется функция VirtualAlloc (вызов ряда Win32 функций, таких, как CreateFileMapping или CreateHeap, также имеет следствием создание региона). В процессе создания региона выделяют два этапа: резервирование региона и передачу ему физической памяти (commit). Оба этапа выполняются в результате вызова VirtualAlloc и могут быть объединены. В итоге каждая виртуальная страница может оказаться в одном из трех состояний: свободная (free), зарезервированная (reserve) и переданная (committed)
Резервирование региона - быстрая операция, а в последующей передаче памяти не всегда возникает необходимость; таким образом, двухэтапный процесс выделения позволяет повысить эффективность системы управления памятью.
Резервирование региона предполагает выравнивание начала региона с учетом GetSystemInfo ). В случае успешного резервирования происходит коррекция дерева
Для создаваемого региона можно указать конкретный диапазон виртуальных адресов. Если этого не делать, то система просматривает виртуальное
Чтобы использовать зарезервированный регион, ему нужно передать, то есть реально выделить, физическую память (физическую или внешнюю по усмотрению ОС). Нет необходимости передавать физическую память всему региону целиком. Более того, это рекомендуется делать поэтапно, по мере необходимости. Так обеспечивается экономия физической памяти. Например, сложные приложения, работающие с большими массивами данных, используют следующую стратегию. Вначале физическая память не передается. Как только происходит обращение к виртуальному адресу, под который не выделена память, она тут же выделяется. Как правило, подобные ситуации обрабатываются при помощи структурной обработки исключений.
В программе, листинг которой приведен ниже, осуществляется резервирование региона размером 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 правила
Передачей памяти кучам, а также учетом свободной и занятой памяти в куче занимается специальный диспетчер куч (
Стандартная куча процесса размером 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 организована поэтапная, по мере необходимости, передача физической памяти стеку при помощи механизма так называемых "сторожевых" страниц (0x80000001 ), после чего флаг PAGE сбрасывается и со страницей можно работать как с обычной страницей преданной памяти. Сторожевая страница служит ловушкой для перехвата ссылок за ее пределы.
При создании потока для его стека резервируется регион размером 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. Рассмотрите различные варианты существования файла и его размеров до и после отображения.
Решение задачи совместного доступа к памяти будет приведено ниже.
Здесь мы временно прекратим изучение виртуальной памяти процесса. Основным итогом изложенного можно считать знакомство с возможностями ОС создавать в ней разнообразные регионы. В качестве самостоятельного упражнения можно рекомендовать анализ состояния виртуального адресного пространства при помощи функций ). Теперь перейдем к рассмотрению других аспектов функционирования менеджера памяти: структуре физической памяти и особенностям трансляции адреса.
Система управления памятью является одной из наиболее важных в составе ОС. Традиционная схема предполагает связывание виртуального и физического адреса на стадии исполнения программы. Для управления виртуальным адресным пространством в нем принято организовывать сегменты (регионы), для описания которых используются структуры данных VirtualAlloc. Описана техника использования таких регионов, как куча процесса, стек потока и регион файла, отображаемого в память.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.