Any time at all, any time at all, any time at all, All you've gotta do is call and I'll be there. John Lennon
Конечно же, вы знаете, что такое прерывание (interrupt), слышали о таблице векторов прерываний (interrupt vector table) и вообще довольно неплохо представляете, как прерывания обрабатываются в реальном режиме :)
СОВЕТ Если это случайно не так, пожалуйста, обратитесь к литературе по программированию на ассемблере или спросите у более опытных в этом вопросе товарищей. Несмотря на то, что глава содержит всю необходимую информацию, вам будет значительно проще, если перед прочтением вы более-менее разберётесь с обработкой прерываний в реальном режиме. Причём желательно не ограничиваться теорией, а написать хотя бы одну работающую программу. |
Но, тем не менее, пару общих слов сказать нужно, так как тема важная и непростая.
Программные прерывания (software generated interrupts) вызываются инструкциями int x, где x – любое число от 0 до 255 (int 3 несколько отличается от прочих, подробнее эта тема освещена в следующей главе). Это, в общем-то, почти и не прерывания вовсе, так как ничего непонятного в них нет :) Фактически это просьба к процессору вызвать определённую подпрограмму, которая должна быть специальным образом зарегистрирована в системе, она называется обработчик прерывания (interrupt handler)
ПРИМЕЧАНИЕ В защищённом режиме это не обязательно будет подпрограмма, есть ещё один вариант, он будет обсуждаться отдельно, в главе «Многозадачность». |
Естественно, защищённый режим, так же как и реальный, предоставляет возможность обработки прерываний, не менее естественно, что, по сравнению с реальным режимом, в нём многое изменилось. Но, что приятно, сохранились основные положения:
Аналог таблицы векторов прерываний, существующий в защищённом режиме, называется таблицей дескрипторов прерываний (Interrupt Descriptor Table, IDT).
ПРИМЕЧАНИЕ Это термин из официального руководства Intel. В книге Дэвида Соломона и Марка Руссиновича «Внутреннее устройство Windows 2000» (Питер, Русская Редакция, 2001) упоминается IDT, но, почему-то, там она названа таблица диспетчеризации прерываний (interrupt dispatch table), причём это не ошибка русских переводчиков, в английском оригинале используется такое же название. На мой взгляд, следует придерживаться терминологии Intel. |
По формату и способу инициализации IDT практически идентична GDT. Основные отличия:
Таким образом, слегка модифицируя код из предыдущей главы, получаем пример инициализации IDT:
… ; Вычисляем линейный адрес начала массива дескрипторов mov eax, 0 mov ax, ds shl eax, 4 add eax, offset IDT ; Записываем его в структуру mov dword ptr offset idtr + 2, eax ; Загружаем IDTR. fword ptr – указатель на шестибайтную структуру lidt fword ptr idtr … ; Interrupt Descriptor Table IDT label byte db … ; Дескриптор #0 … db … ; Дескриптор #N idt_len equ $ - IDT ; размер IDT idtr dw idt_len – 1 ; 16-битный размер IDT – 1 dd ? ; Место для 32-х битного базового адреса IDT |
ПРИМЕЧАНИЕ Точно так же, как и в случае с lgdt, нормальные пользовательские приложения не имеют доступа к инструкции lidt. Этот факт вместе с возможностью поместить IDT в недоступную приложениям область памяти позволяет ОС контролировать обработку прерываний. Подробнее, как обычно, в главе про защиту. |
Несложно заметить, что та же самая структура table_register подходит и для загрузки в IDTR.
Дескриптор – не шлюза, а порядочная девушка! Надпись маркером в лифте.
Как уже говорилось в предыдущей главе, дескриптор это структура, описывающая некую системную сущность. Очередным типом дескрипторов, с которым вам придется познакомиться, будет дескриптор шлюза ловушки (trap gate descriptor; в [Гук 1999] слово «gate» переводится как «вентиль», это стандартный перевод для схемотехники, программисты обычно говорят «шлюз»), он предназначен для IDT и имеет следующий формат:
Положение | Название | Краткое описание |
---|---|---|
Нулевой и первый байты | Offset (part 1) | Младшие два байта 32-х битного поля Offset. Поле Offset содержит смещение обработчика прерывания. |
Второй и третий байты | Segment Selector | Селектор сегмента, содержащего обработчик прерывания. |
Четвёртый байт | 0 | 0, просто 0. Эта часть дескриптора не используется. |
0-й – 3-й биты пятого байта | ?? | Для дескриптора шлюза ловушки значение должно быть равно 1111b. Подробности про это и следующее поле в разделе «[Лирическое отступление] Классификация дескрипторов». |
4-й бит пятого байта | ?? | Устанавливайте в 0. |
5-й – 7-й биты пятого байта | ?? | Пока неважно, устанавливайте в 100b. |
Шестой и седьмой байты | Offset (part 2) | Старшие два байта поля Offset. |
То же самое на картинке:
Основная информация, которую несёт в себе дескриптор шлюза ловушки, это:
Поскольку формат практически прозрачен, остаётся только привести пример:
db 04h ; Offset – два младших байта db 03h db 8 ; Segment selector db 0 db 0 ; 0 db 10001111b ; 10001111 – магическое число.. db 02h ; Offset – два старших байта db 01h |
Это дескриптор шлюза ловушки, селектор сегмента указывает на первый дескриптор GDT, смещение – 01020304h. В виде структуры дескриптор шлюза выглядит так:
gate_descriptor struct offset_low dw 0 ; Два младших байта поля Offset selector dw 0 ; Поле Segment Selector zero db 0 type_and_permit db 0 ; Флаги. Этот байт отвечает за то, кто и как может ; использовать дескриптор. Подробнее – в следующих ; главах. offset_high dw 0 ; Старшие байты поля Offset gate_descriptor ends |
Первый признак классификации дескрипторов – значение 4-го бита пятого байта дескриптора. Полное название этого бита – флаг «descriptor type», краткое – флаг «S» (довольно странное сокращение, но везде применяется именно оно). В соответствии с этим признаком, дескрипторы делятся на два класса:
Принципиальное отличие системных дескрипторов от дескрипторов сегмента кода/данных в том, что селекторы первых не могут быть загружены в сегментные регистры и участвовать в формировании логического адреса <сегмент>:<смещение>. Даже если системный дескриптор описывает сегмент (вы познакомитесь с таким дескриптором в главе «Первая преамбула: одна задача»), для доступа к данным придётся использовать сегмент кода/данных (с соответствующим дескриптором и селектором), проецирующийся на нужную область памяти.
Системные дескрипторы имеют различный формат и единственное общее отличие их формата от формата дескрипторов сегмента кода/данных – младшие четыре бита пятого байта. Для обоих видов дескрипторов это поле называется «Type» и уточняет тип дескриптора, но при этом используются разные подходы:
Второй признак классификации – характер сущности, описываемой дескриптором. Это может быть:
Дескрипторы, описывающие похожие сущности, имеют близкие форматы. Так, любой дескриптор сегмента по формату похож на дескриптор сегмента кода/данных, а любой дескриптор шлюза – на дескриптор шлюза ловушки.
- Каждому прерыванию по шлюзу! - Ура!!! - Каждому шлюзу по обработчику! - Ура!!! - Каждому обработчику по прерыванию! [вопрос с места] - А правда, что прерываний только 255 и их не хватит всем пятистам обработчикам? - Нет! - Ура!!! ... Популистское выступление продолжается. Докладчику пока удаётся не врать...
Объединим всё, уже известное про прерывания и добавим недостающие детали.
Целью всего механизма обработки прерываний является своевременный вызов обработчика. В нашем случае (остальные случаи – в остальных главах, описывать всё сразу совершенно ни к чему) вызов происходит примерно так:
ПРЕДУПРЕЖДЕНИЕ Ещё раз обращаю ваше внимание: независимо от префиксов команд, разрядности сегмента стека, разрядности целевого и исходного сегмента кода и т.п. (всё это будет обсуждаться в приложении в разделе про разрядность), под EFLAGS, CS, EIP в стеке выделяется 12 байт, по 4 байта на каждый регистр. Это связано с тем, что тип 1111b соответствует 32-х разрядному дескриптору шлюза ловушки. Существует полностью аналогичный 16-и разрядный, но он в курсе не рассматривается. |
Упрощённая версия алгоритма в виде комикса:
Ключевые признаки, отделяющие «наш» случай от «не нашего»:
Добавим к алгоритмам переключения ещё несколько шагов. Из реального режима в защищённый:
ПРЕДУПРЕЖДЕНИЕ В данном случае нельзя использовать CS, оставшийся «в наследство» от реального режима, так как при возврате из обработчика прерывания сохранённое в стеке значение CS будет толковаться как селектор сегмента. |
ПРИМЕЧАНИЕ Поскольку обрабатывать аппаратные прерывания мы пока не планируем, разрешать прерывания будет преждевременно. |
Обратное переключение:
Для того, чтобы при переключении в реальный режим восстановить значение IDTR, при старте программы его необходимо сохранить. Для этого предназначена команда sidt.
sidt pointer_to_idtr |
Пример использования:
… ; Сохраняем IDTR sidt fword ptr old_idtr … old_idtr table_register <> |
Практически минимальная программа, устанавливающая обработчик нулевого прерывания и вызывающая его командой int 0.
ПРИМЕЧАНИЕ Программа написана на основе примера cs64kb.asm из предыдущей главы. |
; int0.asm ; Программа, устанавливающая и вызывающая обработчик нулевого прерывания .model tiny .code .386p org 100h ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Структуры ; ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Сегментный дескриптор segment_descriptor struct limit_low dw 0 ; Младшие два байта поля Segment limit base_low dw 0 ; Младшие два байта поля Base Address base_high0 db 0 ; Второй байт поля Base Address type_and_permit db 0 ; Флаги flags db 0 ; Ещё одни флаги base_high1 db 0 ; Старший байт поля Base Address segment_descriptor ends ; Дескриптор шлюза gate_descriptor struct offset_low dw 0 ; Два младших байта поля Offset selector dw 0 ; Поле Segment Selector zero db 0 type_and_permit db 0 ; Флаги offset_high dw 0 ; Старшие байты поля Offset gate_descriptor ends ; Регистр, описывающий таблицу table_register struct limit dw 0 ; Table Limit base dd 0 ; Linear Base Address table_register ends ;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Код ; ;;;;;;;;;;;;;;;;;;;;;;;;;; start: ; Подготавливаем DS push cs pop ds ; В es - начало видеобуфера. Можно было сделать то же ; самое средствами защищённого режима, но так проще push 0b800h pop es ; Устанавливаем правильный сегмент в long-jmp-to-RM mov ax, cs mov cs:rm_cs, ax ; Прописываем адрес начала cs в качестве базового адреса сегмента call cs_to_eax mov cs_dsc.base_low, ax shr eax, 16 mov cs_dsc.base_high0, al ; Сохраняем IDTR реального режима sidt fword ptr old_idtr ; Запретили прерывания call disable_interrupts ; Инициализируем GDT call initialize_gdt ; Инициализируем IDT call initialize_idt ; Переключаем режим call set_PE ; 16-разрядный дальний переход. Перключает содержимое cs из нормального ; для реального режима (адрес) в нормальное для защищённого (селектор). ; Базовый адрес целевого сегмента совпадает с cs, ; поэтому смещение можно прописать сразу db 0EAh ; код команды дальнего перехода dw $ + 4 ; смещение dw 8 ; селектор ; В данный момент сегмент кода - 64 Кб, базовый адрес равен ; адресу сегмента кода до переключения в защищённый режим. int 0 ; вызываем прерывание call clear_PE ; Мы в реальном режиме, осталось разобраться с ; значением регистра cs ; 16-разрядный дальний переход. Перключает содержимое cs из нормального ; для защищённог режима (селектор) в нормальное для реальног (адрес). ; Адрес сегмента вычисляется и прописывается во время выполнения. db 0EAh ; код команды дальнего перехода dw $ + 4 ; смещение rm_cs dw 0 ; сегмент ; восстанавливаем IDTR реального режима lidt fword ptr old_idtr ; разрешаем прерывания call enable_interrupts ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Обработчик прерывания ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Обработчик прерывания int 0, меняет символы и их атрибуты по всему экрану ; Предпологает, что база es - начало видеобуфера int0_handler: ; В данный момент, в стеке: ; esp + 0 - EIP ; esp + 4 – CS и два байта мусора ; esp + 8 - EFLAGS push eax push ecx mov eax, 0 ; Текущий символ mov ecx, 80 * 25 ; Колическтво сиволов на экране screen_loop: inc byte ptr es:[eax] ; Меняем символ inc eax inc byte ptr es:[eax] ; Меняем атрибут inc eax loop screen_loop pop ecx pop eax iretd ; 32-х разрядный возврат из прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Данные ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Глобальная таблица дескрипторов GDT label byte ; Нулевой дескриптор segment_descriptor <> ; Дескриптор сегмента кода, размер 64 Kb cs_dsc segment_descriptor <0ffffh, 0, 0, 10011010b, 0, 0> ; 10011010b - 1001, C/D - 1, 0, R/W - 1, 0 ; 0 - G - 0, 000, Limit - 0 ; Данные для загрузки в GDTR gdtr table_register <$ - GDT - 1, 0> ; Таблица дескрипторов прерываний IDT label byte ; Дескриптор шлюза ловушки. ; Обработчик прерывания находится в сегменте, соответствующем первому ; дескриптору GDT. Поскольку базовый адрес сегмента такой же, как ; в реальном режиме, смещение обработчика тоже совпадает. gate_descriptor <int0_handler, 8, 0, 8Fh, 0> ; Данные для загрузки в IDTR idtr table_register <$ - IDT - 1, 0> ; Место для IDTR реального режима old_idtr table_register <> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Служебные функции ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Инициализирует IDT initialize_idt: ; Вычисляем линейный адрес начала массива дескрипторов call cs_to_eax add eax, offset IDT ; Записываем его в структуру mov idtr.base, eax ; Загружаем IDTR lidt fword ptr idtr ret ; Инициализирует GDT initialize_gdt: ; Вычисляем линейный адрес начала массива дескрипторов call cs_to_eax add eax, offset GDT ; Записываем его в структуру mov gdtr.base, eax ; Загружаем GDTR lgdt fword ptr gdtr ret ; Запрещает маскируемые и немаскируемые прерывания disable_interrupts: cli ; запретить прерывания in al, 70h ; индексный порт CMOS or al, 80h ; установка бита 7 в нем запрещает NMI out 70h, al ret ; Разрешает маскируемые и немаскируемые прерывания enable_interrupts: in al, 70h ; индексный порт CMOS and al, 7Fh ; сброс бита 7 отменяет блокирование NMI out 70h, al sti ; разрешить прерывания ret ; Устанавливает флаг PE set_PE: mov eax, cr0 ; прочитать регистр CR0 or al, 1 ; установить бит PE, mov cr0, eax ; с этого момента мы в защищенном режиме ret ; Сбрасывает флаг PE clear_PE: mov eax, cr0 ; прочитать CR0 and al, 0FEh ; сбросить бит PE mov cr0, eax ; с этого момента мы в реальном режиме ret ; Вычисляет линейный адрес начала сегмента кода cs_to_eax: mov eax, 0 mov ax, cs shl eax, 4 ret end start |