![]() |
Прерывания в защищенном режиме процессора Ia-32
Обработка прерываний в защищенном режиме процессора. Перевод в защищенный режим и обратно. Написание простого загрузчика.
Вообщем-то на написание этой статьи меня толкнула незавершённость цикла статей http://www.wasm.ru/series.php?sid=20 "Процессор Intel в защищенном режиме". К сожалению, автор не успел рассмотреть детально сам процесс переключения режимов, смены адресации, а так же обработку прерываний, без которой невозможна полноценная работа программы в защищенном режиме. Писать мы будем код для транслятора FASM, сгенерируем чистый бинарный файл и используем его как образ загрузочной дискеты в эмуляторе Bochs или запишем на дискету и загрузим с нее реальный компьютер. После Power-On-Self-Test процессор генерирует прерывание 19h, обработчик которого управляет дальнейшим ходом загрузки. Он находит первый (в порядке приоритетов, устанавливаемых в BIOS Setup) загрузочный диск, считывает его первый сектор по линейному адресу 07C00 и передает ему управление. Поскольку процессор при загрузке работает в реальном режиме с 16битной адресацией, нашей задачей будет переключение процессора в защищенный режим с 32битной адресацией, установка обработчиков прерываний и считывание символов с клавиатуры. Так же мы рассмотрим процесс переключения из защищенного режима обратно в реальный. Итак, сначала некоторые подготовительные действия. Поскольку наше тело загрузят по адресу 7C00 и мы вряд ли уместимся в пределы одного сектора (512 байт), надо обеспечить загрузку всего остального кода с диска. Этот код приведу без дополнительных пояснений, потому что он не совсем в тему нашей статьи: Код:
ORG 0x7C00Далее нам необходимо включить отключенные (опять же для совместимости) адресные линии, потому что после включения компьютера функционируют только адресные линии A0-A19. Для использования полноценной 32битной адресации нам нужно включить адресную линию A20, установкой бита 1 на порту ввода-вывода 92h: Код:
; открываем адресную линию A20Код:
; запрет всех прерыванийВ реальном режиме база IDTR = 00000h, а лимит - 3FFh (размер 400h байт минус еденица). По адресу 00000 находится так называемая таблица векторов прерываний (Interrupt Vector Table), состоящая из 256 векторов. Каждый вектор содержит смещение и сегмент своего обработчика. Обе компоненты занимают 2 байта, таким образом общий размер таблицы составляет 256*2*2 = 1024 = 400h байт. В защищенном режиме дело обстоит совершенно по-другому. IDTR должен указывать на так называемую дескрипторную таблицу прерываний (Interrupt Descriptor Table, IDT), состоящую из 8байтных дескрипторов для каждого прерывания, которая может содержать шлюзы задачи, прерывания и ловушки. Мы рассмотрим только шлюз прерывания. Шлюз прерывания описывается следующей структурой: http://gr8.cih.ms/uploads/idt.png Как нетрудно заметить, его формат напоминает дескриптор из GDT/LDT, но есть некоторые изменения. Первое и последнее слова дескриптора шлюза прерывания содержат 32битный адрес обработчика прерывания (зеленое поле "смещение" на картинке). Второе слово содержит селектор сегмента кода, где находится код обработчика. Из дескрипторов сегмента унаследованы только следующие биты: P (Present) - бит присутствия. Если =1, прерывание обрабатывается, если =0 генерируется исключение общей защиты. DPL (Descriptor Privilege Level) - уровень привилегий, о нем позже. D - разрядность. При генерации прерывания происходит следующее. Из IDTR извлекается база таблицы дескрипторов прерываний. В этой таблице по номеру прерывания находится дескриптор шлюза прерывания. Если его бит Present сброшен, генерируется исключение общей защиты. Если текущий уровень привилегий отличается от уровня привилегий обработчика, происходит переключение стека и в стеке обработчика сохраняется указатель на стек прерванной задачи (SS и ESP). В стек помещаются регистры EFLAGS, CS, EIP. Для некоторых исключений последним в стек помещается еще и код ошибки, который, кстати, должен вытолкнуть обработчик исключения после обработки. Очищается бит TF, для программного прерывания или исключения сбрасываются биты VM, RF и NT. При вызове обработчика через шлюз прерывания очищается бит IF, блокируя дальнейшие маскируемые аппаратные прерывания. После обработки прерывания обработчик должен вытолкнуть из стека код ошибки, если он там есть, и выполнить инструкцию IRETD, которая восстанавливает регистр флагов из стека (поле IOPL меняется, если CPL=0, IF меняется, если CPL<=IOPL). Если уровень привилегий прерванной задачи не равен уровню привилегий обработчика, выталкиваются регистры SS и ESP (обратное восстановление стека). При вызове прерывания действуют механизмы защиты: - не позволяется передача управления к менее привилегированному коду, если DPL сегмента кода обработчика больше, чем CPL - в отличие от обычной передачи управления, не проверяется поле RPL селектора - поле DPL шлюза прерывания проверяется только при генерации программного прерывания (INT, INT3, INTO). Для исключений и аппаратных прерываний оно игнорируется Мы создадим таблицу прерываний только из 24 записей. Процессору, грубо говоря, наплевать, что их количество не равно 256. Просто при вызове неопределенных в ней прерываний будет исключение общей защиты. Мы определим обработчики для следующих прерываний: 1 - это у нас будет системный сервис вывода строки на экран =) будет выводить с текущего места ASCIIZ-строку, адрес которой лежит в ESI 8 - IRQ0 - системный таймер. будем по тику таймера менять буковку на экране (смотрится прикольно ;)) 9 - IRQ1 - клавиатура. мы будем отображать символы на экране один за одним, не различая регистр и не воспринимая функциональные клавиши, за одним исключением. Когда будет нажата клавиша <Esc>, мы попробуем переключиться в реальный режим, вызвать прерывание 10h BIOS с кодом AH=3 для очистки экрана и вернуться обратно в защищенный. 10-12,14-23 - IRQ2-4,6-15 забьем обработчиком, который просто будет посылать сигнал End-Of-Interrupt (EOI) на Master и Slave контроллеры прерываний и ничего полезного делать не будет. 13 - обработчик исключения общей защиты #GP. Покажем строчку "** GENERAL PROTECTION FAULT **" на экране Вот такие нехитрые обработчики. Теперь самое время составить таблицу дескрипторов прерываний для нашего загрузчика: Код:
; Interrupt Descriptor TableВернемся к загрузке. Мы пока что только разрешили адресную линию A20. Самое время загрузить GDTR и IDTR: Код:
; загрузка GDTRКод:
; переключение в PMСамое время перезагрузить дескриптор CS командой дальнего джампа и перейти, наконец, в 32битный режим: Код:
; загружаем новый селектор в CSНо мы поменяли только регистр CS, в остальные сегментные регистры так и содержат до сих пор старые значения. Пора бы и их переинициализировать селекторами новых сегментов: Код:
use32Код:
; разрешаем аппаратные прерывания и NMIКод:
; выводим строкуКод:
; переходим на 3 строчкуКод:
;Далее обработчик #GP - покажем злостные ругательства и возвратим управление. Стоит заметить, что управление возвращается на ту же инструкцию, которая и вызвала исключение. Не забываем так же вытолкнуть из стека 4х байтный код ошибки. Код:
;Код:
;Рассмотрим подробнее аппаратные прерывания. В компьютере есть программируемый контроллер прерываний, котогрый тесно взаимосвязан с процессором: http://gr8.cih.ms/uploads/apic1.png При возникновении аппаратного прерывания инициируется выход #INT контроллера. Он напрямую соединен с входом #INTR процессора. Если флаг IF=0, прерывание отбрасыватеся. Процессор опрашивает вход #INTR после выполнения каждой инструкции. Как только обнаруживается сигнал, процессор сразу же подтверждает прерывание через выход #INTA. Контроллер прерываний принимает сигнал INTA и выставляет на шину данных значние номера прерывания. Процессор считывает номер прерывания и входит в прерывание по описанной выше схеме. Контроллер прерываний 8259A имеет восемь входов IRQ0-IRQ7, открытых для внешних источников, выход INT и вход INTA, соединенные с #INTR и #INTA входом и выходом процессора соответственно. При получении внешнего прерывания на шине данных формируется номер прерывания из суммы IRQ-номера входа и некоторого базового значения, которое обычно равно восьми (таким образом, при получении сигнала на входе IRQ0 генерируется прерывания 8, IRQ1 - 9 и так далее). При поступлении нескольких заявок от разных источников они обрабатываются по порядку начиная с меньшего номера IRQ. Можно также выборочно заблокировать некоторые заявки от отдельных IRQ-входов. Блокировку, или маскирование заявок, а так же выбор заявки с наибольним приоритетом обеспечивают три байтовых регистра контроллера - interrupt Mask register (iMr), interrupt Request register (iRr), interrupt Service register (iSr) и арбитр приоритетов Page Resolver (PR). Каждый вход IRQ блокируется отдельным битом регистра масок iMr. Если прерывания на входе IRQn разрешены, бит n регистра iMr сброшен. Регистр маски iMr подключен к порту 21h процессора. Пусть поступление запросов на вход IRQn разрешено - бит n регистра iMr сброшен. Бит n регистра запросов на прерывание iRr установится, когда придет сигнал на вход IRQn. Арбитр приоритетов PR по значению регистра iSr принимает решение о возможности обслуживания запроса. Заявка может быть принята к обслуживанию, если в регистре iSr не зафиксировано заявок с равным или большим номером. Если все нормально, контроллер выставляет сигнал INT. Заявка принимается к обслуживанию при получении сигнала INTA от процессора, на шину данных выставляется номер прерывания, бит n регистра iRr сбрасывается, бит n регистра iSr устанавливается. Установленный бит n регистра iSr теперь блокирует прерывания от входов с номерами большими либо равными n. Блокировку необходимо снять вручную в процедуре обсдуживания прерывания сбросом бита x в регистре iSr, который подключен к порту 20h (нужно записать значение 3Nh, где N - номер входа). На практике обычно пользуются командой неопределенного сброса, реализуемой посылкой в порт 20h значения 20h. По этой команде в iSr сбрасывается заявка с наименьшим номером - которая обслуживается в данный момент как наиболее приоритетная. 8 входов слишком мало, поэтому чаще всего используются два контроллера прерываний 8259A - так называемые ведомый контроллер, выход INT которого подключен к входу IRQ2 ведущего контроллера, который уже в свою очередь общается с процессором. Это называется каскадным включением контроллеров прерываний. Регистр iMr ведомого контроллера доступен через порт 0a1h, а регистр iSr - через 0a0h. Теперь стало ясно, как должен выглядеть минимальный обработчик внешних IRQ прерываний - посылка неопределенного сброса контроллеру прерываний обоим контроллерам: Код:
;Теперь рассмотрим обработчик прерывания IRQ1 от клавиатуры. Прерывание IRQ1 генерируется контроллером клавиатуры каждый раз при нажатии клавиши. Обработчику клавиатуры скан-код считанной клавиши доступен для чтения через порт 060h. Скан-код нужно преобразовать в соответствующий ему ASCII-код символа (если он печатаем) и отобразить на экране. Преобразование произведем по следующей таблице: Код:
; Таблица преобразования печатаемых скан-кодов в ASCIIПоскольку нажатие клавиш Shift и Caps Lock мы не обрабатываем, поэтому регистр букв у нас различаться не будет. В нашем обработчике нам нужно проверить, нажата ли клавиша <Esc> (Её скан-код равен еденице). Если это действительно так, то вызовем процедуру переключения в реальный режим и генерации там прерывания 10h для очистки экрана. Если это не так, отобразим символ на экране. В любом случае нужно перед сбросом заявки на прерывание в контроллере прерываний послать подтверздение обработки прерывания контроллеру клавиатуры в порт 061h - необходимо установить и сразу сбросить 7 бит этого порта. После чего необходимо сбросить заявку на прерывание и вернуть управление: |
Код:
;- полноценный переход в 16битный реальный режим - генерация прерывания с заданным номером с сохранением регистров - переход обратно в 32битный защищенный режим и возврат управления. Чтобы не портить контекст перед вызовом прерывания, нам нужно позаботиться о сохранении тех регистров, которые мы собираемся изменять в процессе перехода: Код:
use32Код:
; переключаемся обратно в реальный режим...Код:
; загружаем в CS селектор 16-битного сегмента с лимитом 64кКод:
; мы в 16битном сегменте. переключаемся в реальный режим.Код:
REAL_ENTRY:Код:
mov [int_no], clДалее восстанавливаем AX, восстановление флагов командой popfd (нельзя тупо сделать sti, что казалось бы более очевидным, потому как перед во время вызова REAL_MODE_SWTICH_SERVICE прерывания отключены!) и делаем RET с выталкиванием четырех байт из стека (номер прерывания): Код:
mov ax, [old_ax]Пока! (C) Great, 2007 В статье использованы материалы wasm.ru, dims.karelia.ru и книги А.Жуков, А.Авдюхин "Ассемблер", СПб.:"БХВ-Петербург", 2003. |
Хм статья хорошая +1
/me пошел ставить на IRQ0 наивысший приоритет :) |
Как было справедливо замечено, вектора 0..1F заняты исключениями. Поэтому IRQ желательно перенсести, перепрограммировав контроллер следующей подпрограммой:
Код:
use32Код:
mov dx, 0FFFFh |
Это теперь доступно тут: http://www.wasm.ru/article.php?article=ia32int
|
| Время: 21:52 |