Pull to refresh

Учим систему страничной адресации и обработке прерываний

Reading time 5 min
Views 7.5K
Приветствую. Сегодня поговорим обо всём понемногу. Введём в нашу наработку 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. И структура такова:

image

Здесь назначение полей ясно (тем более, что некоторые из них встречались нам ещё в 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. Конечно, можно использовать и APICAdvanced 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

IDTR dw IDT_size-1
dd IDT+10000h


Так, а зачем здесь пишем and’ы и shr’ы? – спросите вы. Вот тут трюк: мы же расположим весь код для PM за org’ом (смотрим код в предыдущей статье), в том числе и interrupt handlers… вот здесь и есть уловка: мы составляем 32-битный адрес из 2-х половинок, имея на руках лишь адрес обработчика. Вообще в этом нет ничего магического, просто нужно понимать, что за значение будет в этом двойном слове.
Теперь возникает вопрос, а почему же на местах многих gate’ов нули? А вот почему – совершенно не нужно писать все обработчики, ведь это, мягко говоря тяжело. Проще написать обработчик #GP, ведь, не найдя gate для прерывания, процессор генерирует пресловутое General Protection Fault.
Обработчики аппаратных прерываний вольны делать всё, что им вздумается, но должны сбрасывать флаг наличия прерывания – слать сигнал EOIEnd Of Interrupt (мы же так сконфигурировали PIC, верно?).
К примеру вот так:

mov al, 20h
out 20h, al


Давайте напишем простой обработчик для таймера:

timer:
push ax
mov al, 20h
out 20h, al
pop ax
jmp int_EOI

int_EOI: ;вот здесь посылаем и Master'у и Slave'у EOI
push ax
mov al,20h
out 20h,al
out 0a0h,al
pop ax
iretd ;возврат из прерывания


int_EOI удобно использовать для всех обработчиков прерываний.

Ладно, теперь немного про исключения. Когда они происходят стек выглядит так:
image

Здесь error_code выталкиваем из стека и работаем с ним. Остальным займётся инструкция iretd.

Замечу, что тут содержимое ещё зависит от того, переключали ли мы ring’ или нет.
Структура error_code:

image

Где:
1) EXT — показывает, что сбой произошёл в обработчике прерывания или исключения.
2) IDT — когда установлен, показывает, что поля индекса относится к IDT.
3) TI — когда бит IDT не установлен, показывает, что икать нужно в GDT или LDT.
4) Segment Selector Index — показывает номер дескриптора (в GDT и LDT) или gate в IDT.

Вообще индекс обработчика исключения полезная штука. Он нам пригодится, когда наша поделка станет побольше.

Теперь давайте скомпануем код. Я схитрю: предоставлю вам возможность потренироваться. Давайте вы попробуете самостоятельно написать рабочий код. Функции мы уже написали.
Дам несколько 'подсказок':
1) IDT расположите до org'a (короче вместе с GDT).
2) IDTR загружаем до перехода в PM.
3) Обработчики прерываний распологаем за org'ом.
4) К текстовой видеопамяти можно обращаться как раньше.
5) Не забываем разрешить ВСЕ прерывания после того, как в PM проинициализируем PIC!
6) Paging инициализируем уже в PM.
И ещё, напишите простой обработчик для клавы.

Если будут проблемы — обращайтесь. До следующего поста.
****Исправление****
Функция set_pages сперва была написана неправильно т.к там описывалась всего 1 таблица, вместо 8. Код исправлен.
Tags:
Hubs:
+18
Comments 14
Comments Comments 14

Articles