пятница, 16 августа 2013 г.

PhantomEx: Страничная организация памяти - теория

После продолжительной паузы настал момент вернуться к написанию цикла по разработке учебной операционной системы. При разработке основного (не учебного) варианта PhantomEx возник ряд трудностей, которые требовали вдумчивого разбора.
На сегодня, после реализации в ядре PhantomEx работы с потоками накопилась уйма практического материала для продолжения, и мы будем продолжать, рассматривая тему страничной организации памяти.
До сих пор наше учебное ядро использовало сегментную модель организации памяти в защищенном режиме, однако такая схема к настоящему времени безнадежно устарела - все современные ОС на 32-разрядной архитектуре x86 используют страничную модель памяти, не говоря уже о том что в архитектуре x86-64 от сегментной модели осталось несколько рудиментарных вещей, для обратной совместимости со старым ПО, а полной поддержки сегментной модели и аппаратной многозадачности там уже нет.

Тема организации страничной памяти достаточно обширна, так что она растянется на несколько статей. Начнем с теории.


1. Страничная организация памяти в процессорах Intel


Рассмотрим схему, иллюстрирующую страничную адресацию в процессорах Intel x86.


Физическая память разбивается на участки, называемые страницами. Наиболее характерный размер страниц - 4 Кб. При использовании режима PAE, доступного в процессорах Intel начиная с Pentium Pro размер страниц может устанавливаться в 2 Мб, однако режим PAE мы здесь рассматривать не будем.

Каждая страница размещается в конкретном месте физической памяти, и адрес её начала в памяти называется адресом страницы, или адресом физического фрейма. Если мы обращаемся к памяти внутри страницы, то физический адрес будет равен адресу страницы + смещение относительно начала страницы.

С другой стороны, любая программа обращается к памяти используя так называемый виртуальный адрес. Виртуальное адресное пространство каждого процесса (ВАП) в 32-разрядной системе имеет диапазон адресов от 0x00000000 - 0xFFFFFFFF, то есть непрерывная область размером в 4 Гб. Это очень удобно для разработки программ так как компилятор, собирая программу не озабочен структурой физической памяти конкретного компьютера.

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

Алгоритм трансляции виртуального адреса в физически можно  изобразить в виде схемы


Первые 12 бит виртуального адреса задают смещение внутри страницы. Их как раз хватает чтобы адресовать 212 = 4096 байт внутри страницы. Биты с 12 по 21 кодируют индекс страницы внутри таблицы, таким образом в одной таблице возможно размещение 210 = 1024 дескрипторов страниц. И наконец биты 22 - 31 задают индекс таблицы в каталоге, то есть в каталоге возможно разместить 210 = 1024 дескрипторов таблиц. Располагая таким объемом информационных структур можно разметить по страницам 1024·1024·4096 = 4 Гб всей доступной физической памяти в 32-разрядной системе.

Биты 12 - 31 дескриптора страницы/таблицы задают адрес размещения страницы/таблицы в физической памяти, то есть очевидно что данный адрес имеет формат 0xYYYYY000. Этот адрес разыскивается процессором в каталоге страниц по индексу таблицы и страницы и складывается с 12-битным смещением внутри страницы, давая физический адрес размещения данных/кода в памяти.

И вот тут проявляется самая "вкусная" идея системы страничной адресации - физический адрес обладает известным произволом, определяемым адресом размещения страницы в ОЗУ. То есть одному и тому же виртуальному адресу может соответствовать несколько физических адресов, отличающихся старшими 20 битами. Таким образом возможна физическая изоляция друг от друга ВАП разных процессов - располагая код и данные формально по одному и тому же виртуальному адресу, на деле получаем размещение данных разных процессов в разных областях физической памяти.

Процедура трансляции виртуального адреса в физический достаточно трудоемка для процессора, поэтому в нем предусмотрен специальный кэш страниц - TLB, так что если к странице происходят достаточно частые обращения её физический адрес будет взят из TLB напрямую без "честной" трансляции.  

Что касается полного формата дескриптора, то от выглядит так

Таблица 17. Формат дескриптора страницы/таблицы

БитыНазначение
31-12биты 31-12 физического адреса страницы
11-9зарезервированы для операционной системы
8G - глобальная страница. Страница не удаляется из буфера TLB при переключении задач или перезагрузке регистра CR3
7PS - размер страницы 1 - страница 2 или 4 Мб, иначе - 0
6D - "грязная" страница, устанавливается в 1 при записи в страницу
5A - бит доступа, устанавливается в 1 при обращении к странице
4PCD - бит запрещения кэширования
3PWT - бит разрешения сквозной записи
2U - страница доступна из пользовательского режима (с CPL=3)
1W - страница доступна для записи
0P - страница присутствует

Таким образом младшие 12 бит используются для кодирования служебной информации, касающейся прав доступа к таблицам/страница и правил работы с ними процессора. Рассмотрим эти флаги подробнее

P - бит присутствия, если он взведен, страница присутствует в физической памяти. Если страницы нет в памяти, генерируется исключение #PF (прерывание INT 0Eh). Обработка данного исключения используется в частности для организации подкачки страниц с HDD.

W - бит разрешения записи. Если он сброшен, то страница будет доступна только для чтения, и попытка записи в неё генерирует исключение #PF.

U - доступ к странице из пользовательского режима, если этот бит взведен, то к странице можно обращаться из кода с уровнем привилегий 3 кольца защиты.  

Для того чтобы включить страничную память необходимо:
  1. Сформировать в памяти структуру, определяющую отображение физических страниц в ВАП ядра ОС, то есть создать каталог страниц и разметить хотя бы ту физическую память, где размещено ядро и области ОЗУ к которым оно будет обращаться в процессе работы.
  2. Загрузить адрес каталога страниц в регистр CR3 процессора.
  3. Взвести бит 31 регистра CR0, это действие в общем-то и включает режим страничной адресации.
Формат регистра CR3 таков

Таблица 18. Формат регистра CR3
БитыНазначение
31-1220 старших бит физического адреса начала каталога страниц, если бит PAE в CR4 равен нулю, или
31-527 старших бит физического адреса таблицы указателей на каталоги страниц, если бит PAE = 1.
4 (80486+)бит PCD - запрещение кэширования страниц - этот бит запрещает загрузку текущей страницы в кэш-память
3 (80486+)бит PWT - бит сквозной записи 0 управляет методом записи страниц во внешний кэш

Мы не используем режим PAE, поэтому нас интересует адрес каталога страниц в формате 0xYYYYY000 - то есть выровненный по границе 4 Кб страницы. Первые 12 бит адреса, загруженного в CR3 просто игнорируются процессором и об этом необходимо помнить, формируя структуру каталога страниц! При невыполнения этого условия процессор просто не найдет необходимой ему корректной информации и перезагрузит компьютер.

Завершая теоретическую часть статьи, думаю уместно вскользь упомянуть о режиме PAE, который появился в процессорах Pentium Pro и выше. Суть его заключается в том, что данные процессоры оснащены 36-разрядной шиной адреса, то есть фактически способны адресовать 236 = 64 Гб оперативной памяти. При взведенном бите PAE в регистре CR4 этот режим становится доступным.

Таким образом даже в x86 системе доступна возможность адресовать память более 4 Гб, но эта фишка актуальна для систем технически допускающих установку на материнскую плату более 4 Гб оперативной памяти, и это в основном серверные решения. Ядро Linux например, в его 32-разрядной версии можно собрать с опцией поддержки PAE, однако в связи с широкой распространенностью архитектуры x86-64 даже на домашних ПК эта возможность теряет свою актуальность.

На этом теоретическую часть я завершу, в следующих статьях сосредоточившись на практической реализации описанного в нашем ядре.