Есть регистр CR0. Это флаговый регистр, флаги которого влияют на то, какие фичи процессора включены. То есть просто бит 1 — значит фича включена, бит 0 — фича не включена.
0-ой бит например называется
PE (Protected Enabled) и определяет, включен ли защищённый режиме или нет.
31-ый бит называется
PG (PaGing Enabled) и определяет, используется ли в добавок к сегментной (которая используется абсолютно всегда) ещё и страничная организация памяти.
Если используется, то есть регистр CR3. Естественно, что доступ к нему тоже только привилегированным кодом.
Часть регистра CR3, а если конкретнее, старшие 20 битов, считаются субрегистром
PDBR. Расшифровывается как Page-Directory-Base Register. Регистр базы директории страниц. Правда в русском языке директорию страниц принято звать каталогом страниц.
Собственно каталог страниц — вещь элементарнейшая. Это просто обыкновенный массив структур, одинаковых повторяющихся элементов.
А база каталога страниц — это просто адрес начала этого массива, то есть адрес первого элемента этого массива, первой структурки.
Элемент этого массива (то есть каталога страниц) называется
PDE. Page Directory Entry. Размер одного элемента (то есть размер PDE) — всего лишь 4 байта, то есть DWORD, как Long-переменная. Так что структурой с точки зрения VB-программиста это назвать сложно. Однако это всё-таки структура, ибо это 32-битное поле состоит из одного большого 20-битного поля и 12 однобитных флаговых полей.
Самое главное здесь это 20 битное поле в составе PDE. Это поле — адрес начала
другой таблицы.
Эта другая таблица — Page Table — то есть таблица страниц. Практически такой же массив, как Page Directory (каталог страниц).
Элемент такого массива называется
PTE (Page Table Entry). И он тоже 4-байтный, то есть 32-битный. И он тоже (как и PDE), состоит из одного большого 20-битового поля и 12 однобитных флаговых полей.
Смысл PTE-шки в том, что она описывает отдельно взятую страницу виртуального адресного пространства и ставит ему в соответствие конкретную отдельную страницу физической памяти. Самой что ни на есть физической — то есть непосредствнно RAM-а.
Большое 20-битное поле в составе PTE-шки — это физический адрес физической страницы в физической памяти. А оставшиеся однобитовые флаги как раз и определяют: можно ли читать страницу, можно ли писать в страницу, присутствует ли страница в физической памяти (или она в своп-файле), и была ли она изменена с момента последнего сброса в файл подкачки (процессор просто устанавливает этот бит при каждой записи в страницу, но он никогда не сбрасывает этот бит, ибо сбрасывать его — право (или обязанность?) операционной системы, ибо именно она заведует вопросами подкачки, которой может и вообще не быть в какой-нибудь ОС).
Собственно, ещё раз кратко:
CR3, он же, грубо говоря, PDBR указывает на PageDirectory.
PageDirectory это массив PDE-шек.
PageDirectory всего одна.
PDE-шки указывают на PageTable-ы.
Поскольку PDE-шек много, то и PageTable-ов много (но не обязательно столько же, сколько PDE-шек).
PageTable это массив PTE-шек.
PTE-шки — это описатели страниц. Указывают на страницу в физической памяти и хранят её атрибуты.
Допустим, мы обращаемся к памяти по некоторому адресу? Как ОС находит для этого адреса нужную PDE-шку, а оттуда оттуда адрес массива PTE-шек и находит в нём нужную PTE-шку?
На самом деле, ОС никак не находит, этим занимаются электроника процессора, то есть это делается аппаратно, а не программно.
Когда вы обращаетесь по определённому адресу, этот адрес дробится на три куска:
(картинка из википедии)
Процессор находит начало массива PDE-шек (узнаёт из регистра PDBR (CR3)).
Старшую часть линейного адреса, по которому осуществляется обращение, процессор используется как индекс PDE-шки в массиве PDE-шек. Берёт эту PDE-шек и получает из неё 20-битный адрес начала массива PTE-шек (массивов PTE-шек много).
Среднюю часть линейного адреса, по которому осуществляется обращение, процессор используется как индекс PTE-шки в массиве PTE-шек. Так он находит PTE-шку описывающую страницу, у которой происходит обращение. Из PTE-шки процессор узнаёт физический адрес страницы в физ. памяти.
Оставшуюся младшую часть линейного адреса процессор использует как смещение относительно начала физической страницы.
То есть диапазон оставшегося 12-битного младшего куска адреса — он как раз равен размеру страницы, и означает смещение байта/слова/дворда в адресуемой странице.
Весь этот процесс называется
преобразованием адреса, а блок логический блок процессора, который этим занимается, называется MMU.
Собственно, это только часть дела, причём последняя часть дела.
Ибо есть три адресных пространства (причём надо эти пространства рассматривать не более чем математические абстракции).
Виртуальное адресное пространство. Это именно то адресное пространство, которое видят все приложения, и само ядро ОС. Обычные привычные вам адреса — это всё виртуальные адреса. Причём это адресное пространство 48-битное, ибо указатель состоит из двух частей: 16-битного селектора сегмента и 32-битного смещения в сегменте. Но это полный (far) адрес, а из-за с натяжкой так называемой flat-организации, все обычно применяют не полные 48-битные адрес, а краткие (near) 32-битные — при этом недостающая 16-битная часть виртуального адреса берётся из сегментного регистра, который соответствует выполняемому действю (CS — если вы вызываете что-то, DS — если читаете/пишите, SS — если работаете со стеком).
Итак, если вы Windows или nix-программист, то вы используете виртуальные адреса, которые обычно для вас 32-битные, но вы можете использовать и 48-битные адреса, и более того, в Windows вы их часто используете, например записывая что-нибудь по адресу FS:[0].
Линейное адресное пространство. Линейное пространство имеет размер 4 гб. Линейные адреса — 32-битные. Вы никогда не используете линейные адреса. Вы не моежет просто взять и обратиться в линейному адресному пространству по имеющемуся у вас линейному адресу.
Физическое адресное пространствоСобственно, та самая физическая память. Физический адрес — тот самый адрес, который появляется на «ножках» микросхему процессора.
Собственно, полностью процесс преобразования адреса выглядит так.
- Вы обращаетесь (всегда) по виртуальному адресу СЕЛЕКТОРСЕГМЕНТА:СМЕЩЕНИЕ.
- Ваш виртуальный адрес преобразовывается в линейный адрес (я не описывал здесь механизм преобразования, но он простой — по селектору сегмента находится дескриптор сегмента, из дескриптора берётся значение поля «база сегмента», база сегмента суммируется с 32-битным смещением, взятым из виртуального адреса, и получается 32-битный линейный адрес.
- 32-битный линейный адрес дробится на используется чтобы найти PDE/PTE и преобразовать этот самый линейный адрес в 32-битный физический адрес.
3-ий пункт выполняется только в случае включенного страничного режима, то есть взведённого флага PG в регистре CR0.
Физический адрес выставляется на шину адреса (то есть появляется на ножкам микросхемы) и используется для обращения (то есть контроллер памяти шлёт нужые сигналы непосредственно планкам памяти и микросхемам на них).
В Windows сегментные регистры прединициализированны, в них записаны селекторы предсозданных сегментов, причём база всех сегментов, кроме одного, установленна равной нулю.
Поэтому в Windows виртуальный адрес практически всегда численно равен линейному адресу. Исключение: при обращении к FS:[aabbccdd]. В этом случае линейный адрес не будет равен aabbccdd. Он будет равен сумме aabbccdd и базового адреса сегмента FS. Собственно, он всегда равен сумме, но в случае других сегметов база всегда нулевая.
То есть:
Виртуальный 48-битный адрес → Линейный 32-битный адрес → Физический 32-битный адрес.При этом:
- в системах, исповедующих flat-огранизацию, виртуальный адрес численно равен линейному.
- в системах, не используюзих страничную организацию (флаг PG снят) линейный адрес численно равен физическому
Преобразование виртуального адреса в линейный выглядит примерно так:
И ещё одна аналогичная картинка, иллюстрирующая преобразование адреса из линейного в физический:
(Две стрелки, приходящие в кружочек с крестом обозначают просто суммирование адресов)
На всём этапе преобразования адреса выполняются проверки битов-флагов (сначала в дескрипторе сегмента, к которому идёт обращение, потому в PDE-шке, потом в PTE-шке). Если с флагами что-то не так, возникает исключение, и ОС его обрабатывает. В 99 процентов случаев — без последствий для программы (то есть просто своппинг), но бывает, что и обращение к невыделенной странице или нарушение доступа к странице. Тогда, если обращение было из usermode, в UM-процессе генерируется исключение, которое может быть обработано SEH-хендлером, а если оно было в kernelmode — генерируется BSOD
Все эти преобразования и проверки флагов делаются при каждом обращении к флагом. Хотя, конечно, в современном процессоре всё кешируется, в том числе и результаты проверок и результаты преобразований — если с момента последнего преобразования адреса никто не правил PDE-шки PTE-шки, преобразование не делается заново, а берётся закешированный результат из
TLB (это не та TLB, о которой вы подумали).
Фух.
Всё это относится к обычному режиму (не используется PAE и AWE)
Я всё хочу написать в википедию статью на эту тему, с хорошими картинками, но никак нет времени.