Изменения

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

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

13 571 байт добавлено, 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|right|thumb|frame200px|<center>Хранение в физической памяти <br/>(серым выделена заполненная память)Адрес разбивается на индекс страницы и смещение</center>]] |}При запуске программы операционная система создает процессПолучив запрос на обращение к ячейке памяти (p, o), которому выделяется адресное пространство размером <tex>4GB</tex> процессор обращался к уникальной для каждого процесса таблице (в первом приближении ее можно считать массивом из 2^20 32-битных системах, чисел) по индексу страницы p. В этой таблице для всех страниц виртуального адресного пространства процесса прописывались индексы страниц физической памяти (20-битное число) и <tex>2^{64}B</tex> некоторая служебная информация (12-битное число: флаг доступности страницы в 64-битныхфизической памяти (present flag), флаг возможности записи (write flag), представляющее из себя массив байтфлаг изменения страницы (dirty flag) и т.д.). В этом массиве можно писать и читать данные из любого его места. Яснослучае, что адресное пространство не может полностью содержаться если страница обнаруживалась в физической памяти, поэтому представим, что эта память просто дана каждому процессу, неважнопроцессор вычислял физический адрес искомой ячейки, где она находитсявзяв ее смещение относительно начала страницы. Важно помнитьВ случае же если искомой странице не соответствовала страница в физической памяти, что у каждого процесса свое адресное пространствопроцессор бросал исключение page fault, которое не пересекается с адресными пространствами других программперехватывала операционная система.
==Старт процесса==При старте процессаКонечно, в адресное пространство помещается код исполняемой программы и код используемых в программе библиотек (например4Мб на каждый процесс -- непозволительная трата ресурсов, Kernelпоэтому вместо массива использовалось двухуровневое дерево.dll)Массив из 2^20 элементов условно делился на 2^10 блока по 2^10 записей. Так жеЕсли блок полностью состоял из отсутствующих в физической памяти страниц, страницы, содержащей его, не было. Список из 2^10 блоков содержался в адресном пространстве хранится специальной странице. [[Файл:IMAG0049.jpg|right|thumb|200px|<texcenter>heapТаблица представляет собой двухуровневое дерево</texcenter>, в который будут записываться глобальные переменные.]]
==Хранение Примерно так работает страничная адресация и в современных процессорах. Как же заполняется таблица виртуального адресного пространства=====Хранение ? Операционная система позволяет с помощью своего API резервировать и освобождать страницы в виртуальном адресном пространстве и сопоставлять этим страницам физическую память. Конечно, если все процессы начнут забирать физическую память, рано или поздно система не сможет найти свободную страницу в физической памяти===Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы В этом случае она начнет использовать файл подкачки (или раздел жесткого диска, как в хранится linux). В первом приближении этот механизм работает так: ОС выбирает страницу, которую давно не использовали и, если она была модифицирована (установлен флаг dirty) или если ее адрес образа нет в файле подкачки, сохраняет страницу в файле подкачки. Далее ОС модифицирует записи в физической памятитаблицах виртуальных адресных пространств процессов, либо указаниеиспользовавших эту страницу, что эта сбрасывая флаг present. На место этой страницы помещается страница пуста из файла подкачки (в таком случае хранить саму страницу не надо если процесс хотел обратиться к странице, которую уже когда- поэтому все адресные пространства помещаются в физической памятито использовал)или она просто заполняется нулями. Физическая память формируется Страницы могут подгружаться не только из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, файла подкачки -- в адресное пространство загружается код процесса и места на жестком диске, где хранится непосредственно код программывсех его зависимостей. В адресное пространство может быть спроецирован файл с жесткого диска. В этих случаях страницы могут загружаться из соотвествующих файлов.
Заметим, что суммарный объем физической памяти, выделенной адресным пространствам разных процессов, не может превышать полный объем физической памяти, потому что, в таком случае, некоторая область физической памяти будет принадлежать одновременно двум адресным пространствам, что не допускается.===Куча===
===Обращение к адресному пространству===При обращении к той или иной странице, если она не находится в оперативной Рассмотрим теперь пример использования памяти: попробуем представить себе как можно реализовать динамически расширяющийся массив (вектор). Нас будет интересовать операция расширения массива: предположим, она загружается тудамассив уже занимает какую-то непрерывную область виртуального адресного пространства (под его данные было выделено несколько страниц виртуального адресного пространства). Для этогоМы хотим расширить этот массив, увеличив его размер вдвое, либо скопировать в оперативке находится свободное местоновый массив старые данные. После чего старый массив нам становится не нужен. Тут возникают два вопроса: какие страницы резервировать и что делать со старыми страницами. Заметим, либо выбирается страница, которая будет выгружена оттудачто резервирование новых страниц и сопоставление им физической памяти -- дорогое удовольствие. Если эта страница уже существует на жестком дискеПонятно, что у нас может возникнуть необходимость выделить еще какие-то данные и старые выделенные страницы памяти могут нам пригодиться -- используя их мы сможем записать новые данные, не была изменена за время ее пребывания в оперативной памятивыделяя новые страницы. Также заметим, что нам нужно бережнее относиться к остаткам страницы (когда мы записываем данные, она просто удаляется оттуда; иначе занимающие страницу не полностью) -- записывается в <tex>swap</tex>нужно помнить про свободные куски используемых страниц.
Обратим вниманиеПамять можно выделять и освобождать напрямую через системные функции <tex>VirtualAlloc</tex> и <tex>VirtualFree</tex>. Вызывая <tex>VirtualAlloc</tex>, указывая размер блока памяти и желаемый атрибут доступа (обычно: чтение-запись). Система выделяет от свободной памяти блок. Теперь в программе выделена память, что код программы и используемых библиотек не меняется по ходу выполнения программыесть указатель на нее. Когда память надо освободить - вызывайте <tex>VirtualFree</tex>. Система переведёт память обратно в свободную. Но как говорилось ранее с памятью нужно работать эффективно, поэтому их можно не подгружать существует куча, которая манипулирует страницами для эффективной работы с памятью. Хорошая новость заключается в оперативную памятьтом, а читать что работа с места кучей реализована на жестком диске, где они расположеныуровне ОС и вам можно не реализовывать ее самостоятельно.
===Page fault===Из-за тогоВ стандартной библиотеке, что адресное пространство программы не полностью хранится в физической памятипришедшей из языка C, <tex>libc</tex> реализованы функции <tex>malloc()</tex> и <tex>free()</tex>, возникает возможность возникновения ошибки обращения к соответственно для выделения и освобождения памяти. Если под страницу, к которой пытается обратиться поток, не была выделена физическая память, поток убивается. Если был убит главный поток, выполнение программы завершаетсяВ самом C++ есть аналогичные функции <tex>new (new[])</tex> и <tex>delete(delete[])</tex>.
==Визуализация адресного пространства с помощью VMMap==[[Файл:vmmap.png|right|thumb|200px|Окно программы VMMap]]Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в физической памяти много меньше, чем предоставленные ему каждого <tex>4GBmalloc/new/new[]</tex> (или должны вызываться <tex>2^{64}Bfree/delete/delete[]</tex> для 64-битных систем), то есть, оно т.к. память сама не хранит пустые страницыосвобождается при выходе из функций. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессовНе вызвав эти функции, куча останется неосвобожденнной, и не бояться того, что объем физической произойдут утечки памяти сильно меньше <tex>2^{64}B</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^{64}B</tex> в 64-битных, представляющее из себя массив байт. В этом массиве можно писать и читать данные из любого его места. Ясно, что адресное пространство не может полностью содержаться в физической памяти, поэтому представим, что эта память просто дана каждому процессу, неважно, где она находится. Важно помнить, что у каждого процесса свое адресное пространство, которое не пересекается с адресными пространствами других программ. Адресное пространство существует, пока живёт процесс.
==Старт процесса==
При старте процесса, в адресное пространство помещается код исполняемой программы, используемые в программе библиотеки и глобальные данные.
==Хранение адресного пространства==
===Хранение в физической памяти===
Адресное пространство разбито на ''страницы'' объемом по <tex>4kB</tex>. Для каждой страницы в хранится ее адрес в физической памяти, либо указание, что эта страница пуста (в таком случае хранить саму страницу не надо - поэтому все адресные пространства помещаются в физической памяти). Физическая память формируется из оперативной памяти (<tex>RAM</tex>), места на жестком диске, отведенного под <tex>swap</tex>, и места на жестком диске, где хранится непосредственно код программы и подгружаемых библиотек.
Заметим, что суммарный объем физической памяти, выделенной адресным пространствам разных процессов, не может превышать полный объем физической памяти, потому что, иначе, некоторая область физической памяти будет принадлежать одновременно двум адресным пространствам, что не допускается.
===Обращение к адресному пространству===
При обращении к той или иной странице, если она не находится в оперативной памяти, она загружается туда. Для этого, либо в оперативке находится свободное место, либо выбирается страница, которая будет выгружена оттуда. Если эта страница уже существует на жестком диске, и не была изменена за время ее пребывания в оперативной памяти, она просто удаляется оттуда; иначе {{---}} записывается в <tex>swap</tex>.
Обратим внимание, что код программы и используемых библиотек не меняется по ходу выполнения программы, поэтому их можно не подгружать в оперативную память, а читать с места на жестком диске, где они расположены.
===Page fault===
Из-за того, что адресное пространство программы не полностью хранится в физической памяти, возникает возможность возникновения ошибки обращения к памяти. Если под страницу, к которой пытается обратиться поток, не была выделена физическая память, возникает ошибка page fault.
==Визуализация с помощью VMMap==
[[Файл:vmmap.png|right|thumb|200px|Окно программы VMMap]]
Для визуализации адресного пространства можно использовать программу VMMap. В главном окне на диаграмме можно видеть распределение физической памяти адресного пространство между самой программой, подключенными библиотеками, переменными и др. Как видно из скриншота, адресное пространство занимает в физической памяти много меньше, чем предоставленные ему <tex>4GB</tex> (или <tex>2^{64}B</tex> для 64-битных систем), то есть, оно не хранит пустые страницы. Именно поэтому даже на 64-битных системах возможно одновременно запускать несколько процессов, и не бояться того, что объем физической памяти сильно меньше <tex>2^{64}B</tex>.
== Ссылки ==
===Архив===Рассмотрим процесс выделения памяти в куче на примере простой программы: [[Файл:vmmap_before.png|right|thumb|200px|Память не выделена]][[Файл:vmmap_after.png||right|thumb|200px|Выделена память под массив]][[Файл:vmmap_delete.png||right|thumb|200px|Память освобождена]]  int main() { int *a = new int [1000000]; delete [] a; return 0; } До выполнения int *a = new int [1000000];куча в адресном пространстве программы занимает <tex>1152kB</tex>, а после выполнения команды - <tex>5060kB</tex>. Значит, память была выделена именно в куче, и объем выделенной памяти равен <tex>3908kB</tex>, что почти в точности соответствует объему массива <tex>int</tex> из <tex>1000000</tex> элементов при выделении <tex>4B</tex> на элемент.  После выполнения delete [] a;размер кучи снова равен <tex>1152kB<references/tex>, как и до выделение памяти под массив.  {{TODO|t=Показать в адресном пространстве ран-тайм, библиотеки ядра}}
1632
правки

Навигация