Virtual ;теперь, фактически, забиваем пространство до конца сегмента
rb 10000h-$;
end virtual
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PM32 Entry;;;;;;;;;;;;;;;;;;;
use32
org $+10000h;вот для чего: в PM мы работаем с Flat-сегментами, и если мы оставим код ;для PM перед org’ом, то ;внутрисегментный адрес не будет совпадать с Flat адресом. Так вот.
Startup32: ;точка входа в PM
mov ax,10h ;здесь пихаем селекторы. Зачастую (! не забываем про порядковый номер в
mov es,ax ;таблице) селектор сегмент кода - 08h. данных - 10h, видеосегмент - 18h
mov ds,ax
mov fs,ax
mov ss,ax
mov esp,10000h;стек
mov ax,18h
mov gs,ax
mov esi,hi_string ;покажем, что мы удачно перешли
call print
jmp $
;ESI - адрес строки
print:
pushad
xor ebx,ebx
mov ah,07h;атрибут
puts:
mov al,[esi+ebx]
mov [gs:(ebx*2)],ax
inc ebx
test al,al
jnz puts
popad
ret
hi_string db ‘Welcome to PM, dude’,0
Что мы сделали? Загрузчик нас успешно загрузил по адресу 1000h:0, откуда мы и продолжили выполнение. Сначала включили А20, запретили все прерывания, загрузили в GDTR подходящее значение, прыгнули на метку входа. Замечу, что прыгали мы на
jmp fword 08h:Startup32
Т.е 08h — селектор дескриптора кода. Привыкайте.
Теперь как сие чудо запустить. Лично я пользуюсь WinImage и VirtualBox. Запихиваем загрузчик в бутсектор дискеты и кладём .bin’овский файл в корень. Сохраняем в .vfd, прописываем путь к образу дискеты в свойствах виртуальной машины, запускаем и видим результат.
Введём в нашу наработку paging, разберёмся с прерываниями и их видами. Напишем несколько функций, добавим сие в код из предыдущего поста.
1) Paging.
Все, наверно, уже покопались в манах и знают, что за лепота этот paging. Вырываемся за пределы 1 Мб и можем адресовать все 4 гигабайта оперативки. Но зачем нам в нашей скромной поделке 4 Гб (тем более что не у всех есть столько RAM’а). Нам вполне будет достаточно 32Мб (ну, это тоже очень много, но давайте помечтаем). Итак, будем использовать 4Кб’айтные страницы. Так, теперь считаем – 1024 страницы описывают 4Мб. Нам, значит, нужно всего-то 1024*8 страниц. Иными словами нужно завести 8 таблиц и 1 каталог. Ещё, для удобства, страницы будут тождественными т.е логический и физический адреса совпадают. Теперь, давайте напишем функцию, которая сформирует всё необходимое.
set_pages:
.set_cat:
mov edi,100000h;базовый адрес директории
mov eax,101007h;базовый адрес таблицы страниц и флаги
mov cx,8 ;8*4Мб=32Мб
.fill_cat_usef: ;опишем таблицы страниц
stosd
add eax,1000h
loop .fill_cat_usef
mov cx,1016 ;а остальное забьём нулями
xor eax,eax
rep stosd
mov eax,00000007h
mov ecx,1024*8;32Mb
.fill_page_table: ;теперь опишем страницы
stosd
add eax,1000h
loop .fill_page_table
;End;
mov eax,00100000h;1 Mb;и установим базовый адрес первого каталога в cr3
mov cr3,eax
mov eax,cr0
or eax,80000000h
mov cr0,eax
ret
Возможно у вас возникнет вопрос, зачем же забивать нулями другие каталоги? А не забываем, что располагать таблицы, как и каталоги, можно по адресам, кратным 1000h.
2) Interrupts.
И с прерываниями PM преподносит сюрпризы: нет больше 4-ых байтных векторов прерываний, как в RM. На их место пришли Interrupt Gates (для нас сейчас важны именно они), Trap Gates, Task Gates – 64-битные структуры, находящиеся в IDT. И структура такова:
Здесь назначение полей ясно (тем более, что некоторые из них встречались нам ещё в segment descriptor’ах). Мы пока рассмотрим только Interrupt Gates. IDT нужно составить до перехода в PM, загрузить размер и смещение в регистр IDTR (его структура аналогична структуре GDTR) командой lidt. Кстати, IDT – сегмент (скажем так, антипод GDT, которая просто в линейном адресном пространстве находится) т.е она располагается в определённом сегменте. Например, если вы до перехода в PM описали, к примеру, 4Кб памяти, а располагаете IDT за этими пределами, и разрешите прерывания после перехода в PM, то всё рухнет.
Еще пара замечаний: Intel зарезервировала прерывания 0-31. Туда нужно класть обработчики для этих exception’ов. Среди них ошибка деления на 0 (#DE), исключение общей защиты #GP и другие не менее весёлые вещи. Поэтому нужно базовый адрес прерываний сместить т.е сделать так, чтобы IRQ0 занимало 32 позицию и.т.д. Сразу оговорюсь, что использовать будем старую-добрую микросхему i8259a. Конечно, можно использовать и APIC – Advanced Programmable Interrupt Controller (кстати, его нужно ручками включить), но нам просто не придётся пользоваться преимуществами такового (расширение кол-ва аппаратных прерываний, приспособленность под многопроцессорность и.т.д). Надеюсь, все помнят, что такое ICW (Initialization Control Word) и OCW (Operation Control Word)? Давайте повторим, что для чего используется (структуру приводить не буду):
ICW:
1) Определение особенности последовательности приказов.
2) Определение базового адреса (вот о чём я говорил).
3) Связь контроллеров.
4) Дополнительные особенности обработки прерываний.
OCW:
1) Управление регистром масок IMR.
2) Управление приоритетом.
3) Общее управление контроллером.
Процедура инициализации выглядит следующим образом:
Init_PIC:
mov al,11h;ICW1 – прерывание по перепаду сигнала, схемы подключены каскадно
out 20h,al;Мастеру
out 0A0h,al;Слэйву
mov al,20h;ICW2;базовый адрес (он абстрактный! Не путайте с адресом в RAM)
out 21h,al;Мастеру одно значение
mov al,28h;Слэйву другое
out 0A1h,al
mov al,04h;ICW3 – слэйв к входу IRQ2 подключён
out 21h,al
mov al,02h
out 0A1h,al
mov al,11h;ICW4 – сбрасываем флаг наличия прерывания вручную и используем камень Pentium
out 21h,al
mov al,01h
out 0A1h,al
ret
Теперь построим IDT:
IDT:
dd 0,0; 0
dd 0,0 ; 1
dd 0,0
dd 0,0 ; 3
;…. Убрал для краткости!
dd 0,0 ; 12
dw GP_handler and 0FFFFh,08h, 1000111000000000b, GP_handler shr 16 ; 13 #GP
dd 0,0 ; 14
;…. Я просто для краткости так написал. Забейте хоть тем же dup’ом это свободное место,
;чтоб не писать наскучившее dd 0,0
dd 0,0 ; 30
dd 0,0 ; 31;Здесь заканчиваются зарезервированные Intel номера ;gate’ов
dw timer and 0FFFFh, 08h, 1000111000000000b, timer shr 16 ; IRQ 0 - системный таймер
dw keyboard and 0FFFFh , 08h, 1000111000000000b, keyboard shr 16 ; IRQ 1 - клавиатура - ;предлагаю вам самим написать обработчик
dd 0,0;IRQ2
dd 0,0;IRQ3
dd 0,0;IRQ4
dd 0,0;IRQ5
dd 0,0;IRQ6
label IDT_size at $-IDT
