Изменения

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

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

14 979 байт добавлено, 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|right|thumb|frame200px|<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 байт (1МБ). Конечно, где она находитсяпамяти в машине могло быть меньше. В этом случае запись в некоторые ячейки памяти игнорировалась. Важно помнить{{TODO| t=Уточнить, что у каждого процесса свое адресное пространствопроисходит при чтении из несуществующей физической памяти (вероятно, читалась минус единица).}} Исполняемая программа могла читать или писать в любое место памяти. Из-за этого программа с ошибкой или вредоносная программа могли привести к некорректной работе всей системы. Недостаток такого подхода к работе с памятью попытались исправить с помощью введения защищенного режима (''protected mode'') для i80286.<ref>http://ru.wikipedia.org/wiki/Защищённый_режим</ref> Защищенный режим позволял изолировать процессы друг от друга, чтобы один не мог испортить данные другого. Механизм изоляции процессов появившийся в 286, которое был не очень удобным и в настоящий момент практически не пересекается с адресными пространствами других программиспользуется. В настоящий момент времени для изоляции процессов используется механизм страничной адресации (''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>).}}
==Старт процесса==
[[Файл:add_space.png|right|thumb|200px|<center>Адресное пространство</center>]]
==Определение==[[Файл:Hranenie dannyh.png|right|thumb|350px|<center>Хранение в физической памяти <br/>(серым выделена заполненная память)</center>]]При запуске программы операционная система создает процесс, которому выделяется адресное пространство размером <tex>4GB</tex> в 32-битных системах, и <tex>2^{{TODO|t=наверно лучше вставить картиночку чтото типа этой без хипа}64}B</tex> в 64-битных, представляющее из себя массив байт. В этом массиве можно писать и читать данные из любого его места. Ясно, что адресное пространство не может полностью содержаться в физической памяти, поэтому представим, что эта память просто дана каждому процессу, неважно, где она находится. Важно помнить, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программ. Адресное пространство существует, пока живёт процесс.
==Старт процесса==
При старте процесса, в адресное пространство помещается код исполняемой программы, используемые в программе библиотеки и глобальные данные.
[[Файл:vmmap.png|right|thumb|200px|Окно программы VMMap]]
Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в физической памяти много меньше, чем предоставленные ему <tex>4GB</tex> (или <tex>2^{64}B</tex> для 64-битных систем), то есть, оно не хранит пустые страницы. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессов, и не бояться того, что объем физической памяти сильно меньше <tex>2^{64}B</tex>.
 
 
== Ссылки ==
 
<references/>
1632
правки

Навигация