Изменения

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

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

15 442 байта добавлено, 19:41, 4 сентября 2022
м
rollbackEdits.php mass rollback
[[Категория:С++ 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|frameright|thumb|200px|<center>Адресное пространствоСтраницы адресного пространства</center>]] |[[Файл:Hranenie dannyhIMAG0048.pngjpg|frameright|thumb|200px|<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>VirtualAlloc</tex> и <tex>VirtualFree</tex>. Вызывая <tex>4GBVirtualAlloc</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=Переписать раздел про кучу подробнее и понятнее. ЯсноИдея в том, чтобы сначала показать, что адресное пространство не может полностью содержаться в физической можно делать для эффективного распределения памяти, поэтому представима потом обрадовать людей тем, что эта память просто дана каждому процессукуча уже реализована.}} ===Аллокаторы=== {{TODO| t=Написать про аллокаторы}}  == История == Чтобы понимать, неважнопочему работа с памятью на современных компьютерах устроена так, где как она находится. Важно помнитьустроена, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программнеобходимо знать как она эволюционировала по мере увеличения производительности железа и по мере появления в железе новых фич.
==Старт процесса==При старте процессаИзначально, на самых первых компьютерах память для процессора представляла из себя просто массив байт. Например, в адресное пространство помещается код исполняемой программы и код используемых в программе библиотек для процессора i8086 размер этого массива был 2^20 байт (например, Kernel.dll1МБ). Так жеКонечно, памяти в адресном пространстве хранится <tex>heap</tex>, машине могло быть меньше. В этом случае запись в который будут записываться глобальные переменныенекоторые ячейки памяти игнорировалась.{{TODO|t=расписать подробнее - надо ли?Уточнить, что происходит при чтении из несуществующей физической памяти (вероятно, читалась минус единица).}}Исполняемая программа могла читать или писать в любое место памяти. Из-за этого программа с ошибкой или вредоносная программа могли привести к некорректной работе всей системы.
==Хранение адресного пространства=====Хранение в физической памяти===Адресное пространство разбито на Недостаток такого подхода к работе с памятью попытались исправить с помощью введения защищенного режима (''страницыprotected mode'' объемом по ) для i80286.<texref>4kB<http://tex>ru. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти)wikipedia. Физическая память формируется из оперативной памяти (<tex>RAMorg/wiki/Защищённый_режим</texref>)Защищенный режим позволял изолировать процессы друг от друга, места на жестком диске, отведенного под <tex>swap</tex>чтобы один не мог испортить данные другого. Механизм изоляции процессов появившийся в 286, был не очень удобным и места на жестком диске, где хранится непосредственно код программыв настоящий момент практически не используется.
===Подгрузка страниц в оперативную память===При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается тудаВ настоящий момент времени для изоляции процессов используется механизм страничной адресации (''paging''<ref>http://en. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттудаwikipedia. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда; иначе - записывается в <tex>swaporg/wiki/Virtual_memory</texref>), введенный в i80386 процессоре.
{{TODO| t==Визуализация адресного пространства с помощью VMMap==Сейчас может сложиться впечатление, будто мы противопоставляем защищенный режим и страничную адресацию. Реально под термином ''защищенный режим'' понимаются две вещи: сегментная адресация (''segmentation'') и страничная адресация. 286 имел только сегментную адресацию. Сегментная адресация была неудобна и в настоящий момент практически не используется (кроме, например, Thread Information Block в винде<ref>http://en.wikipedia.org/wiki/Win32_Thread_Information_Block</ref>).}}
{{TODO|t=Можно не расписывать здесь что есть стэк, что куча, а просто сказать что посмотрим на выделение памяти, и как это выглядит в адресном пространстве}}
{{TODO|t=Если что перенесем в другое место}}
Программа VMMap предназначена для визуализации адресного пространства[[Файл:add_space. Если ее запустить на каком либо процессе, перед вами будет примерно следующая картина:png|right|thumb|200px|<center>Адресное пространство</center>]]
==Определение==[[Файл:vmmapHranenie dannyh.png|centerright|500pxthumb|thumb350px|Окно программы VMMap<center>Хранение в физической памяти <br/>(серым выделена заполненная память)</center>]]При запуске программы операционная система создает процесс, которому выделяется адресное пространство размером <tex>4GB</tex> в 32-битных системах, и <tex>2^{64}B</tex> в 64-битных, представляющее из себя массив байт. В этом массиве можно писать и читать данные из любого его места. Ясно, что адресное пространство не может полностью содержаться в физической памяти, поэтому представим, что эта память просто дана каждому процессу, неважно, где она находится. Важно помнить, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программ. Адресное пространство существует, пока живёт процесс.
Первое==Старт процесса==При старте процесса, что бросается в глаза - строки помеченные разными цветами. Различные цвета - различные области в адресном пространстве. Также можно увидеть сами адреса памяти(на рисунке выделено адресное пространство помещается код исполняемой программы, используемые в красную рамку)программе библиотеки и глобальные данные.
Давайте посмотрим ==Хранение адресного пространства=====Хранение в физической памяти===Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что происходит при выделении эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы и ее освобождении в адресном пространствеподгружаемых библиотек. Для этого запустим следующую программу:
int main() { int *a = new int [1000000]; delete [] a; return 0; }Заметим, что суммарный объем физической памяти, выделенной адресным пространствам разных процессов, не может превышать полный объем физической памяти, потому что, иначе, некоторая область физической памяти будет принадлежать одновременно двум адресным пространствам, что не допускается.
С помощью оператора new можно выделять память ===Обращение к адресному пространству===При обращении к той или иной странице, если она не находится в куче(что такое куча оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет рассказано выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в следующих статьях). Пока что оперативной памяти, она просто посмотрим как эта память выделяетсяудаляется оттуда; иначе {{---}} записывается в <tex>swap</tex>.
До Обратим внимание, что код программы и используемых библиотек не меняется по ходу выполнения int *a = new int [1000000];программы, поэтому их можно не подгружать в оперативную память, а читать с места на жестком диске, где они расположены.
[[Файл:vmmap_before===Page fault===Из-за того, что адресное пространство программы не полностью хранится в физической памяти, возникает возможность возникновения ошибки обращения к памяти. Если под страницу, к которой пытается обратиться поток, не была выделена физическая память, возникает ошибка page fault.png|center|500px|thumb]]
После выполнения==Визуализация с помощью VMMap== int *a = new int [1000000[Файл:vmmap.png|right|thumb|200px|Окно программы VMMap]];Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в физической памяти много меньше, чем предоставленные ему <tex>4GB</tex> (или <tex>2^{64}B</tex> для 64-битных систем), то есть, оно не хранит пустые страницы. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессов, и не бояться того, что объем физической памяти сильно меньше <tex>2^{64}B</tex>.
[[Файл:vmmap_after.png|center|500px|thumb]]
Видим выделение памяти, и ее обьем.== Ссылки ==
{{TODO|t=Здесь можно написать кстати про обьем страниц, как я подозреваю выделилось 3908 Кб, тк для хранения 10^6 интов нужно 3906,25 Кб}}<references/>
1632
правки

Навигация