После длительного затишья мы продолжаем изучать защищенный режим. В предыдущем выпуске был рассмотрен пример переключения процессора в защищенный режим БЕЗ использования страничной адресации. В данном выпуске будет рассмотрен код перевода проца в защищенный режим С использованием страничной адресации. По сути – код остался прежним, я лишь опишу изменения, которые необходимо внести.
Приступим. Следует иметь ввиду, что все изменения для включения страничной адресации производятся уже ПОСЛЕ ПЕРЕХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ !!! И еще - страницы у нас будут 4-килобайтными!!!
И вот они, эти изменения:
1. Подготовить каталог страниц
2. Заполнить таблицу страниц
3. Адрес каталога страниц поместить в регистр CR3
4. Включить страничную адресацию (установка бита 31 в регистре CR0)
THAT’S ALL!
Видишь, как все просто? А ты боялся!
Вот картинка (она уже у нас фигурировала), из которой видно для чего нужны эти 4 пункта:
Прежде, чем приступить, хочу вкратце рассказать, чего же мы будем творить. Вопрос в том, КАК НАГЛЯДНО ПОКАЗАТЬ РАЗНИЦУ МЕЖДУ СЕГМЕНТНОЙ И СЕГМЕНТНО-СТРАНИЧНОЙ адресацией? Оказывается, это не так-то уж и просто... Но вот как я попытаюсь этого добиться:
В предыдущем выпуске рассылки мы рассмотрели код, который выводит строку на экран, т.е. все свелось в конце концов к тому, что по адресу ES:0 вывелась надпись, где ES – селектор на предварительно подготовленный дескриптор, база которого равна 0B8000h (т.е. начало видеопамяти в текстовом режиме). Все однозначно и понятно. Теперь, как ты уже знаешь, при страничной адресации адрес 0B8000h препарируется на три части: старшие 10 бит – номер элемента в каталоге страниц, средние 10 бит – номер элемента в таблице страниц, и, наконец, младшие 12 бит – это СМЕЩЕНИЕ В СТРАНИЦЕ.
Недолго думая, можно умозаключить, что, попытавшись что либо записать по адресу 0B8000h при СТРАНИЧНОЙ адресации, мы не обнаружим там никакого начала видеопамяти, разумеется, если ПРЕДВАРИТЕЛЬНО (!) мы корректно не настроим каталог и таблицы страниц. Вот она, виртуальность! Вроде, вот же он: адрес начала видеопамяти! 0B8000h! У нас перед носом! Ан, нет... Это всего лишь иллюзия, т.к. этот адрес – не линейный, а виртуальный. Вот разбить его на три части, пройтись по каталогу и таблице страниц – вот тогда сформируется ОКОНЧАТЕЛЬНЫЙ АДРЕС (ВЫСТАВЛЯЕМЫЙ ПРОЦЕМ НА ШИНУ), который (при страницах в 4Кб) В ДАННОМ, КОНКРЕТНОМ СЛУЧАЕ (0B8000h) может принять значение от 0 до 1Мб!!! Надеюсь, понятно почему именно эти значения?
Поэтому вот оно, золотое правило: ПРИ СТРАНИЧНОЙ АДРЕСАЦИИ ЛИНЕЙНЫЙ АДРЕС ФОРМИРУЮТ ЭЛЕМЕНТЫ КАТАЛОГОВ И ТАБЛИЦ СТРАНИЦ, ну и конечно «проводником» по этим таблицам служит сам ВИРТУАЛЬНЫЙ адрес...
Значит, это все к чему? А к тому, что если представить все это дело НАОБОРОТ, то придем точно к такому же выводу! Т.е. я хочу сказать, что если 0B8000h – это на самом деле нечто совершенно иное, то с точно таким же успехом, ЛЮБОЙ ВИРТУАЛЬНЫЙ АДРЕС ПОСЛЕ ПРЕОБРАЗОВАНИЙ МОЖЕТ ПРЕВРАТИТЬСЯ В 0B8000!!! НАМ СТОИТ ТОЛЬКО ЗАХОТЕТЬ ЭТОГО! А что значит захотеть? А это значит, соответствующим образом заполнить элементы каталога и таблицы страниц, только и всего... На примере все станет совершенно ясно.
Вот что мы еще сделаем: вообще уберем дескриптор сегмента видеопамяти из таблицы GDT (итого у нас останется два дескриптора – кода и данных). Для чего – станет ясно ниже.
Итак, где-нибудь сразу после перехода в защ. режим и загрузки сегментных регистров соотв. селекторами нужно создать КАТАЛОГ СТРАНИЦ.
ENTRY_POINT:
; загрузим сегментные регистры селекторами на соответствующие дескрипторы:
mov AX,00010000b ; селектор на второй дескриптор (DATA_descr)
mov DS,AX ; в DS его
mov ES,AX ; и в ES его же
вот теперь создадим каталог страниц. Что это такое? Каталог страниц – это набор 32- разрядных записей (элементов); вообще, структура записей подробно представлена в седьмом выпуске рассылки, поэтому кто забыл – посмотрите.
Начало каталога страниц в оперативной памяти будет располагаться по адресу 1Мб (100000h).
Теперь секундочку внимания! В нашем примере единственное, что мы делаем полезного – выводим на экран некоторую надпись. По сути, при этом используется один единственный адрес – ES:ESI, или 0B8000h. Следовательно, в данном примере для демонстрации возможностей страничной адресации достаточно заполнить лишь ОДИН ЕДИНСТВЕННЫЙ элемент каталога страниц. Так и сделаем:
mov EDI,100000h ; начало каталога страниц (1Мб)
mov EAX,101007h ; это наш один единственный значащий элемент
stosd ; ES:[EDI] <- EAX
mov ECX,1023 ; остальные 1023 элементов
xor EAX,EAX
rep stosd ; забьем нулями все остальные элементы каталога
кстати, почему такой загадочный элемент – 101007h? Открываем формат элемента каталога страниц и все становится ясно. (не ленимся, не ленимся! Открываем 7 выпуск и смотрим на картинку). 7 (установлены младшие 3 бита адреса) – означает, что страница присутствует в оперативной памяти (бит P), доступна для чтения записи (бит R/W) и доступна с любого уровня привилегий (бит U/S). А что такое 1010? Не забыл, что младшие 12 бит для АДРЕСА таблицы страниц ВСЕГДА РАВНЫ нулю? Значит, это 1010000h, что соответствует 1Мб + 4Кб, а 4 Кб – потому что САМ каталог занимает столько.
все. С каталогом покончено. Теперь примемся за ТАБЛИЦУ СТРАНИЦ
mov EAX,00000007h ; первая запись – адрес нулевой страницы равен 0
mov ECX,1024 ; кол-во страниц в таблице
fill_page_table:
stosd ; запишем первый элемент
add EAX,1000h ; добавим 4 Кб
loop fill_page_table ; и повторим для всех элементов таблицы страниц
mov EAX,00100000h ; базовый адрес = 1 Мб
mov CR3,EAX ; в CR3 его! (база каталога страниц ВСЕГДА должна лежать в CR3)
; включить страничную адресацию
mov EAX,CR0
or EAX,80000000h
mov CR0,EAX
; а теперь изменить физический адрес страницы 12000h на 0B8000h
mov EAX,000B8007h
mov ES:00101000h+012h*4,EAX
вот здесь надо проникнуться всем существом. Чего это мы такого сотворили? А вот чего: мы, в таблице страниц, заменили адрес страницы, начало которой равно 12000h НА АДРЕС НАЧАЛА ВИДЕОПАМЯТИ (0B8000h)!!! Как это у нас получилось? Очень просто. АДРЕС НАЧАЛА ТАБЛИЦЫ СТРАНИЦ = 101000h, так? А сколько занимает один элемент? 4 байта!!! (32 бита). Поэтому 12*4 – это и есть ЗАПИСЬ ТАБЛИЦЫ СТРАНИЦ, которая указывает на страницу, начинающуюся по адресу 12000h!!!
А вот теперь демонстрация силы и мощи страничной адресации:
; вывод mes1 по стандартному адресу (начало видеопамяти 0B8000h)
mov EDI,0B8000h ; для команды movsw, EDI = начало видепамяти
mov ESI,PM_DATA
shl ESI,4
add ESI,offset mes1 ; ESI = адрес начала mes1
mov ECX,mes_len ; длина текста в ECX
rep movsw ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)
; вывод mes2 по НЕСТАНДАРТНОМУ АДРЕСУ 12000h:
mov EDI,0120A0h ; 12000h (уже можешь считать, что это 0B8000h) + A0h
mov ESI,PM_DATA
shl ESI,4
add ESI,offset mes2 ; ESI = адрес начала mes2
mov ECX,mes_len ; длина текста в ECX
rep movsw ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)
А теперь небольшое партийное задание: переделать прогу так, чтобы начало видеопамяти совпадало с адресом 66000h.
Ответы присылать на brokensword@mail.ru
Кто разберется и пришлет правильный ответ – тот проникся страничной адресацией ).
Напоследок – код всей проги (запускать ТОЛЬКО в реальном режиме :) Качать здесь: PM2.asm
; TASM:
; TASM /m PM.asm
; TLINK /x /3 PM.obj
; PM.exe
; MASM:
; ML /c PM.asm
; LINK PM.obj,,NUL,,,
; PM.exe
.386p ; разрешить привилегированные инструкции i386
; СЕГМЕНТ КОДА (для Real Mode)
; ---------------------------------------------------------------------------------------------------------
RM_CODE segment para public 'CODE' use16
assume CS:RM_CODE,SS:RM_STACK
@@start:
mov AX,03h
int 10h ; текстовый режим 80x25 + очистка экрана
; открываем линию А20 (для 32-х битной адресации):
in AL,92h
or AL,2
out 92h,AL
; вычисляем линейный адрес метки ENTRY_POINT (точка входа в защищенный режим):
xor EAX,EAX ; обнуляем регистра EAX
mov AX,PM_CODE ; AX = номер сегмента PM_CODE
shl EAX,4 ; EAX = линейный адрес PM_CODE
add EAX,offset ENTRY_POINT ; EAX = линейный адрес ENTRY_POINT
mov dword ptr ENTRY_OFF,EAX ; сохраняем его в переменной
; (кстати, подобный "трюк" называется SMC или Self Modyfing Code - самомодифицирующийся код)
; теперь надо вычислить линейный адрес GDT (для загрузки регистра GDTR):
xor EAX,EAX
mov AX,RM_CODE ; AX = номер сегмента RM_CODE
shl EAX,4 ; EAX = линейный адрес RM_CODE
add AX,offset GDT ; теперь EAX = линейный адрес GDT
; линейный адрес GDT кладем в заранее подготовленную переменную:
mov dword ptr GDTR+2,EAX
; а подобный трюк назвать SMC уже нельзя, потому как по сути мы модифицируем данные :)
; собственно, загрузка регистра GDTR:
lgdt fword ptr GDTR
; запрет маскируемых прерываний:
cli
; запрет немаскируемых прерываний:
in AL,70h
or AL,80h
out 70h,AL
; переключение в защищенный режим:
mov EAX,CR0
or AL,1
mov CR0,EAX
; загрузить новый селектор в регистр CS
db 66h ; префикс изменения разрядности операнда
db 0EAh ; опкод команды JMP FAR
ENTRY_OFF dd ? ; 32-битное смещение
dw 00001000b ; селектор первого дескриптора (CODE_descr)
; ТАБЛИЦА ГЛОБАЛЬНЫХ ДЕСКРИПТОРОВ:
GDT:
; нулевой дескриптор (обязательно должен присутствовать в GDT!):
NULL_descr db 8 dup(0)
CODE_descr db 0FFh,0FFh,00h,00h,00h,10011010b,11001111b,00h
DATA_descr db 0FFh,0FFh,00h,00h,00h,10010010b,11001111b,00h
GDT_size equ $-GDT ; размер GDT
GDTR dw GDT_size-1 ; 16-битный лимит GDT
dd ? ; здесь будет 32-битный линейный адрес GDT
RM_CODE ends
; ---------------------------------------------------------------------------------------------------------
; СЕГМЕНТ СТЕКА (для Real Mode)
; ---------------------------------------------------------------------------------------------------------
RM_STACK segment para stack 'STACK' use16
db 100h dup(?) ; 256 байт под стек - это даже много
RM_STACK ends
; ---------------------------------------------------------------------------------------------------------
; СЕГМЕНТ КОДА (для Protected Mode)
; ---------------------------------------------------------------------------------------------------------
PM_CODE segment para public 'CODE' use32
assume CS:PM_CODE,DS:PM_DATA
ENTRY_POINT:
; загрузим сегментные регистры селекторами на соответствующие дескрипторы:
mov AX,00010000b ; селектор на второй дескриптор (DATA_descr)
mov DS,AX ; в DS его
mov ES,AX ; его же - в ES
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; создать каталог страниц
mov EDI,00100000h ; его физический адрес - 1 Мб
mov EAX,00101007h ; адрес таблицы 0 = 1 Мб + 4 Кб
stosd ; записать первый элемент каталога
mov ECX,1023 ; остальные элементы каталога -
xor EAX,EAX ; нули
rep stosd
; заполнить таблицу страниц 0
mov EAX,00000007h ; 0 - адрес страницы 0
mov ECX,1024 ; число страниц в таблице
fill_page_table:
stosd ; записать элемент таблицы
add EAX,00001000h ; добавить к адресу 4096 байтов
loop fill_page_table ; и повторить для всех элементов
; поместить адрес каталога страниц в CR3
mov EAX,00100000h ; базовый адрес = 1 Мб
mov CR3,EAX
; включить страничную адресацию,
mov EAX,CR0
or EAX,80000000h
mov CR0,EAX
; а теперь изменить физический адрес страницы 12000h на 0B8000h
mov EAX,000B8007h
mov ES:00101000h+012h*4,EAX
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; вывод mes1 по стандартному адресу (начало видеопамяти 0B8000h)
mov EDI,0B8000h ; для команды movsw, EDI = начало видепамяти
mov ESI,PM_DATA
shl ESI,4
add ESI,offset mes1 ; ESI = адрес начала mes1
mov ECX,mes_len ; длина текста в ECX
rep movsw ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)
; вывод mes2 по НЕСТАНДАРТНОМУ АДРЕСУ 12000h:
mov EDI,0120A0h ; 12000h (уже можешь считать, что это 0B8000h) + A0h
mov ESI,PM_DATA
shl ESI,4
add ESI,offset mes2 ; ESI = адрес начала mes2
mov ECX,mes_len ; длина текста в ECX
rep movsw ; DS:ESI (наше сообщение) -> ES:12000h (типа видеопамять)
jmp $ ; погружаемся в вечный цикл
PM_CODE ends
; ---------------------------------------------------------------------------------------------------------
; СЕГМЕНТ ДАННЫХ (для Protected Mode)
; ---------------------------------------------------------------------------------------------------------
PM_DATA segment para public 'DATA' use32
assume CS:PM_DATA
; сообщение, которое мы будем выводить на экран (оформим его в виде блока повторений irpc):
mes1:
irpc mes1, This string was outputted to standart adress 0B8000h...
db '&mes1&',0Dh
endm
mes2:
irpc mes2, And this one - to dummy adress 0120A0h. Cool? Now press RESET...
db '&mes2&',0Bh
endm
mes_len equ 66 ; длина в байтах
PM_DATA ends
; ---------------------------------------------------------------------------------------------------------
end @@start
|