Изменения

Перейти к: навигация, поиск

Работа с памятью

15 298 байт добавлено, 22:33, 23 июля 2011
Куча
[[Категория:С++ 2 семестр]]
{{В разработке}}
==Физическая память==
===Общие понятия===
Физическая (оперативная) память может быть представлена как массив байт. Процессор имеет возможность обращаться к данным из этого массива по индексу ячейки памяти (физическому адресу). В старых процессорах (например, i8086) каждый процесс использовал команды процессора для физической адресации к оперативной памяти, что, конечно, приводило к многочисленным ошибкам при неправильной (или злонамеренной) работе с памятью. Попытки разделить доступ разным процессам к физической памяти привели к появлению в i80286 процессорах защищенного режима (''protected mode'')<ref>http://ru.wikipedia.org/wiki/Защищённый_режим</ref>. В i80386 процессоре защищенный режим был расширен механизмом страничной адресации, которая по сей день является основным механизмом изоляции памяти процессов.
==Определение=={|align="right" |Работал этот механизм приблизительно так: каждый процесс мог обращаться к любой ячейке памяти из диапазона [0, 2^32 -valign="top" |1] (такой диапазон называется виртуальным адресным пространством). Адресное пространство (виртуальное и физическое) условно делилось на блоки (страницы) по 4Кб, таким образом адрес ячейки (32-class="standard" |битное число) естественным образом можно было интерпретировать как пару (индекс страницы (20 бит), смещение в странице (12 бит)). [[Файл:Adresnoe prostranstvoIMAG0047.pngjpg|right|thumb|frame200px|<center>Адресное пространствоСтраницы адресного пространства</center>]] |[[Файл:Hranenie dannyhIMAG0048.pngjpg|right|thumb|frame200px|<center>Хранение Адрес разбивается на индекс страницы и смещение</center>]] Получив запрос на обращение к ячейке памяти (p, o), процессор обращался к уникальной для каждого процесса таблице (в первом приближении ее можно считать массивом из 2^20 32-битных чисел) по индексу страницы p. В этой таблице для всех страниц виртуального адресного пространства процесса прописывались индексы страниц физической памяти (20-битное число) и некоторая служебная информация (12-битное число: флаг доступности страницы в физической памяти (present flag), флаг возможности записи (write flag), флаг изменения страницы (dirty flag) и т.д.). В случае, если страница обнаруживалась в физической памяти, процессор вычислял физический адрес искомой ячейки, взяв ее смещение относительно начала страницы. В случае же если искомой странице не соответствовала страница в физической памяти, процессор бросал исключение page fault, которое перехватывала операционная система.  Конечно, 4Мб на каждый процесс -- непозволительная трата ресурсов, поэтому вместо массива использовалось двухуровневое дерево. Массив из 2^20 элементов условно делился на 2^10 блока по 2^10 записей. Если блок полностью состоял из отсутствующих в физической памяти страниц, страницы, содержащей его, не было. Список из 2^10 блоков содержался в специальной странице. [[Файл:IMAG0049.jpg|right|thumb|200px|<br/center>(серым выделена заполненная память)Таблица представляет собой двухуровневое дерево</center>]] |}При запуске программы операционная Примерно так работает страничная адресация и в современных процессорах. Как же заполняется таблица виртуального адресного пространства? Операционная система позволяет с помощью своего API резервировать и освобождать страницы в виртуальном адресном пространстве и сопоставлять этим страницам физическую память. Конечно, если все процессы начнут забирать физическую память, рано или поздно система создает не сможет найти свободную страницу в физической памяти. В этом случае она начнет использовать файл подкачки (или раздел жесткого диска, как в linux). В первом приближении этот механизм работает так: ОС выбирает страницу, которую давно не использовали и, если она была модифицирована (установлен флаг dirty) или если ее образа нет в файле подкачки, сохраняет страницу в файле подкачки. Далее ОС модифицирует записи в таблицах виртуальных адресных пространств процессов, использовавших эту страницу, сбрасывая флаг present. На место этой страницы помещается страница из файла подкачки (если процессхотел обратиться к странице, которому выделяется которую уже когда-то использовал) или она просто заполняется нулями. Страницы могут подгружаться не только из файла подкачки -- в адресное пространство размером загружается код процесса и код всех его зависимостей. В адресное пространство может быть спроецирован файл с жесткого диска. В этих случаях страницы могут загружаться из соотвествующих файлов.  ===Куча=== Рассмотрим теперь пример использования памяти: попробуем представить себе как можно реализовать динамически расширяющийся массив (вектор). Нас будет интересовать операция расширения массива: предположим, массив уже занимает какую-то непрерывную область виртуального адресного пространства (под его данные было выделено несколько страниц виртуального адресного пространства). Мы хотим расширить этот массив, увеличив его размер вдвое, скопировать в новый массив старые данные. После чего старый массив нам становится не нужен. Тут возникают два вопроса: какие страницы резервировать и что делать со старыми страницами. Заметим, что резервирование новых страниц и сопоставление им физической памяти -- дорогое удовольствие. Понятно, что у нас может возникнуть необходимость выделить еще какие-то данные и старые выделенные страницы памяти могут нам пригодиться -- используя их мы сможем записать новые данные, не выделяя новые страницы. Также заметим, что нам нужно бережнее относиться к остаткам страницы (когда мы записываем данные, занимающие страницу не полностью) -- нужно помнить про свободные куски используемых страниц.  Память можно выделять и освобождать напрямую через системные функции <tex>4GBVirtualAlloc</tex> и <tex>VirtualFree</tex>. Вызывая <tex>VirtualAlloc</tex>, указывая размер блока памяти и желаемый атрибут доступа (обычно: чтение-запись). Система выделяет от свободной памяти блок. Теперь в 32-битных системахпрограмме выделена память, и есть указатель на нее. Когда память надо освободить - вызывайте <tex>2^{64}BVirtualFree</tex> . Система переведёт память обратно в 64-битныхсвободную. Но как говорилось ранее с памятью нужно работать эффективно, поэтому существует куча, которая манипулирует страницами для эффективной работы с памятью. Хорошая новость заключается в том, что работа с кучей реализована на уровне ОС и вам можно не реализовывать ее самостоятельно.  В стандартной библиотеке, представляющее пришедшей из себя массив байтязыка C, <tex>libc</tex> реализованы функции <tex>malloc()</tex> и <tex>free()</tex>, соответственно для выделения и освобождения памяти. В этом массиве можно писать самом C++ есть аналогичные функции <tex>new (new[])</tex> и читать данные <tex>delete(delete[])</tex>. Для каждого <tex>malloc/new/new[]</tex> должны вызываться <tex>free/delete/delete[]</tex>, т.к. память сама не освобождается при выходе из любого его местафункций. ЯсноНе вызвав эти функции, что адресное пространство не может полностью содержаться куча останется неосвобожденнной, и произойдут утечки памяти.  {{TODO| t=Переписать раздел про кучу подробнее и понятнее. Идея в физической памятитом, поэтому представимчтобы сначала показать, что эта память просто дана каждому процессу, неважноможно делать для эффективного распределения памяти, где она находится. Важно помнитьа потом обрадовать людей тем, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программкуча уже реализована. }} ===Аллокаторы===
==Старт процесса==При старте процесса, в адресное пространство помещается код исполняемой программы и код используемых в программе библиотек (например, Kernel.dll). Так же, в адресном пространстве хранится <tex>heap</tex>, в который будут записываться глобальные переменные.{{TODO|t=расписать подробнее - надо ли?Написать про аллокаторы}}
==Хранение адресного пространства==
===Хранение в физической памяти===
Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы.
==История =Подгрузка страниц в оперативную память===При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда; иначе - записывается в <tex>swap</tex>.
==Визуализация адресного пространства Чтобы понимать, почему работа с помощью VMMap==памятью на современных компьютерах устроена так, как она устроена, необходимо знать как она эволюционировала по мере увеличения производительности железа и по мере появления в железе новых фич.
Изначально, на самых первых компьютерах память для процессора представляла из себя просто массив байт. Например, для процессора i8086 размер этого массива был 2^20 байт (1МБ). Конечно, памяти в машине могло быть меньше. В этом случае запись в некоторые ячейки памяти игнорировалась. {{TODO|t=Можно не расписывать здесь что есть стэкУточнить, что куча, а просто сказать что посмотрим на выделение происходит при чтении из несуществующей физической памяти(вероятно, и как это выглядит в адресном пространствечиталась минус единица).}}{{TODO|t=Если что перенесем Исполняемая программа могла читать или писать в другое любое место}}памяти. Из-за этого программа с ошибкой или вредоносная программа могли привести к некорректной работе всей системы.
Программа VMMap предназначена Недостаток такого подхода к работе с памятью попытались исправить с помощью введения защищенного режима (''protected mode'') для визуализации адресного пространстваi80286. Если ее запустить на каком либо процессе<ref>http://ru.wikipedia.org/wiki/Защищённый_режим</ref> Защищенный режим позволял изолировать процессы друг от друга, перед вами будет примерно следующая картина:чтобы один не мог испортить данные другого. Механизм изоляции процессов появившийся в 286, был не очень удобным и в настоящий момент практически не используется.
[[ФайлВ настоящий момент времени для изоляции процессов используется механизм страничной адресации (''paging''<ref>http:vmmap//en.wikipedia.org/wiki/Virtual_memory</ref>), введенный в i80386 процессоре.png|center|500px|thumb|Окно программы VMMap]]
Первое{{TODO| t=Сейчас может сложиться впечатление, что бросается в глаза - строки помеченные разными цветамибудто мы противопоставляем защищенный режим и страничную адресацию. Реально под термином ''защищенный режим'' понимаются две вещи: сегментная адресация (''segmentation'') и страничная адресация. 286 имел только сегментную адресацию. Различные цвета - различные области Сегментная адресация была неудобна и в адресном пространстве. Также можно увидеть сами адреса памятинастоящий момент практически не используется (на рисунке выделено кроме, например, Thread Information Block в красную рамкувинде<ref>http://en.wikipedia.org/wiki/Win32_Thread_Information_Block</ref>).}}
Давайте посмотрим что происходит при выделении памяти и ее освобождении в адресном пространстве. Для этого запустим следующую программу:
int main() { int *a = new int [1000000[Файл:add_space.png|right|thumb|200px|<center>Адресное пространство</center>]; delete [] a; return 0; }
С помощью оператора new можно выделять ==Определение==[[Файл:Hranenie dannyh.png|right|thumb|350px|<center>Хранение в физической памяти <br/>(серым выделена заполненная память )</center>]]При запуске программы операционная система создает процесс, которому выделяется адресное пространство размером <tex>4GB</tex> в куче(32-битных системах, и <tex>2^{64}B</tex> в 64-битных, представляющее из себя массив байт. В этом массиве можно писать и читать данные из любого его места. Ясно, что такое куча будет рассказано адресное пространство не может полностью содержаться в следующих статьях). Пока физической памяти, поэтому представим, что просто посмотрим как эта память выделяетсяпросто дана каждому процессу, неважно, где она находится. Важно помнить, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программ. Адресное пространство существует, пока живёт процесс.
До выполнения==Старт процесса== int *a При старте процесса, в адресное пространство помещается код исполняемой программы, используемые в программе библиотеки и глобальные данные. ==Хранение адресного пространства= new int [1000000];====Хранение в физической памяти===Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы и подгружаемых библиотек.
[[Файл:vmmap_beforeЗаметим, что суммарный объем физической памяти, выделенной адресным пространствам разных процессов, не может превышать полный объем физической памяти, потому что, иначе, некоторая область физической памяти будет принадлежать одновременно двум адресным пространствам, что не допускается.png|center|500px|thumb]]
После выполнения===Обращение к адресному пространству=== int *a = new int [1000000]При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда;иначе {{---}} записывается в <tex>swap</tex>.
[[Файл:vmmap_afterОбратим внимание, что код программы и используемых библиотек не меняется по ходу выполнения программы, поэтому их можно не подгружать в оперативную память, а читать с места на жестком диске, где они расположены.png|center|500px|thumb]]
Видим выделение ===Page fault===Из-за того, что адресное пространство программы не полностью хранится в физической памяти, и ее обьемвозникает возможность возникновения ошибки обращения к памяти. Если под страницу, к которой пытается обратиться поток, не была выделена физическая память, возникает ошибка page fault.
{{TODO==Визуализация с помощью VMMap==[[Файл:vmmap.png|right|thumb|200px|t=Здесь Окно программы VMMap]]Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно написать про обьем страниц видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в 4Кбфизической памяти много меньше, чем предоставленные ему <tex>4GB</tex> (или <tex>2^{64}B</tex> для 64-битных систем), то есть, как я подозреваю выделилось 3908 Кбоно не хранит пустые страницы. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессов, тк для хранения 10и не бояться того, что объем физической памяти сильно меньше <tex>2^6 интов нужно 3906,25 Кб}{64}B</tex>.
После выполнения
delete [] a;
[[Файл:vmmap_delete.png|center|500px|thumb]]== Ссылки ==
Видим освобождение памяти.<references/>
69
правок

Навигация