Изменения

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

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

15 075 байт добавлено, 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=расписать подробнее - надо ли?}}
{{TODO| t==Хранение адресного пространства=====Хранение в физической памяти===Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы.Написать про аллокаторы}}
===Подгрузка страниц в оперативную память===
При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда; иначе - записывается в <tex>swap</tex>.
==Визуализация адресного пространства с помощью VMMapИстория ==
{{TODO|t=Можно не расписывать здесь что есть стэкЧтобы понимать, что кучапочему работа с памятью на современных компьютерах устроена так, а просто сказать что посмотрим на выделение памятикак она устроена, необходимо знать как она эволюционировала по мере увеличения производительности железа и как это выглядит в адресном пространстве}}{{TODO|t=Если что перенесем по мере появления в другое место}}железе новых фич.
Программа VMMap предназначена Изначально, на самых первых компьютерах память для визуализации адресного пространствапроцессора представляла из себя просто массив байт. Если ее запустить на каком либо процессеНапример, для процессора i8086 размер этого массива был 2^20 байт (1МБ). Конечно, памяти в машине могло быть меньше. В этом случае запись в некоторые ячейки памяти игнорировалась. {{TODO| t=Уточнить, что происходит при чтении из несуществующей физической памяти (вероятно, перед вами будет примерно следующая картина:читалась минус единица).}} Исполняемая программа могла читать или писать в любое место памяти. Из-за этого программа с ошибкой или вредоносная программа могли привести к некорректной работе всей системы.
[[ФайлНедостаток такого подхода к работе с памятью попытались исправить с помощью введения защищенного режима (''protected mode'') для i80286.<ref>http:vmmap//ru.wikipedia.org/wiki/Защищённый_режим</ref> Защищенный режим позволял изолировать процессы друг от друга, чтобы один не мог испортить данные другого. Механизм изоляции процессов появившийся в 286, был не очень удобным и в настоящий момент практически не используется.png|center|500px|thumb|Окно программы VMMap]]
Первое, что бросается в глаза - строки помеченные разными цветамиВ настоящий момент времени для изоляции процессов используется механизм страничной адресации (''paging''<ref>http://en. Различные цвета - различные области в адресном пространствеwikipedia. Также можно увидеть сами адреса памяти(на рисунке выделено org/wiki/Virtual_memory</ref>), введенный в красную рамку)i80386 процессоре.
Давайте посмотрим что происходит при выделении памяти {{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];
delete [] a;
return 0;
}
С помощью оператора new можно выделять память в куче(что такое куча будет рассказано в следующих статьях). Пока что просто посмотрим как эта память выделяется[[Файл:add_space.png|right|thumb|200px|<center>Адресное пространство</center>]]
До выполнения==Определение== int *a = new int [1000000[Файл:Hranenie dannyh.png|right|thumb|350px|<center>Хранение в физической памяти <br/>(серым выделена заполненная память)</center>]];При запуске программы операционная система создает процесс, которому выделяется адресное пространство размером <tex>4GB</tex> в 32-битных системах, и <tex>2^{64}B</tex> в 64-битных, представляющее из себя массив байт. В этом массиве можно писать и читать данные из любого его места. Ясно, что адресное пространство не может полностью содержаться в физической памяти, поэтому представим, что эта память просто дана каждому процессу, неважно, где она находится. Важно помнить, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программ. Адресное пространство существует, пока живёт процесс.
[[Файл:vmmap_before==Старт процесса==При старте процесса, в адресное пространство помещается код исполняемой программы, используемые в программе библиотеки и глобальные данные. ==Хранение адресного пространства=====Хранение в физической памяти===Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы и подгружаемых библиотек.png|center|500px|thumb]]
После выполнения int *a = new int [1000000];Видим выделение Заметим, что суммарный объем физической памяти, выделенной адресным пространствам разных процессов, не может превышать полный объем физической памяти, потому что, иначе, некоторая область физической памятибудет принадлежать одновременно двум адресным пространствам, и ее обьемчто не допускается.
[[Файл:vmmap_after===Обращение к адресному пространству===При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда; иначе {{---}} записывается в <tex>swap</tex>.png|center|500px|thumb]]
{{TODO|t=Здесь Обратим внимание, что код программы и используемых библиотек не меняется по ходу выполнения программы, поэтому их можно написать про обьем страниц не подгружать в 4Кб, как я подозреваю выделилось 3908 Кбоперативную память, тк для хранения 10^6 интов нужно 3906а читать с места на жестком диске,25 Кб}}где они расположены.
После выполнения===Page fault=== delete [] a;Видим освобождение Из-за того, что адресное пространство программы не полностью хранится в физической памяти, возникает возможность возникновения ошибки обращения к памяти. Если под страницу, к которой пытается обратиться поток, не была выделена физическая память, возникает ошибка page fault.
==Визуализация с помощью VMMap==[[Файл:vmmap_deletevmmap.png|centerright|500pxthumb|200px|thumbОкно программы VMMap]]Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в физической памяти много меньше, чем предоставленные ему <tex>4GB</tex> (или <tex>2^{64}B</tex> для 64-битных систем), то есть, оно не хранит пустые страницы. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессов, и не бояться того, что объем физической памяти сильно меньше <tex>2^{64}B</tex>.
Также можно видеть сам процесс.
[[Файл:Process.png|center|500px|thumb]]== Ссылки ==
{{TODO|t=Показать в адресном пространстве ран-тайм, библиотеки ядра}}<references/>
69
правок

Навигация