Как работает paging?
Страничная память (paging) — способ организации виртуальной памяти, при котором единицей отображения виртуальных адресов на физические является страница. Типичный размер 4096 байт, для некоторых архитектур до 128 КБ.
Поддержка такого режима присутствует в большинстве 32битных и
64битных процессоров.
Решаемые задачи
- поддержка изоляции процессов и защиты памяти путём создания своего собственного виртуального адресного пространства для каждого процесса
- поддержка изоляции области ядра от кода пользовательского режима
- поддержка памяти «только для чтения» и неисполняемой памяти
- поддержка отгрузки давно не используемых страниц в область подкачки на диске
- поддержка отображённых в память файлов, в том числе загрузочных модулей
- поддержка разделяемой между процессами памяти, в том числе с копированием-по-записи для экономии физических страниц
- поддержка системного вызова fork() в ОС семейства UNIX
Запись таблицы страниц обычно содержит в себе следующую информацию:
- флаг «страница отображена»
- физический адрес
- флаг «страница доступна из режима пользователя». При неустановке данного флага страница доступна только из режима ядра.
- флаг «страница доступна только на чтение».
- флаг «страница недоступна на исполнение».
- режим использования кэша
для страницы. Особенно часто используется для
видеопамяти и для отображенных в память
регистров устройств (полное отсутствие кэширования).
Так как число записей в одной таблице ограничено и зависит от размера
записи и размера страницы, используется многоуровневая организация
таблиц, часто 2 или 3 уровня, иногда 4 уровня (для 64-х разрядных
архитектур). В случае 2 уровней используется «директория» страниц,
имеющая в себе записи, указывающие на физические адреса таблиц страниц.
Таблицы содержат в себе записи, указывающие уже на страницы данных. В
случае 3 уровней возникает ещё и супер-директория, содержащая в себе
записи, указывающие на несколько директорий.
Старшие биты виртуального адреса указывают на номер записи в
директории, средние — номер записи в таблице, младшие (адрес внутри
страницы) попадают в физический адрес без трансляции.
Формат записей таблиц, их размер, размер страницы и организация
таблиц зависит от типа процессора, а иногда и от режима его работы.
Исторически, x86 использует 32битные PTE, 32битные виртуальные
адреса, 4KB страницы, 1024 записи в таблице, двухуровневые таблицы,
старшие 10 бит виртуального адреса — номер записи в директории,
следующие 10 — номер записи в таблице, младшие 12 — адрес внутри
страницы.
Физический адрес директории или же
супер-директории загружен в один из управляющих регистров процессора.
Если обращение к памяти не может быть оттранслировано через TLB, то
микрокод процессора обращается к таблицам страниц и пытается загрузить
PTE оттуда в TLB. Если и после такой попытки сохранились проблемы, то
процессор исполняет специальное прерывание, называемое «отказ страницы»
(page fault). Обработчик этого прерывания находится в подсистеме
виртуальной памяти ядра ОС.
Причины отказа страницы (page fault):
- не существует таблицы, отображающей данный регион
- PTE не имеет взведённого флага «страница отображена».
- попытка обратиться из пользовательского режима к странице «только для ядра».
- попытка записи в страницу «только для чтения».
- попытка исполнения кода из страницы «исполнение запрещено».
Обработчик отказов в ядре может загрузить нужную страницу из файла или же из области подкачки,
может создать доступную на запись копию страницы «только для чтения», а
может и возбудить исключительную ситуацию в данном процессе.
Каждый процесс имеет свой собственный набор таблиц страниц. Регистр «директория страниц» перегружается при каждом переключении контекста процесса. Также необходимо сбросить ту часть TLB, которая относится к данному процессу.
В большинстве случаев ядро ОС помещается в то же адресное
пространство, что и процессы, для него резервируются верхние 1-2
гигабайта 32битного адресного пространства каждого процесса. Это
делается с целью избежать переключения таблиц страниц при входе в ядро и
выходе из него. Страницы ядра помечаются как недоступные для кода
режима пользователя.
Так как память ядра одинакова у всех процессов, соответствующие ей
TLB не нужно перегружать после переключения процесса. Для этой
оптимизации x86 поддерживает флаг «глобальный» у PTE.
PAE, x64, IOMMU?
В архитектуре x86_64 возможно использовать страницы размером 4
килобайта (4096 байт), 2 мегабайта, и (в некоторых AMD64) 1 гигабайт. Организация страниц для этой архитектуры имеет 4 уровня.
Physical Address Extension (PAE) — режим работы встроенного блока управления памятью x86-совместимых
процессоров, в котором используются 64-битные элементы таблиц страниц
(из которых для адресации используются только 36 бит), c помощью которых
процессор может адресовать 64 ГБ физической памяти (вместо 4 ГБ,
адресуемых при использовании 32-разрядных таблиц), хотя каждая задача
(программа) всё равно может адресовать максимум до 4 ГБ виртуальной
памяти. Также, в новых моделях процессоров в PAE-режиме старший бит элемента таблицы страниц отвечает за запрет исполнения кода в странице, что затрудняет атаку по методу переполнения буфера.
IOMMU — блок управления памятью
(MMU) для операций ввода-вывода. Так же как традиционный, процессорный
блок управления памятью, который переводит виртуальные адреса, видимые
процессором в физические, этот блок занимается трансляцией виртуальных
адресов, видимых аппаратным устройством, в физические адреса. Некоторые
IOMMU также позволяют задавать различные ограничения операций
ввода-вывода для защиты от неправильно работающих устройств или для
изоляции, например, при использовании виртуализации.
Обработчики прерываний обычно пишутся таким образом, чтобы время их
обработки было как можно меньшим, поскольку во время их работы могут не
обрабатываться другие прерывания, а если их будет много (особенно от
одного источника), то они могут теряться.
До окончания обработки прерывания обычно устанавливается запрет на
обработку этого типа прерывания, чтобы процессор не входил в цикл
обработки одного прерывания. Все источники
прерываний делятся на классы и каждому классу назначается свой уровень
приоритета запроса на прерывание. Приоритеты могут обслуживаться как
относительные и абсолютные.
На этом на сегодня всё, читайте продолжение в следующем посте.