Как работает пейджинг на x86?
Этот вопрос призван заполнить вакуум хорошей бесплатной информацией по этому вопросу.
Я считаю, что хороший ответ будет соответствовать одному большому SO-ответу или, по крайней мере, нескольким ответам.
Основная цель - дать начинающим новичкам достаточно информации, чтобы они могли самостоятельно изучить руководство и понять основные понятия ОС, связанные с подкачкой страниц.
Предлагаемые рекомендации:
- ответы должны быть новичками:
- конкретные, но, возможно, упрощенные примеры очень важны
- приложения представленных концепций приветствуются
- цитирование полезных ресурсов - это хорошо
- небольшие отступления в том, как ОС используют функции подкачки
- PAE и PSE объяснения приветствуются
- небольшие отступления в x86_64 приветствуются
Связанные вопросы и почему я думаю, что они не обманщики:
Как работают таблицы страниц x86?: название почти такое же, как этот вопрос, но основная часть задает конкретные вопросы, связанные с cr3 и TLB. Этот вопрос является подмножеством этого.
Как работает виртуализация x86: тело запрашивает только источники.
2 ответа
Версия этого ответа с хорошим содержанием и большим содержанием.
Я исправлю любую сообщенную ошибку. Если вы хотите сделать большие изменения или добавить недостающий аспект, сделайте их на свои собственные ответы, чтобы получить заслуженную репутацию. Незначительные правки могут быть объединены непосредственно в.
Образец кода
Минимальный пример: https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S
Как и все остальное в программировании, единственный способ действительно понять это - играть с минимальным количеством примеров.
То, что делает это "трудным" предметом, состоит в том, что минимальный пример велик, потому что вам нужно сделать свою собственную маленькую ОС.
Руководство Intel
Хотя понять это невозможно без примеров, постарайтесь как можно скорее ознакомиться с руководствами.
Intel описывает пейджинг в Руководстве по системному программированию Intel Том 3 - 325384-056RU Сентябрь 2015 Глава 4 "Пейджинг".
Особенно интересным является рисунок 4-4 "Форматы записей CR3 и записей пейджинговой структуры с 32-битным пейджингом", на котором представлены ключевые структуры данных.
MMU
Пейджинг осуществляется частью блока управления памятью (MMU) ЦПУ. Как и многие другие (например, сопроцессор x87, APIC), раньше он был отдельным чипом, который впоследствии был интегрирован в процессор. Но термин все еще используется.
Главные факты
Логические адреса - это адреса памяти, используемые в "обычном" коде земли пользователя (например, содержимое rsi
в mov eax, [rsi]
).
Сначала сегментация переводит их в линейные адреса, а затем пейджинг затем переводит линейные адреса в физические адреса.
(logical) ------------------> (linear) ------------> (physical)
segmentation paging
Большую часть времени мы можем думать о физических адресах как об индексе фактических аппаратных ячеек оперативной памяти, но это не на 100% верно из-за:
Пейджинг доступен только в защищенном режиме. Использование подкачки в защищенном режиме не является обязательным. Пейджинг включен, если PG
немного cr0
регистр установлен.
Пейджинг против сегментации
Одно из основных различий между разбиением на страницы и сегментацией заключается в том, что:
- подкачка разделяет оперативную память на куски одинакового размера, называемые страницами
- Сегментация разбивает память на куски произвольных размеров
Это главное преимущество подкачки, поскольку куски одинакового размера делают вещи более управляемыми.
Пейджинг стал настолько популярным, что поддержка сегментации была прекращена в x86-64 в 64-битном режиме, основном режиме работы для нового программного обеспечения, где он существует только в режиме совместимости, который эмулирует IA32.
заявка
Пейджинг используется для реализации процессов виртуальных адресных пространств на современных ОС. С виртуальными адресами ОС может разместить два или более параллельных процесса в одной оперативной памяти таким образом, чтобы:
- обе программы не должны ничего знать о других
- память обеих программ может увеличиваться и уменьшаться по мере необходимости
- переключение между программами происходит очень быстро
- одна программа никогда не сможет получить доступ к памяти другого процесса
Пейджинг исторически возник после сегментации и в значительной степени заменил ее для реализации виртуальной памяти в современных ОС, таких как Linux, поскольку проще управлять фрагментами памяти фиксированного размера страниц вместо сегментов переменной длины.
Аппаратная реализация
Подобно сегментации в защищенном режиме (когда изменение регистра сегмента запускает загрузку из GDT или LDT), аппаратные средства разбиения на страницы используют структуры данных в памяти для выполнения своей работы (таблицы страниц, каталоги страниц и т. Д.).
Формат этих структур данных фиксируется аппаратным обеспечением, но операционная система должна правильно настроить эти структуры данных в оперативной памяти и управлять ими, а также сообщать аппаратному обеспечению, где их найти (через cr3
).
Некоторые другие архитектуры почти полностью оставляют подкачку в руках программного обеспечения, поэтому при пропадании TLB запускается функция, предоставляемая ОС, для обхода таблиц страниц и вставки нового отображения в TLB. Это оставляет ОС для выбора форматов таблиц страниц, но делает маловероятным, чтобы аппаратное обеспечение могло перекрывать обход страниц с неупорядоченным выполнением других инструкций, как это может сделать x86.
Пример: упрощенная одноуровневая схема подкачки
Это пример того, как пейджинг работает в упрощенной версии архитектуры x86 для реализации пространства виртуальной памяти.
Таблицы страниц
ОС может дать им следующие таблицы страниц:
Таблица страниц, предоставленная процессу 1 ОС:
RAM location physical address present
----------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0x00000 1
PT1 + 2 * L 0x00003 1
PT1 + 3 * L 0
... ...
PT1 + 0xFFFFF * L 0x00005 1
Таблица страниц, предоставленная процессу 2 ОС:
RAM location physical address present
----------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000B 1
PT2 + 2 * L 0
PT2 + 3 * L 0x00003 1
... ... ...
PT2 + 0xFFFFF * L 0x00004 1
Куда:
PT1
а такжеPT2
: начальная позиция таблиц 1 и 2 в оперативной памяти.Примерные значения:
0x00000000
,0x12345678
, так далее.Именно ОС определяет эти значения.
L
: длина записи таблицы страниц.present
: указывает, что страница присутствует в памяти.
Таблицы страниц расположены в оперативной памяти. Например, они могут быть расположены как:
--------------> 0xFFFFFFFF
--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1
--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2
--------------> 0x0
Начальные расположения в ОЗУ для обеих таблиц страниц являются произвольными и контролируются ОС. Это зависит от ОС, чтобы они не перекрывались!
Каждый процесс не может напрямую касаться таблиц страниц, хотя он может отправлять запросы к ОС, которые вызывают изменение таблиц страниц, например, запрашивая большие сегменты стека или кучи.
Страница представляет собой фрагмент размером 4 КБ (12 бит), и поскольку адреса имеют 32 бита, для идентификации каждой страницы требуется только 20 бит (20 + 12 = 32, то есть 5 символов в шестнадцатеричном формате). Это значение фиксируется оборудованием.
Записи таблицы страниц
Таблица страниц - это... таблица записей таблицы страниц!
Точный формат записей таблицы устанавливается аппаратно.
В этом упрощенном примере записи таблицы страниц содержат только два поля:
bits function
----- -----------------------------------------
20 physical address of the start of the page
1 present flag
поэтому в этом примере конструкторы оборудования могли выбрать L = 21
,
Большинство реальных записей таблицы страниц имеют другие поля.
Было бы нецелесообразно выравнивать объекты на 21 байте, поскольку память адресуется байтами, а не битами. Следовательно, даже в этом случае требуется всего 21 бит, разработчики оборудования, вероятно, выберут L = 32
чтобы сделать доступ быстрее, и просто зарезервировать биты оставшиеся биты для последующего использования. Фактическое значение для L
на x86 стоит 32 бита.
Трансляция адресов в одноуровневой схеме
После того, как таблицы страниц настроены ОС, аппаратное обеспечение выполняет преобразование адресов между линейными и физическими адресами.
Когда ОС хочет активировать процесс 1, она устанавливает cr3
в PT1
Начало таблицы для процесса один.
Если процесс 1 хочет получить доступ к линейному адресу 0x00000001
аппаратная схема пейджинга автоматически выполняет следующие действия для ОС:
разделить линейный адрес на две части:
| page (20 bits) | offset (12 bits) |
Так что в этом случае мы бы имели:
- страница = 0x00000
- смещение = 0x001
загляните в таблицу страниц 1, потому что
cr3
указывает на это.смотреть запись
0x00000
потому что это часть страницы.Аппаратное обеспечение знает, что эта запись находится по адресу RAM
PT1 + 0 * L = PT1
,поскольку он присутствует, доступ действителен
по таблице страниц, расположение номера страницы
0x00000
я сидела0x00001 * 4K = 0x00001000
,чтобы найти конечный физический адрес, нам просто нужно добавить смещение:
00001 000 + 00000 001 ----------- 00001 001
так как
00001
это физический адрес страницы, которая была найдена на столе и001
это смещение.Как видно из названия, к смещению всегда просто добавляется физический адрес страницы.
аппаратное обеспечение затем получает память в этом физическом месте.
Таким же образом, следующие процессы будут происходить для процесса 1:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00002 000 00002 000
FFFFF 000 00005 000
Например, при доступе к адресу 00001000
часть страницы 00001
оборудование знает, что его запись в таблице страниц находится по адресу RAM: PT1 + 1 * L
(1
из-за части страницы), и именно там он будет искать его.
Когда ОС хочет переключиться на процесс 2, все, что ей нужно сделать, это сделать cr3
укажите на страницу 2. Это так просто!
Теперь для процесса 2 произойдут следующие переводы:
linear physical
--------- ---------
00000 002 00001 002
00000 003 00001 003
00000 FFF 00001 FFF
00001 000 00000 000
00001 001 00000 001
00001 FFF 00000 FFF
00003 000 00003 000
FFFFF 000 00004 000
Один и тот же линейный адрес преобразуется в разные физические адреса для разных процессов, в зависимости только от значения внутри cr3
,
Таким образом, каждая программа может ожидать, что ее данные начнутся с 0
и заканчивается в FFFFFFFF
, не беспокоясь о точных физических адресах.
Ошибка страницы
Что если процесс 1 попытается получить доступ к адресу на странице, которого нет?
Аппаратное обеспечение уведомляет программное обеспечение через исключение ошибки страницы.
Затем обычно ОС должна зарегистрировать обработчик исключений, чтобы решить, что должно быть сделано.
Возможно, что доступ к странице, которой нет в таблице, является ошибкой программирования:
int is[1];
is[2] = 1;
но могут быть случаи, когда это приемлемо, например в Linux, когда:
программа хочет увеличить свой стек.
Он просто пытается получить доступ к определенному байту в заданном возможном диапазоне, и, если ОС довольна, он добавляет эту страницу в адресное пространство процесса.
страница была перенесена на диск.
ОС должна будет выполнить некоторую работу за процессами, чтобы вернуть страницу обратно в оперативную память.
ОС может обнаружить, что это так, основываясь на содержимом остальной части записи таблицы страниц, поскольку, если текущий флаг сброшен, другие записи записи таблицы страниц полностью оставляются для ОС в соответствии с тем, что она хочет.
В Linux, например, когда присутствует = 0:
если все поля записи таблицы страниц равны 0, неверный адрес.
иначе, страница была перенесена на диск, и фактические значения этих полей кодируют положение страницы на диске.
В любом случае ОС должна знать, по какому адресу возникла ошибка страницы, чтобы справиться с проблемой. Вот почему хорошие разработчики IA32 устанавливают значение cr2
по этому адресу всякий раз, когда происходит сбой страницы. Обработчик исключений может просто посмотреть на cr2
чтобы получить адрес.
Упрощения
Упрощения к реальности, которые облегчают понимание этого примера:
все реальные пейджинговые схемы используют многоуровневый пейджинг для экономии места, но это показало простую одноуровневую схему.
Таблицы страниц содержали только два поля: 20-битный адрес и 1-битный флаг присутствия.
Реальные таблицы страниц содержат в общей сложности 12 полей и, следовательно, другие функции, которые были опущены.
Пример: многоуровневая пейджинговая схема
Проблема с одноуровневой схемой подкачки состоит в том, что она будет занимать слишком много оперативной памяти: 4G / 4K = 1M записей на процесс. Если каждая запись имеет длину 4 байта, это составит 4 МБ на процесс, что слишком много даже для настольного компьютера: ps -A | wc -l
говорит, что я сейчас запускаю 244 процесса, так что это займет около 1 ГБ моей оперативной памяти!
По этой причине разработчики x86 решили использовать многоуровневую схему, которая уменьшает использование оперативной памяти.
Недостатком этой системы является то, что она имеет немного большее время доступа.
В простой трехуровневой схеме подкачки, используемой для 32-разрядных процессоров без PAE, 32 адресных бита разделены следующим образом:
| directory (10 bits) | table (10 bits) | offset (12 bits) |
Каждый процесс должен иметь один и только один каталог страниц, связанный с ним, поэтому он будет содержать как минимум 2^10 = 1K
записи каталога страниц, намного лучше, чем минимум 1M, требуемый для одноуровневой схемы.
Таблицы страниц распределяются только по мере необходимости ОС. Каждая таблица страниц имеет 2^10 = 1K
записи каталога страниц
Каталоги страниц содержат... записи каталога страниц! Записи каталога страниц такие же, как записи таблицы страниц, за исключением того, что они указывают на адреса ОЗУ таблиц страниц, а не на физические адреса таблиц. Поскольку эти адреса имеют ширину всего 20 бит, таблицы страниц должны быть в начале страниц размером 4 КБ.
cr3
теперь указывает на расположение в оперативной памяти каталога страниц текущего процесса вместо таблиц страниц.
Записи в таблицах страниц вообще не меняются по одноуровневой схеме.
Таблицы страниц изменяются от одноуровневой схемы, потому что:
- каждый процесс может иметь до 1 КБ таблиц страниц, по одной на каждую запись каталога страниц.
- Каждая таблица страниц содержит ровно 1 КБ записей вместо 1 МБ.
Причина использования 10 битов на первых двух уровнях (а не, скажем, 12 | 8 | 12
) состоит в том, что каждая запись таблицы страниц имеет длину 4 байта. Тогда 2^10 записей каталогов страниц и таблиц страниц будут хорошо вписываться в страницы размером 4 КБ. Это означает, что для этой цели быстрее и проще выделять и освобождать страницы.
Трансляция адресов в многоуровневой схеме
Каталог страницы, данный процессу 1 ОС:
RAM location physical address present
--------------- ----------------- --------
PD1 + 0 * L 0x10000 1
PD1 + 1 * L 0
PD1 + 2 * L 0x80000 1
PD1 + 3 * L 0
... ...
PD1 + 0x3FF * L 0
Таблицы страниц, переданные процессу 1 ОС по адресу PT1 = 0x10000000
(0x10000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT1 + 0 * L 0x00001 1
PT1 + 1 * L 0
PT1 + 2 * L 0x0000D 1
... ...
PT1 + 0x3FF * L 0x00005 1
Таблицы страниц, переданные процессу 1 ОС по адресу PT2 = 0x80000000
(0x80000
* 4K):
RAM location physical address present
--------------- ----------------- --------
PT2 + 0 * L 0x0000A 1
PT2 + 1 * L 0x0000C 1
PT2 + 2 * L 0
... ...
PT2 + 0x3FF * L 0x00003 1
где:
PD1
: начальная позиция каталога страниц процесса 1 в оперативной памяти.PT1
а такжеPT2
: начальная позиция таблицы страниц 1 и таблицы страниц 2 для процесса 1 в оперативной памяти.
Таким образом, в этом примере каталог страницы и таблица страниц могут храниться в ОЗУ примерно так:
----------------> 0xFFFFFFFF
----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2
----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1
----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1
----------------> 0x0
Переведем линейный адрес 0x00801004
шаг за шагом.
Мы предполагаем, что cr3 = PD1
то есть он указывает на каталог страниц, только что описанный.
В двоичном коде линейный адрес:
0 0 8 0 1 0 0 4
0000 0000 1000 0000 0001 0000 0000 0100
Группировка как 10 | 10 | 12
дает:
0000000010 0000000001 000000000100
0x2 0x1 0x4
который дает:
- запись каталога страницы = 0x2
- запись таблицы страниц = 0x1
- смещение = 0x4
Таким образом, аппаратное обеспечение ищет запись 2 каталога страницы.
Таблица каталога страниц говорит, что таблица страниц расположена в 0x80000 * 4K = 0x80000000
, Это первый доступ к ОЗУ процесса.
Поскольку запись таблицы страниц 0x1
аппаратное обеспечение смотрит на запись 1 таблицы страниц в 0x80000000
, что говорит о том, что физическая страница находится по адресу 0x0000C * 4K = 0x0000C000
, Это второй доступ к ОЗУ процесса.
Наконец, пейджинговое оборудование добавляет смещение, и конечный адрес 0x0000C004
,
Другие примеры переведенных адресов:
linear 10 10 12 split physical
-------- --------------- ----------
00000001 000 000 001 00001001
00001001 000 001 001 page fault
003FF001 000 3FF 001 00005001
00400000 001 000 000 page fault
00800001 002 000 001 0000A001
00801008 002 001 008 0000C008
00802008 002 002 008 page fault
00B00001 003 000 000 page fault
Сбои страниц возникают, если отсутствует запись каталога страниц или запись таблицы страниц.
Если ОС захочет запустить другой процесс одновременно, она даст второму процессу отдельный каталог страниц и свяжет этот каталог с отдельными таблицами страниц.
64-битные архитектуры
64-битный - это все еще слишком большой адрес для текущих размеров оперативной памяти, поэтому большинство архитектур будет использовать меньше битов.
x86_64 использует 48 бит (256 TiB), а PAE в старом режиме уже позволяет 52-битные адреса (4 PiB).
12 из этих 48 битов уже зарезервированы для смещения, которое оставляет 36 битов.
Если используется двухуровневый подход, лучшим разделением будут два 18-битных уровня.
Но это будет означать, что каталог страницы будет иметь 2^18 = 256K
записей, которые занимали бы слишком много ОЗУ: близко к одноуровневой подкачке для 32-битных архитектур!
Следовательно, 64-битные архитектуры создают еще больше уровней страниц, обычно 3 или 4.
x86_64 использует 4 уровня в 9 | 9 | 9 | 12
схема, так что верхний уровень занимает только 2^9
записи более высокого уровня.
PAE
Расширение физического адреса.
С 32 битами можно адресовать только 4 ГБ ОЗУ.
Это стало ограничением для больших серверов, поэтому Intel представила механизм PAE для Pentium Pro.
Чтобы решить эту проблему, Intel добавила 4 новые адресные строки, чтобы можно было адресовать 64 ГБ.
Структура таблицы страниц также изменяется, если включен PAE. Точный способ его изменения зависит от того, включен или выключен PSE.
PAE включается и выключается через PAE
немного cr4
,
Даже если общая адресуемая память составляет 64 ГБ, отдельные процессы могут использовать только до 4 ГБ. Однако ОС может размещать разные процессы на разных блоках по 4 ГБ.
PSE
Расширение размера страницы.
Позволяет страницам иметь длину 4M (или 2M, если включена PAE) вместо 4K.
PSE включается и выключается через PAE
немного cr4
,
PAE и PSE схемы таблиц страниц
Если активны либо PAE, либо PSE, используются разные схемы уровня пейджинга:
нет PAE и нет PSE:
10 | 10 | 12
нет PAE и PSE:
10 | 22
,22 - это смещение на странице 4Mb, так как 22-битный адрес 4Mb.
PAE и нет PSE:
2 | 9 | 9 | 12
Причина дизайна, почему 9 используется дважды вместо 10, состоит в том, что теперь записи больше не могут помещаться в 32 бита, которые были заполнены 20 адресными битами и 12 значащими или зарезервированными битами флага.
Причина в том, что 20 бит больше не достаточно для представления адреса таблиц страниц: теперь необходимо 24 бита из-за 4 дополнительных проводов, добавленных к процессору.
Поэтому дизайнеры решили увеличить размер записи до 64 бит, и, чтобы они поместились в одну таблицу страниц, необходимо уменьшить количество записей до 2^9 вместо 2^10.
Начало 2 - это новый уровень страницы, называемый таблицей указателей страниц (PDPT), поскольку он указывает на каталоги страниц и заполняет 32-битный линейный адрес. PDPT также имеют ширину 64 бита.
cr3
Теперь указывает на PDPT, которые должны быть на первых четырех 4 ГБ памяти и выровнены по 32-битным кратным для эффективности адресации. Это означает, что сейчасcr3
имеет 27 значащих бит вместо 20: 2^5 для 32 кратных * 2^27 для завершения 2^32 из первых 4 ГБ.PAE и PSE:
2 | 9 | 21
Дизайнеры решили оставить поле шириной 9 бит, чтобы оно поместилось на одной странице.
Это оставляет 23 бита. Оставляя 2 для PDPT, чтобы сохранить единообразие в случае PAE без PSE, оставляется 21 для смещения, что означает, что страницы имеют ширину 2M вместо 4M.
TLB
Буфер преобразования перевода (TLB) - это кэш для адресов подкачки.
Поскольку это кеш, он разделяет многие проблемы проектирования кеша процессора, такие как уровень ассоциативности.
В этом разделе должен быть описан упрощенный полностью ассоциативный TLB с 4 отдельными адресными записями. Обратите внимание, что, как и другие кэши, реальные TLB обычно не являются полностью ассоциативными.
Основная операция
После преобразования между линейным и физическим адресом он сохраняется в TLB. Например, TLB с 4 записями начинается в следующем состоянии:
valid linear physical
------ ------- ---------
> 0 00000 00000
0 00000 00000
0 00000 00000
0 00000 00000
>
указывает текущую запись, подлежащую замене.
и после страницы линейный адрес 00003
переводится на физический адрес 00005
TLB становится:
valid linear physical
------ ------- ---------
1 00003 00005
> 0 00000 00000
0 00000 00000
0 00000 00000
и после второго перевода 00007
в 00009
это становится:
valid linear physical
------ ------- ---------
1 00003 00005
1 00007 00009
> 0 00000 00000
0 00000 00000
Сейчас если 00003
необходимо снова перевести, аппаратное обеспечение сначала ищет TLB и выясняет его адрес с одним доступом к ОЗУ 00003 --> 00005
,
Конечно, 00000
не находится в TLB, так как ни одна действительная запись не содержит 00000
в качестве ключа.
Политика замены
Когда TLB заполнен, старые адреса перезаписываются. Как и в случае с кэшем ЦП, политика замены является потенциально сложной операцией, но простой и разумной эвристикой является удаление наименее использованной записи (LRU).
С LRU, начиная с состояния:
valid linear physical
------ ------- ---------
> 1 00003 00005
1 00007 00009
1 00009 00001
1 0000B 00003
добавление 0000D -> 0000A
даст:
valid linear physical
------ ------- ---------
1 0000D 0000A
> 1 00007 00009
1 00009 00001
1 0000B 00003
CAM
Использование TLB ускоряет преобразование, поскольку для первоначального преобразования требуется один доступ на уровень TLB, что означает 2 в простой 32-разрядной схеме, но 3 или 4 в 64-разрядных архитектурах.
TLB обычно реализуется как дорогой тип оперативной памяти, называемый контентно-адресуемой памятью (CAM). CAM реализует ассоциативную карту на аппаратном уровне, то есть структуру, которая дает ключ (линейный адрес), получает значение.
Сопоставления также могут быть реализованы для адресов ОЗУ, но для сопоставлений САМ может потребоваться гораздо меньше записей, чем для сопоставления ОЗУ.
Например, карта, на которой:
- и ключи, и значения имеют 20 бит (в случае простых схем пейджинга)
- максимум 4 значения должны быть сохранены каждый раз
может храниться в TLB с 4 записями:
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
FFFFF 00000
Однако для реализации этого с ОЗУ необходимо иметь 2^20 адресов:
linear physical
------- ---------
00000 00001
00001 00010
00010 00011
... (from 00011 to FFFFE)
FFFFF 00000
что было бы даже дороже, чем использование TLB.
Недействительные записи
когда cr3
Изменения, все записи TLB становятся недействительными, потому что будет использоваться новая таблица страниц для нового процесса, поэтому маловероятно, чтобы какая-либо из старых записей имела какое-либо значение.
X86 также предлагает invlpg
инструкция, которая явно делает недействительной одну запись TLB. Другие архитектуры предлагают еще больше инструкций для недействительных записей TLB, например, делают недействительными все записи в данном диапазоне.
Некоторые процессоры x86 выходят за рамки требований спецификации x86 и обеспечивают большую согласованность, чем она гарантирует, между изменением записи таблицы страниц и ее использованием, когда она еще не была кэширована в TLB. Очевидно, Windows 9x полагался на это для правильности, но современные процессоры AMD не обеспечивают последовательного просмотра страниц. Процессоры Intel делают, даже если они должны обнаружить неправильные предположения, чтобы сделать это. Воспользоваться этим преимуществом, вероятно, плохая идея, поскольку, вероятно, выиграть нечего, и существует большой риск возникновения тонких, чувствительных ко времени проблем, которые трудно будет отладить.
Использование ядра Linux
Ядро Linux широко использует функции подкачки x86, чтобы обеспечить быстрое переключение процессов с небольшой фрагментацией данных.
В v4.2
заглянуть под arch/x86/
:
include/asm/pgtable*
include/asm/page*
mm/pgtable*
mm/page*
Кажется, не существует структур, определенных для представления страниц, только макросы: include/asm/page_types.h
особенно интересно. Выдержка:
#define _PAGE_BIT_PRESENT 0 /* is present */
#define _PAGE_BIT_RW 1 /* writeable */
#define _PAGE_BIT_USER 2 /* userspace addressable */
#define _PAGE_BIT_PWT 3 /* page write through */
arch/x86/include/uapi/asm/processor-flags.h
определяет CR0
и, в частности, PG
положение бита:
#define X86_CR0_PG_BIT 31 /* Paging */
Список используемой литературы
Свободно:
rutgers-pxk-416 глава "Управление памятью: конспект лекции"
Хороший исторический обзор методов организации памяти, используемых старыми ОС.
Несвободное:
bovet05 глава "Адресация памяти"
Разумное введение в адресацию памяти x86. Не хватает хороших и простых примеров.
Вот очень короткий ответ высокого уровня:
Процессор x86 работает в одном из нескольких возможных режимов (примерно: реальный, защищенный, 64-битный). Каждый режим может использовать одну из нескольких возможных моделей адресации памяти (но не каждый режим может использовать каждую модель), а именно: адресация в реальном режиме, сегментированная адресация и плоско-линейная адресация.
В современном мире важна только плоско-линейная адресация в защищенном или 64-битном режиме, и эти два режима, по сути, одинаковы, главное отличие состоит в размере машинного слова и, следовательно, в адресуемом объеме памяти.
Теперь режим адресации памяти придает смысл операндам памяти машинных инструкций (таких как mov DWORD PTR [eax], 25
, который хранит 32-битный (ака dword
) целое число со значением 25 в память, адрес которой хранится в eax
32-битный регистр). При плоско-линейной адресации это число в eax
разрешено работать в одном непрерывном диапазоне от нуля до максимального значения (в нашем случае это 232 - 1).
Однако плоско-линейная адресация может быть страничной или не страничной. Без подкачки адрес напрямую относится к физической памяти. При использовании подкачки блок управления памятью (или MMU) процессора прозрачно подает требуемый адрес (теперь называемый виртуальным адресом) в механизм поиска, так называемые таблицы страниц, и получает новое значение, которое интерпретируется как физический адрес. Исходная операция теперь работает с этим новым переведенным адресом в физической памяти, даже если пользователь только когда-либо видит виртуальный адрес.
Основное преимущество подкачки состоит в том, что таблицы страниц управляются операционной системой. Таким образом, операционная система может изменять и заменять таблицы страниц произвольно, например, при "переключении задач". Он может хранить целую коллекцию таблиц страниц, по одной для каждого "процесса", и всякий раз, когда он решает, что конкретный процесс будет выполняться на данном ЦП, он загружает таблицы страниц процесса в MMU этого ЦП (каждый ЦП имеет свой собственный набор таблиц страниц). В результате каждый процесс видит свое собственное виртуальное адресное пространство, которое выглядит одинаково независимо от того, какие физические страницы были свободны, когда ОС должна была выделить для него память. Он никогда не знает о памяти какого-либо другого процесса, так как не может напрямую обращаться к физической памяти.
Таблицы страниц - это вложенные древовидные структуры данных, хранящиеся в обычной памяти, записываемые ОС, но считываемые непосредственно аппаратно, поэтому формат является фиксированным. Они "загружаются" в MMU, устанавливая специальный регистр управления CPU, чтобы он указывал на таблицу верхнего уровня. Процессор использует кеш, называемый TLB, для запоминания поисков, поэтому повторный доступ к одним и тем же нескольким страницам происходит намного быстрее, чем по отдельности, по причинам пропуска TLB, а также по обычным причинам кеширования данных. Обычно термин "запись TLB" используется для обозначения записей таблицы страниц, даже если они не кэшированы в TLB.
И в случае, если вы беспокоитесь о том, что процесс может просто отключить разбиение на страницы или попытаться изменить таблицы страниц: это недопустимо, поскольку x86 реализует уровни привилегий (называемые "кольцами"), а код пользователя выполняется с уровнем привилегий, слишком низким, чтобы это изменить таблицы страниц процессора.