Исследование модели памяти Linux

Понимание моделей памяти, используемых в Linux, — это первый шаг к
освоению на более высоком уровне структуры операционной системы Linux и
ее реализации. В этой статье модели памяти Linux и управление памятью
рассматриваются на ознакомительном уровне.

Операционная система Linux использует монолитный подход, при котором
определяется набор примитивов или системных вызовов для реализации
служб операционной системы, таких как управление процессами,
параллельная работа и управление памятью, в нескольких модулях,
работающих в режиме супервизора. И хотя Linux в целях совместимости
поддерживает модель модуля управления сегментами (segment control unit)
как символическое представление, она использует эту модель на
минимальном уровне.

Основными задачами управления памятью являются:

  • Управление виртуальной памятью — логический уровень между запросами памяти в приложениях и физической памятью.
  • Управление физической памятью.
  • Управление виртуальной памятью на уровне ядра (модуль ядра,
    занимающийся распределением памяти — компонент, который пытается
    удовлетворить запросы к памяти). Запрос может быть выполнен из ядра или
    из программы пользователя.
  • Управление виртуальным адресным пространством.
  • Свопинг и кэширование.

Данная статья призвана помочь вам в освоении внутреннего устройства
Linux с точки зрения управления памятью операционной системы.
Рассматриваются следующие темы:

  • Общая модель модуля управления сегментами и ее особенности в Linux.
  • Общая модель управления страницами памяти и ее особенности в Linux.
  • Физические детали области памяти.

В данной статье не рассматриваются детально вопросы управления
памятью в ядре Linux, но информация по общей модели памяти и по работе
с ней должна дать вам основу для дальнейшего изучения. Внимание в
статье уделяется архитектуре x86, но вы можете использовать эти
материалы и с другими аппаратными реализациями.

Расширенное разбиение на страницы«). Тогда размер каждой равен 4 MB. Адрес данных, указываемый глобальным массивом, сохраняется в регистре cr3,
что, я полагаю, является первым этапом установки модуля разбиения на
страницы для процессов Linux. Остальные страничные записи
устанавливаются во второй фазе.

Вторая фаза реализована в методе paging_init().

Отображение RAM выполняется между PAGE_OFFSET и
адресом, представляющим границу 4 GB (0xFFFFFFFF) в 32-битной
x86-архитектуре. Это означает, что RAM размером примерно в 1 GB может
быть отображена при загрузке Linux, и это происходит по умолчанию.
Однако, если установить HIGHMEM_CONFIG, то физическая
память размером более 1 GB может быть отображена для ядра — имейте в
виду, что это временная мера. Это делается вызовом kmap().

Зона физической памяти

Я уже показал вам, что ядро Linux (на 32-битной архитектуре) делит
виртуальную память в отношении 3:1 — 3 GB виртуальной памяти для
пространства пользователя и 1 GB для пространства ядра. Код ядра и его
структуры данных должны размещаться в этом 1 GB адресного пространства,
но даже еще большим потребителем этого адресного пространства является
виртуальное отображение физической памяти.

Это происходит потому, что ядро не может манипулировать памятью,
если она не отображена в его адресное пространство. Таким образом,
максимальным количеством физической памяти, которое может обработать
ядро, является объем памяти, который мог бы отобразиться в виртуальное
адресное пространство ядра минус объем, необходимый для отображения
самого кода ядра. В итоге Linux на основе x86-системы смогла бы
работать с объемом менее 1 GB физической памяти. И это максимум.

Поэтому, для обслуживания большого количества пользователей, для
поддержки большего объема памяти, для улучшения производительности и
для установки архитектурно-независимого способа описания памяти модель
памяти Linux должна была совершенствоваться. Для достижения этих целей
в более новой модели каждому CPU был назначен свой банк памяти. Каждый
банк называется узлом; каждый узел разделяется на зоны. Зоны (представляющие диапазоны памяти) далее разбивались на следующие типы:

  • ZONE_DMA (0-16 MB): Диапазон памяти, расположенный в нижней области памяти, чего требуют некоторые устройства ISA/PCI.
  • ZONE_NORMAL (16-896 MB): Диапазон памяти, который
    непосредственно отображается ядром в верхние участки физической памяти.
    Все операции ядра могут выполняться только с использованием этой зоны
    памяти; таким образом, это самая критичная для производительности зона.
  • ZONE_HIGHMEM (896 MB и выше): Оставшаяся доступная память системы не отображается ядром.

Концепция узла реализована в ядре при помощи структуры struct pglist_data. Зона описывается структурой struct zone_struct. Физический страничный фрейм представлен структурой struct Page, и все эти структуры хранятся в глобальном массиве структур struct mem_map, который размещается в начале NORMAL_ZONE. Эти основные взаимоотношения между узлом, зоной и страничным фреймом показаны на рисунке 9.

Рисунок 9. Взаимоотношения между узлом, зоной и страничным фреймом
Рисунок 9. Взаимоотношения между узлом, зоной и страничным фреймом

Зона верхней области памяти появилась в системе управления памятью
ядра тогда, когда были реализованы расширение виртуальной памяти
Pentium II (для доступа к 64 GB памяти средствами PAE — Physical
Address Extension — на 32-битных системах) и поддержка 4 GB физической
памяти (опять же, на 32-битных системах). Эта концепция применима к
платформам x86 и SPARC. Обычно эти 4 GB памяти делают доступными
отображение ZONE_HIGHMEM в ZONE_NORMAL посредством kmap().
Обратите внимание, пожалуйста, на то, что не желательно иметь более 16
GB RAM на 32-битной архитектуре даже при разрешенном PAE.

(PAE — разработанное Intel расширение адресов памяти, разрешающее
процессорам увеличить количество битов, которые могут быть использованы
для адресации физической памяти, с 32 до 36 через поддержку в
операционной системе приложений, использующих Address Windowing
Extensions API.)

Управление этой зоной физической памяти выполняется распределителем
(allocator) зоны. Он отвечает за разделение памяти на несколько зон и
рассматривает каждую зону как единицу для распределения. Любой
конкретный запрос на распределение использует список зон, в которых
распределение может быть предпринято, в порядке от наиболее
предпочтительной к наименее предпочтительной.

Например:

  • Запрос на пользовательскую страницу должен быть сначала заполнен из «нормальной» зоны (ZONE_NORMAL);
  • если завершение неудачно — с ZONE_HIGHMEM;
  • если опять неудачно — с ZONE_DMA.

Список зон для такого распределения состоит из зон ZONE_NORMAL, ZONE_HIGHMEM и ZONE_DMA
в указанном порядке. С другой стороны, запрос DMA-страницы может быть
выполнен из зоны DMA, поэтому список зон для таких запросов содержит
только зону DMA.

Заключение

Управление памятью — это большой, комплексный и трудоемкий набор
задач, один из тех, в которых разобраться непросто, потому что создание
модели поведения системы в реальных условиях в многозадачной среде
является трудной работой. Такие компоненты как планирование, разбиение
на страницы и взаимодействие процессов предъявляют серьезные
требования. Я надеюсь, что эта статья поможет вам разобраться в
основных понятиях, необходимых для овладения задачами управления
памятью в Linux, и обеспечит вас начальной информацией для дальнейших
исследований.