Занятие 17. Реализация динамического управления памятью в Linux и Windows
Для динамического распределения крупных блоков физической памяти (по размеру кратных фрейма) в Linux используют систему двойников. Использование таких блоков позволяет снизить необходимость модификации таблиц страниц во время работы приложения, а это, в свою очередь, снижает вероятность очистки кэша трансляции. Алгоритм двойников не подходит для распределения блоков памяти произвольного размера, поскольку минимальный объем памяти, который он может выделить или освободить, составляет один фрейм (4 Кбайт), что вызывает значительную внутреннюю фрагментацию.
Для распределения памяти под отдельные объекты применяют кусковый распределитель (slab allocator). При его разработке пытаются организовать кэширование часто используемых объектов ядра в инициализированном состоянии (поскольку большая часть времени при размещении объекта тратится на его инициализацию, а не на само выделение памяти), а также выделение памяти блоками малого размера без внутренней фрагментации, свойственной алгоритмам системы двойников.
Структура данных кускового распределителя состоит из переменного количества кэшей объектов, объединенных в двусвязный циклический список, называемый цепочкой кэшей. Каждый такой кэш отвечает за распределение объектов конкретного типа или конкретного размера.
Различают два вида кэшей объектов:
- Специализированные кэши, которые создают различные компоненты ядра системы для хранения объектов конкретного типа. Для таких кэшей обычно задают конструктор и деструктор, а также уникальное имя, которое зависит от типа объекта. Под конструктором понимают функцию, которую вызывают во время инициализации объектов этого типа, под деструктором — функцию, которую вызывают при высвобождении памяти из под объекта.
- Кеши общего назначения, используемые для хранения блоков памяти произвольного назначения конкретного размера. Есть кэши для блоков размером от 25 = 32 бит до 217 — 13 1072 бит, их называют size-N (N — размер блока кэша в байтах, например, size-128).
Память для кэша хранят в виде кусковых блоков (slabs). Кусковой блок состоит из одного или нескольких непрерывно расположенных фреймов памяти, разделенных на фрагменты памяти (chunks) одного размера, которые содержат объекты. Размер фрагмента памяти зависит от типа кэша, для которого выделяют кусковый блок (он равен размеру объекта, экземпляры которого нужно распределять с помощью этого кэша). Использование таких блоков для распределения памяти снижает как внешнюю, так и внутреннюю фрагментацию.
Системные пулы памяти ядра
Различают невыгружаемые (nonpaged) и выгружаемые (paged) системные пулы памяти. Оба вида пулов находятся в системной области памяти и отражаются в адресное пространство любого процесса.
- Невыгружаемый содержит диапазоны адресов, которые всегда соответствуют физической памяти, поэтому доступ к ним никогда не вызывает страничного прерывания.
- Выгружаемый соответствует памяти, страницы которой могут быть выгружены па диск.
Оба вида системных пулов можно использовать для выделения блоков памяти произвольного заранее неизвестного размера.
Списки предыстории являются быстрым способом распределения памяти и во многом похожи на кэши кускового распределителя Linux.
Они позволяют выделять память блоков одного заведомого размера. Обычно, их специально создают для выделения часто используемым объектам (например, такие списки формируют различные компоненты исполнительной системы для своих структур данных). Как и для кэшей кускового распределителя, есть списки общего назначения для выделения блоков заданного размера. В случае высвобождения объектов они возвращаются обратно в соответствующий список. Если список долго не использовали, его автоматически сокращают.
Динамические участки памяти Windows
В Windows, как и в Linux, каждый процесс имеет доступ к специальной области памяти. Ее также называют динамическим участком памяти или кучей (heap). Особенностью Windows является то, что таких динамических участков для процесса может быть создано несколько, и внутри каждой из них, распределитель памяти может отдельно выделять блоки меньшего размера.
Распределитель памяти в Windows называют, менеджером динамических участков памяти (heap manager). Этот менеджер позволяет процессам распределять память блоками произвольного размера, а не только кратными размеру страницы.
Каждый процесс запускают с динамическим участком по умолчанию, размер которого составляет 1 Мбайт (при компоновке программы этот размер может быть изменен).
Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:
Занятие 15. Реализация управления основной памятью в Linux и Windows
Проблему сегментации в Linux решают достаточно просто — ядро практически не использует средств поддержки сегментации архитектуры IA-32. В системе поддерживают минимальное количество сегментов, без которых невозможна корректная адресация памяти процессором (сегменты кода и данных ядра и пользовательского режима). Код ядра и пользовательского режима совместно использует эти сегменты.
Сегменты кода используют при формировании логических адресов кода (для вызова процедур и т.п.); такие сегменты обозначают как доступные для чтения и выполнения. Сегменты данных предназначены для формирования логических адресов данных (глобальных переменных, стека и т.д.) и обозначаются как доступные для чтения и записи. Сегменты режима пользователя доступны из режима пользователя, сегменты ядра — только из режима ядра.
Все сегменты, которые используются в Linux, определяют границу смещения, что позволяет создать в рамках каждого из них 4 Гбайт логических адресов. Это означает, что Linux фактически передает всю работу «по управлению памятью на уровень преобразования между линейными и физическими адресами (поскольку каждый логический адрес соответствует линейному).
Страничная адресация в Linux
В ядре Linux версии 2.4 используют трехуровневую организацию таблиц страниц. Поддерживаются три типа таблиц страниц: глобальный (Page Global Directoiy, PGD); промежуточный каталог страниц (Page Middle Directoiy, PMD); таблица страниц (Page Table).
Каждый глобальный каталог содержит адреса одного или нескольких промежуточных каталогов страниц, а те, в свою очередь, — адреса таблиц страниц. Элементы таблицы страниц (РТЕ) указывают на фреймы физической памяти.
Каждый процесс имеет свой глобальный каталог страниц и набор таблиц страниц. При переключении контекста Linux сохраняет значение регистра в управляющем блоке процесса, передает управление и загружает в этот регистр значение из управляющего блока процесса, начинает выполняться. Итак, когда процесс начинает выполняться, устройство страничной поддержки уже ссылается на корректный набор таблиц страниц.
Особенности адресации процессов и ядра
Линейное адресное пространство каждого процесса разделяют на две части: первые 3 Гбайт адресов используются в режиме ядра и пользователя, они отражают защищенное адресное пространство процесса; остальные 1 Гбайт адресов используют только в режиме ядра.
Элементы глобального каталога процесса, определяющие адреса до 3 Гбайт, могут быть заданы самим процессом, другие элементы должны быть одинаковыми для всех процессов и задаваться ядром.
Потоки ядра не используют элементов глобального каталога первого диапазона. На практике, когда происходит передача управления потока ядра, не меняется значение регистра, то есть поток ядра использует таблицы страниц процесса пользователя, который выполнялся последним (поскольку ему нужны только элементы, доступные в режиме ядра, а вши во всех процессах пользователя одинаковы).
Адресное пространство ядра начинается с четвертого гигабайта линейной памяти. Для прямого отображения на физические адреса доступны первые 896 Мбайт этого пространства (128 Мб, оставшиеся используется преимущественно для динамического распределения памяти ядром).
Система Windows использует общие сегменты памяти подобно тому, как это делается в Linux. Для всех сегментов в программе задают одинаковые значения базы и границы, поэтому работу по управлению памятью передают на уровень линейных адресов (которые со смещением в этих общих сегментах).
Страничная адресация в Windows
При работе с линейными адресами в Windows используют двухуровневые таблицы страниц, полностью соответствующие архитектуре IA-32. У каждого процесса есть свой каталог страниц, каждый элемент которого указывает на таблицу страниц. Таблицы страниц всех уровней содержат по 1024 элемента таблиц страниц, каждый такой элемент указывает на фрейм физической памяти.
Размер линейного адреса, с которым работает система, составляет 32 бита. Из них 10 бит соответствуют адресу в каталоге страниц, еще 10 — это индекс элемента в таблице, последние 12 бит адресуют конкретный байт страницы (и являются смещением).
Размер элемента таблицы страниц тоже составляет 32 бита. Первые 20 бит адресуют конкретный фрейм (и используются вместе с последними 12 битами линейного адреса), а остальные 12 бит описывают атрибуты страницы (защита, состояние страницы в памяти, файл подкачки использует). Если страница не состоит в памяти, то в первые 20 бит сохраняют смещение в файле подкачки.
Особенности адресации процессов и ядра в Windows
Линейное адресное пространство процесса делится на две части: первые 2 Гбайт адресов доступны для процесса в режиме пользователя и являются его защищенным адресным пространством; остальные 2 Гбайт адресов доступны только в режиме ядра и отражают системное адресное пространство.
Структура адресного пространства процессов и ядра
В адресном пространстве процесса можно выделить следующие участки:
- первые 64 Кбайт (начиная с нулевого адреса) — это специальный участок, доступ к которому всегда вызывает ошибки;
- всю память между первыми 64 Кбайт и последние 136 Кбайт (почти 2 Гбайт) может использовать процесс во время своего выполнения;
- дальше расположены два блока по 4 Кбайт: блоки окружения потока (TEB) и процесса (PEВ);
- следующие 4 Кбайт — участок памяти, куда отображаются различные системные данные (системное время, значение счетчика системных часов, номер версии системы), поэтому для доступа к ним, процессу не нужно переключаться в режим ядра;
- последние 64 Кбайт используют для предотвращения попыток доступа за пределы адресного пространства процесса (попытка доступа к этой памяти даст ошибку).
Системное адресное пространство содержит большое количество различных участков:
- Первые 512 Мбайт системного адресного пространства используют для загрузки ядра системы.
- 4 Мбайт памяти выделяют под каталог страниц и таблицы страниц процесса.
- Специальный участок памяти размером 4 Мбайт, которую называют гиперпространственной, используют для отображения различных структур данных, специфичных для процесса.
- 512 Мбайт выделяют под системный кэш.
- В системное адресное пространство отражаются специальные участки памяти — выгружаемый пул и невыгружаемый пул.
- Примерно 4 Мбайт в самом конце системного адресного пространства выделяют под структуры данных, необходимые для создания аварийного образа памяти, а также для структур данных.