Управление памятью (также динамическое управление памятью , динамическое распределение хранилища или динамическое распределение памяти ) — это форма управления ресурсами , применяемая к памяти компьютера . Основным требованием управления памятью является предоставление способов динамического распределения частей памяти программам по их запросу и освобождение ее для повторного использования, когда она больше не нужна. Это имеет решающее значение для любой передовой компьютерной системы, где в любой момент времени может выполняться более одного процесса . [1]
Было разработано несколько методов, которые повышают эффективность управления памятью. Системы виртуальной памяти разделяют адреса памяти , используемые процессом, от фактических физических адресов, что позволяет разделять процессы и увеличивать размер виртуального адресного пространства сверх доступного объема оперативной памяти с помощью подкачки или обмена на вторичное хранилище . Качество менеджера виртуальной памяти может оказывать значительное влияние на общую производительность системы . Система позволяет компьютеру выглядеть так, как будто у него может быть больше доступной памяти, чем физически есть, тем самым позволяя нескольким процессам совместно использовать ее.
В некоторых операционных системах , например, Burroughs/Unisys MCP , [2] а также OS/360 и их последователях , [3] память управляется операционной системой. [примечание 1] В других операционных системах, например, Unix-подобных операционных системах, память управляется на уровне приложений.
Управление памятью в адресном пространстве обычно подразделяется на ручное управление памятью и автоматическое управление памятью.
Задача выполнения запроса на выделение памяти состоит в поиске блока неиспользуемой памяти достаточного размера. Запросы памяти удовлетворяются путем выделения частей из большого пула [примечание 2] памяти, называемой кучей [примечание 3] или свободным хранилищем . В любой момент времени некоторые части кучи используются, а некоторые являются «свободными» (неиспользуемыми) и, таким образом, доступны для будущих выделений. В языке C вызывается функция, которая выделяет память из кучи malloc
, и вызывается функция, которая берет ранее выделенную память и отмечает ее как «свободную» (для использования при будущих выделениях) free
. [примечание 4]
Несколько проблем усложняют реализацию, например внешняя фрагментация , которая возникает, когда между выделенными блоками памяти имеется много небольших промежутков, что делает их использование для запроса на выделение недействительным. Метаданные распределителя также могут увеличивать размер (по отдельности) небольших выделений. Это часто управляется с помощью фрагментации . Система управления памятью должна отслеживать невыполненные выделения, чтобы гарантировать, что они не перекрываются и что никакая память никогда не «теряется» (т. е. что нет « утечек памяти »).
Конкретный реализованный алгоритм динамического распределения памяти может существенно повлиять на производительность. Исследование, проведенное в 1994 году компанией Digital Equipment Corporation, иллюстрирует накладные расходы , связанные с различными распределителями. Самая низкая средняя длина пути инструкции , необходимая для выделения одного слота памяти, составила 52 (согласно измерениям с помощью профилировщика уровня инструкций на различных программах). [1]
Поскольку точное местоположение выделения заранее неизвестно, доступ к памяти осуществляется косвенно, обычно через указательную ссылку . Конкретный алгоритм, используемый для организации области памяти и выделения и освобождения фрагментов, связан с ядром и может использовать любой из следующих методов:
Выделение блоков фиксированного размера, также называемое выделением пула памяти, использует свободный список блоков памяти фиксированного размера (часто все одинакового размера). Это хорошо работает для простых встраиваемых систем , где не требуется выделять большие объекты, но страдает от фрагментации , особенно с длинными адресами памяти. Однако из-за значительного снижения накладных расходов этот метод может существенно повысить производительность для объектов, которым требуется частое выделение и освобождение, и поэтому он часто используется в видеоиграх .
В этой системе память выделяется в несколько пулов памяти вместо одного, где каждый пул представляет собой блоки памяти определенной степени двойки по размеру или блоки некоторой другой удобной прогрессии размера. Все блоки определенного размера хранятся в отсортированном связанном списке или дереве , и все новые блоки, которые формируются во время выделения, добавляются в соответствующие им пулы памяти для последующего использования. Если запрашивается меньший размер, чем доступен, выбирается наименьший доступный размер и разделяется. Выбирается одна из полученных частей, и процесс повторяется до тех пор, пока запрос не будет выполнен. Когда блок выделяется, распределитель начнет с наименьшего достаточно большого блока, чтобы избежать ненужного разрыва блоков. Когда блок освобождается, он сравнивается со своим приятелем. Если они оба свободны, они объединяются и помещаются в соответствующий список приятельских блоков большего размера.
Этот механизм распределения памяти предварительно выделяет фрагменты памяти, подходящие для размещения объектов определенного типа или размера. [5] Эти фрагменты называются кэшами, и распределителю нужно только отслеживать список свободных слотов кэша. Создание объекта будет использовать любой из свободных слотов кэша, а уничтожение объекта добавит слот обратно в список свободных слотов кэша. Этот метод уменьшает фрагментацию памяти и эффективен, поскольку нет необходимости искать подходящий фрагмент памяти, поскольку подойдет любой открытый слот.
Многие Unix-подобные системы, а также Microsoft Windows реализуют функцию, вызываемую alloca
для динамического выделения стековой памяти способом, аналогичным основанному на куче malloc
. Компилятор обычно транслирует ее во встроенные инструкции, манипулирующие указателем стека. [6] Хотя нет необходимости вручную освобождать память, выделенную таким образом, поскольку она автоматически освобождается при alloca
возврате из вызванной функции, существует риск переполнения. И поскольку alloca является специальным расширением, которое можно увидеть во многих системах, но никогда в POSIX или стандарте C, его поведение в случае переполнения стека не определено.
Более безопасная версия alloca, называемая _malloca
, которая сообщает об ошибках, существует в Microsoft Windows. Она требует использования _freea
. [7] gnulib предоставляет эквивалентный интерфейс, хотя вместо того, чтобы выдавать исключение SEH при переполнении, он делегирует malloc при обнаружении слишком большого размера. [8] Похожую функцию можно эмулировать с помощью ручного учета и проверки размера, например, при использовании alloca_account
в glibc. [9]
Правильное управление памятью в приложении является сложной проблемой, и было разработано несколько различных стратегий для управления памятью.
Во многих реализациях языка программирования среда выполнения программы автоматически выделяет память в стеке вызовов для нестатических локальных переменных подпрограммы , называемых автоматическими переменными , когда подпрограмма вызывается, и автоматически освобождает эту память при выходе из подпрограммы. Специальные объявления могут позволять локальным переменным сохранять значения между вызовами процедуры или могут разрешать доступ к локальным переменным другим подпрограммам. Автоматическое выделение локальных переменных делает возможной рекурсию на глубину, ограниченную доступной памятью.
Сборка мусора — это стратегия автоматического определения памяти, выделенной для объектов, которые больше не могут использоваться в программе, и возврата этой выделенной памяти в пул свободных ячеек памяти. Этот метод отличается от «ручного» управления памятью, когда программист явно кодирует запросы памяти и освобождения памяти в программе. Хотя автоматическая сборка мусора имеет преимущества в виде снижения нагрузки на программиста и предотвращения определенных видов ошибок выделения памяти, сборка мусора требует собственных ресурсов памяти и может конкурировать с прикладной программой за процессорное время.
Подсчет ссылок — это стратегия обнаружения того, что память больше не может использоваться программой, путем ведения счетчика для того, сколько независимых указателей указывают на память. Всякий раз, когда новый указатель указывает на часть памяти, программист должен увеличивать счетчик. Когда указатель меняет свое местонахождение или когда указатель больше не указывает на какую-либо область или сам был освобожден, счетчик должен уменьшаться. Когда счетчик падает до нуля, память следует считать неиспользуемой и освобожденной. Некоторые системы подсчета ссылок требуют участия программиста, а некоторые автоматически реализуются компилятором. Недостатком подсчета ссылок является то, что могут возникнуть циклические ссылки , которые приводят к утечке памяти. Это можно смягчить, добавив концепцию «слабой ссылки» (ссылки, которая не участвует в подсчете ссылок, но уведомляется, когда область, на которую она указывает, больше недействительна) или объединив подсчет ссылок и сборку мусора.
Пул памяти — это метод автоматического освобождения памяти на основе состояния приложения, например жизненного цикла запроса или транзакции. Идея заключается в том, что многие приложения выполняют большие фрагменты кода, которые могут генерировать выделения памяти, но есть точка в выполнении, когда все эти фрагменты, как известно, больше недействительны. Например, в веб-службе после каждого запроса веб-службе больше не нужна память, выделенная во время выполнения запроса. Поэтому вместо того, чтобы отслеживать, ссылаются ли на память в данный момент, память выделяется в соответствии с запросом или стадией жизненного цикла, с которой она связана. Когда этот запрос или стадия пройдены, вся связанная память одновременно освобождается.
Виртуальная память — это метод отделения организации памяти от физического оборудования. Приложения работают с памятью через виртуальные адреса . Каждая попытка приложения получить доступ к определенному адресу виртуальной памяти приводит к тому, что адрес виртуальной памяти транслируется в фактический физический адрес . [10] Таким образом, добавление виртуальной памяти обеспечивает детальный контроль над системами памяти и методами доступа.
В системах виртуальной памяти операционная система ограничивает доступ процесса к памяти. Эта функция, называемая защитой памяти , может использоваться для запрета процессу читать или писать в память, которая ему не выделена, предотвращая вмешательство вредоносного или неисправного кода в одной программе в работу другой.
Хотя память, выделенная для определенных процессов, обычно изолирована, иногда процессам необходимо иметь возможность делиться информацией. Общая память — один из самых быстрых методов межпроцессного взаимодействия .
Память обычно классифицируется по скорости доступа на первичную и вторичную . Системы управления памятью, среди прочих операций, также обрабатывают перемещение информации между этими двумя уровнями памяти.
Операционная система управляет различными ресурсами в вычислительной системе. Подсистема памяти является элементом системы для управления памятью. Подсистема памяти объединяет аппаратный ресурс памяти и программное обеспечение MCP OS, которое управляет ресурсом.
Подсистема памяти управляет физической памятью и виртуальной памятью системы (обе части аппаратного ресурса). Виртуальная память расширяет физическую память, используя дополнительное пространство на периферийном устройстве, обычно диске. Подсистема памяти отвечает за перемещение кода и данных между основной и виртуальной памятью в процессе, известном как наложение. Burroughs был первой коммерческой реализацией виртуальной памяти (хотя разработанной в Манчестерском университете для компьютера Ferranti Atlas) и интегрировал виртуальную память с системным дизайном B5000 с самого начала (в 1961 году), не нуждаясь во внешнем блоке управления памятью (MMU). [11] : 48
Подсистема памяти отвечает за отображение логических запросов на блоки памяти в физические части памяти (сегменты), которые находятся в списке свободных сегментов. Каждый выделенный блок управляется с помощью дескриптора сегмента [12] , специального управляющего слова, содержащего соответствующие метаданные о сегменте, включая адрес, длину, тип машины и p-бит или бит «присутствия», который указывает, находится ли блок в основной памяти или его необходимо загрузить с адреса, указанного в дескрипторе.
Дескрипторы необходимы для обеспечения безопасности и защиты памяти, чтобы операции не могли переполнить или опустошить указанный блок (обычно это называется переполнением буфера). Сами дескрипторы являются защищенными управляющими словами, которыми нельзя манипулировать, за исключением определенных элементов ОС MCP (включенных директивой блока UNSAFE в NEWP ).
Дональд Кнут описывает похожую систему в разделе 2.5 «Динамическое распределение памяти» книги «Фундаментальные алгоритмы» .
IBM System/360 не поддерживает виртуальную память. [примечание 5] Изоляция памяти заданий опционально выполняется с помощью ключей защиты , назначая хранилище для каждого задания разный ключ, 0 для супервизора или 1–15. Управление памятью в OS/360 является функцией супервизора . Хранилище запрашивается с помощью GETMAIN
макроса и освобождается с помощью FREEMAIN
макроса, что приводит к вызову супервизора ( SVC ) для выполнения операции.
В OS/360 детали различаются в зависимости от того, как создана система , например, для PCP , MFT , MVT .
В OS/360 MVT подвыделение в области задания или общей области системной очереди (SQA) основано на подпулах , областях размером кратным 2 КБ — размеру области, защищенной ключом защиты. Подпулы нумеруются от 0 до 255. [13] Внутри области подпулам назначается либо защита хранилища задания, либо ключ супервизора, ключ 0. Подпулы 0–127 получают ключ задания. Первоначально создается только подпул нулевой, и все запросы пользователей на хранилище удовлетворяются из подпула 0, если в запросе памяти не указан другой. Подпулы 250–255 создаются запросами памяти супервизором от имени задания. Большинству из них назначается ключ 0, хотя некоторые получают ключ задания. Номера подпулов также актуальны в MFT, хотя детали намного проще. [14] MFT использует фиксированные разделы, переопределяемые оператором, вместо динамических регионов, а PCP имеет только один раздел.
Каждый подпул отображается списком блоков управления, идентифицирующих выделенные и свободные блоки памяти в подпуле. Память выделяется путем поиска свободной области достаточного размера или путем выделения дополнительных блоков в подпуле, вплоть до размера региона задания. Можно освободить всю или часть выделенной области памяти. [15]
Подробности для OS/VS1 аналогичны [16] подробностям для MFT и MVT; подробности для OS/VS2 аналогичны подробностям для MVT, за исключением того, что размер страницы составляет 4 КиБ. Для OS/VS1 и OS/VS2 общая системная область очереди (SQA) невыгружаемая.
В MVS адресное пространство [17] включает дополнительную выгружаемую общую область, Common Storage Area (CSA), и две дополнительные частные области, невыгружаемая область локальной системной очереди (LSQA) и выгружаемая системная рабочая область (SWA). Кроме того, ключи хранения 0–7 зарезервированы для использования привилегированным кодом.