§ Включение страничного преобразования

Прежде, чем включить в программе страничное преобразование, нужно выполнить следующие действия:
  • Подготовить описания всех используемых в программе страниц, таблиц страниц и каталога страниц;
  • Загрузить адрес начала (базовый адрес) каталога страниц в CR3;
  • Разрешить в процессоре страничное преобразование.

§ Подготовка описаний страниц

Мы будем использовать формат страницы в следующем виде:

Формат элемента PTE (элемент таблицы страниц) будет в следующем виде:
Бит     Описание
0:      P = если страница 1=определена; 2=нет
1:      R/W = если страницу можно 1=писать и читать, 0=только читать
2:      U/S = 0, т.к. наши примеры пока работают только на нулевом уровне привилегий
3..11:  0 (не используются)
12..31: Старшие 20 бит физического адреса, на который отображена страница.
Как видите, нужно будет устанавливать только биты 0 и 1 в элементах PDE и PTE, что мы и будем делать примерно следующим образом:
1mov	eax, page_address    ; Загружаем в EAX выравненный на границу 4Кб адрес страницы.
2mov	al, 3                ; Устанавливаем биты 0 и 1 (все остальные должны быть равны 0).
3mov	PTE_or_PDE, eax      ; Записываем готовый элемент PDE или PTE.
В примере к этой главе мы будем использовать только первый мегабайт адресного пространства, чтобы не перегружать исходник лишними деталями; первый мегабайт гарантированно есть в любом используемом сейчас компьютере и поэтому не нужно дополнительно определять количество присутствующей физической памяти. В нашем примере мы будем использовать только один элемент каталога страниц - PDE 0, который определит одну таблицу страниц. Этого достаточно, т.к. таблица страниц может покрыть до 4Мб адресного пространства.
В таблице страниц мы определим только первые 256 страниц (т.е. элементов PTE) и зададим им тождественное отображение. Это значит, что линейные адреса будут совпадать с физическими и для элемента PTE 0 базовый адрес будет равен 0000 0000h, для PTE 1 - 0000 1000h, PTE 2 - 0000 2000h и т.д.
256 страниц по 4Кб каждая образуют один мегабайт. Все остальные элементы PTE мы просто очищаем нулями - главное, чтобы бит P (0-й бит в каждом PTE) был равен 0 для неопределённых страниц - тогда процессор не допустит обращения к ним и сгенерирует исключения страничного нарушения.
Все операции по установке элементов PDE и PTE выполняет процедура "set_pages". Эта процедура сбрасывает (т.е. записывает в них нули) все элементы PDE и все PTE в первой и единственной таблице страниц. Затем она устанавливает первый элемент каталога страниц (PDE 0), в котором указывает адрес таблицы страниц и, наконец, устанавливает первые 256 PTE в таблице страниц. В качестве указателя на каталог страниц используется содержимое переменной Page_Directory, которая записывается другой процедурой set_PDBR (о ней - см. дальше).
1set_pages	proc	near
2;  Установка каталога страниц, PDE 0 и тождественного отображения страниц для
3;  первого мегабайта.
4
5    mov  ax, Data_selector
6    mov  es, ax
7    mov  di, Page_Directory ; ES:DI = указатель на каталог страниц.
8    xor	 eax, eax
9    mov  cx, 1024 + 1024    ; Очистка 2-х страниц памяти (по 4Кб).
10    cld
11    push di
12    rep  stosd      ; Очищаем все элементы каталога страниц
13                    ;  (PDE 0 .. PDE 1023) и всю первую таблицу
14                    ;  страниц (соответствующую элементу PDE 0).
15    pop  di
16    mov  eax, cr3   ; EAX = адрес начала каталога страниц.
17    add  eax, 4096  ; Первая таблица страниц (PDE 0) будет
18                    ;  располагаться сразу за каталогом страниц.
19
20    mov	 al, 3      ; Флаги PDE: P=1, R/W = 1
21    stosd           ; Записали PDE 0
22    add	 di,4096-4  ; Переходим к таблице страниц
23    xor	 eax,eax    ; Начнём с адреса 0
24    mov	 al,3       ; Флаги PTE: P=1, R/W = 1
25    mov	 cx,256     ; Установим 256 страниц (они покроют первый мегабайт физической памяти)
26
27@@: stosd
28    add   eax,4096
29    loop  @b
30	ret
31endp

§ Загрузка базового адреса каталога страниц в CR3

Вообще говоря, загрузка регистра CR3 должна проводится после того, как будет определена вся используемая структура страниц, но на самом деле не имеет значения, когда вы запишите в CR3 базовый адрес каталога страниц - до тех пор, пока в процессоре не будет разрешено страничное преобразование, этот регистр также не будет использоваться.
В нашем примере установка CR3 производится почти в самом начале программы, процедурой "set_PDBR". Дело в том, что каталог страниц и любые таблицы страниц должны находиться целиком в физической странице памяти, т.е. по адресам, кратным 4Кб. Определить эти адреса гораздо проще находясь в режиме реальных адресов, используя значения сегментных регистров - в защищённом режиме понадобилось бы обратиться к GDT, чтобы получить адрес текущего сегмента данных, либо вводить дополнительные переменные.
Процедура "set_PDBR" вычисляет адрес каталога страниц как адрес ближайшей к концу программы свободной физической страницы памяти и помещает его в CR3:
1set_PDBR    proc    near
2
3    ; Установка значения в PDBR
4    xor    eax, eax
5    mov    edx, eax
6    mov    ax, ds
7    shl    eax, 4
8    mov    ebx, eax       ; Сохраняем в EBX физический адрес начала
9                          ;  сегмента данных.
10    lea    dx, Last_label ; Last_label - это последняя метка программы.
11    add    eax, edx       ; EAX = физический адрес последней метки.
12
13    mov    dx, ax
14    and    ax, 0f000h     ; Сбрасываем младшие 12 бит адреса.
15    sub    dx, ax
16    cmp    dx, 0          ; Адрес последней метки уже был выравнен
17                          ; на границу 4Кб ?
18    je     spdbr_1
19    add    ah,10h         ; Если нет, то выравниваем его.
20
21spdbr_1:
22
23    mov    cr3,eax        ; Загрузка адреса в CR3 (т.е. в PDBR).
24                          ; По этому адресу будет размещён каталог страниц.
25    sub    eax,ebx        ; EAX = AX = смещение каталога страниц
26                          ;  относительно начала сегмента данных.
27    mov    Page_Directory, ax
28    ret
29endp
Примечание:
Команда add ah, 10h здесь используется для выравнивания адреса на границу 4Кб. Эта команда эквивалентна команде add eax,1000h, но, так как в EAX уже находится адрес со сброшенными 12 младшими битами, а добавляем мы dw-число, то код можно сократить add ax,1000h или add ah,10h. Всё-таки, мы пишем на Ассемблере...

§ Разрешение в процессоре страничного преобразования

Для того, чтобы включить или, другими словами, разрешить в процессоре страничное преобразование, необходимо установить бит PG (это 31-й бит) в регистре управления CR0.
После того, как это будет сделано, нужно выполнить команду перехода - она заставит процессор сбросит конвейер и начать выборку команд уже используя механизм трансляции страниц.
Перед возвратом программы в режим реальных адресов нужно запретить страничное преобразование - сбросить бит PG в CR0 и выполнить команду перехода - в этом случае она заставит процессор при выборке команд не использовать содержимое буферов TLB и программа корректно выйдет в R-Mode.
В нашем примере тождественное отображение для всех страниц устанавливается не потому, что это просто, а потому, что это - надёжно. Если вы включите страничное отображение и какая-либо часть программы (код, стек, данные) будет описываться страницами с не тождественным отображением, то программа может "уйти" по нормальному линейному адресу, указанному в программе на совершенно другой физический адрес, что в любом случае приведёт к нестабильности программы и скорее всего - к зависанию или сбросу процессора.
Для разрешения страничного преобразования в наших примерах будет использоваться процедура "set_paging", для запрещения - "reset_paging":
1set_paging:   ; Включение страничного преобразования
2
3    mov    eax, cr0
4    bts    eax, 31        ; Устанавливаем 31-й бит (PG)
5    mov    cr0, eax
6    jmp    @f
7@@: ret
8
9reset_paging: ; Запрещение страничного преобразования
10
11    mov    eax, cr0
12    btc    eax, 31        ; Сбрасываем 31-й бит (PG)
13    mov    cr0, eax
14    jmp    @f
15@@: ret
В примере оставлен подсчёт времени - теперь только до 2-х секунд. Результатом работы страничного преобразования этого примера есть то, что он работает. Это значит, что страничное отображение было установлено правильно и все элементы PTE и элемент PDE хранят правильные адреса и флаги; в противном случае, процессор попросту зависнет либо произойдёт аппаратный сброс, так как в программе разрешены прерывания.
И всё же изменения в работе примера есть - число, которое останется на экране будет отличаться от того, которое там осталось, если бы вы закомментировали вызов процедуры "set_paging". Оно - больше (на 2-3 процента, но больше). Это результат работы страничного отображения - работают буферы TLB, кэшируя обращения к памяти (TLB - это отдельная тема).
Ещё больший выигрыш в производительности видно с появлением мультизадачности, когда процессору приходится часто пропускать через себя большие объёмы данных.