Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

3. ОБРАБОТКА ПРЕРЫВАНИЙ В ЗАЩИЩЁННОМ РЕЖИМЕ

3.1. Прерывания в реальном режиме

3.2. Прерывания защищённого режима

3.3. Программа, которая работает с прерываниями

Механизм обработки прерываний в защищённом режиме сильно отличается от механизма реального режима. До сих пор мы ничего не говорили о прерываниях, в приведённом выше примере программы перед переходом в защищённый режим мы просто замаскировали все прерывания. Однако такой способ "решения" проблемы прерываний подходит не всегда.

В этой главе мы расскажем вам о том, как надо работать с прерываниями в защищённом режиме и приведём соответствующий пример программы. Вы научитесь обрабатывать в защищённом режиме не только программные прерывания, но и аппаратные, что необходимо, в частности, для создания драйверов.

3.1. Прерывания в реальном режиме

В главе 4 первой книги первого тома серии "Библиотека системного программиста", который называется "Операционная система MS-DOS", мы подробно рассказали об особенностях обработки прерываний в реальном режиме.

Напомним только основные моменты.

В реальном режиме имеются программные и аппаратные прерывания. Программные прерывания инициируются командой INT, аппаратные - внешними событиями, асинхронными по отношению к выполняемой программе. Обычно аппаратные прерывания инициируются аппаратурой ввода/вывода после завершения выполнения текущей операции.

Кроме того, некоторые прерывания зарезервированы для использования самим процессором - прерывания по ошибке деления, прерывания для пошаговой работы, немаскируемое прерывание и т.д.

Для обработки прерываний в реальном режиме процессор использует таблицу векторов прерываний. Эта таблица располагается в самом начале оперативной памяти, т.е. её физический адрес - 00000.

Таблица векторов прерываний реального режима состоит из 256 элементов по 4 байта, таким образом её размер составляет 1 килобайт. Элементы таблицы - дальние указатели на процедуры обработки прерываний. Указатели состоят из 16-битового сегментного адреса процедуры обработки прерывания и 16-битового смещения. Причём смещение хранится по младшему адресу, а сегментный адрес - по старшему.

Когда происходит программное или аппаратное прерывание, текущее содержимое регистров CS, IP а также регистра флагов FLAGS записывается в стек программы (который, в свою очередь, адресуется регистровой парой SS:SP). Далее из таблицы векторов прерываний выбираются новые значения для CS и IP, при этом управление передаётся на процедуру обработки прерывания.

Перед входом в процедуру обработки прерывания принудительно сбрасываются флажки трассировки TF и разрешения прерываний IF. Поэтому если ваша процедура прерывания сама должна быть прерываемой, вам необходимо разрешить прерывания командой STI. В противном случае, до завершения процедуры обработки прерывания все прерывания будут запрещены.

Завершив обработку прерывания, процедура должна выдать команду IRET, по которой из стека будут извлечены значения для CS, IP, FLAGS и загружены в соответствующие регистры. Далее выполнение прерванной программы будет продолжено.

Что же касается аппаратных маскируемых прерываний, то в компьютере IBM AT и совместимых с ним существует всего шестнадцать таких прерываний, обозначаемых IRQ0-IRQ15. В реальном режиме для обработки прерываний IRQ0-IRQ7 используются вектора прерываний от 08h до 0Fh, а для IRQ8-IRQ15 - от 70h до 77h.

3.2. Прерывания защищённого режима

В защищённом режиме все прерывания разделяются на два типа - обычные прерывания и исключения (exception - исключение, особый случай).

Обычное прерывание инициируется командой INT (программное прерывание) или внешним событием (аппаратное прерывание). Перед передачей управления процедуре обработки обычного прерывания флаг разрешения прерываний IF сбрасывается и прерывания запрещаются.

Исключение происходит в результате ошибки, возникающей при выполнении какой-либо команды, например, если команда пытается выполнить запись данных за пределами сегмента данных или использует для адресации селектор, который не определён в таблице дескрипторов. По своим функциям исключения соответствуют зарезервированным для процессора внутренним прерываниям реального режима. Когда процедура обработки исключения получает управление, флаг IF не изменяется. Поэтому в мультизадачной среде особые случаи, возникающие в отдельных задачах, не оказывают влияния на выполнение остальных задач.

В защищённом режиме прерывания могут приводить к переключению задач. О задачах и мультизадачности мы будем говорить в следующей главе.

Теперь перейдём к рассмотрению механизма обработки прерываний и исключений в защищённом режиме.

Таблица прерываний защищённого режима

Обработка прерываний и исключений в защищённом режиме по аналогии с реальным режимом базируется на таблице прерываний. Но таблица прерываний защищённого режима является таблицей дескрипторов, которая содержит так называемые вентили прерываний, вентили исключений и вентили задач.

Таблица прерываний защищённого режима называется дескрипторной таблицей прерываний IDT (Interrupt Descriptor Table). Также как и таблицы GDT и LDT, таблица IDT содержит 8-байтовые дескрипторы. Причём это системные дескрипторы - вентили прерываний, исключений и задач. Поле TYPE вентиля прерывания содержит значение 6, а вентиля исключения - значение 7.

Формат элементов дескрипторной таблицы прерываний IDT показан на рис. 12.

Рис. 12. Формат элементов дескрипторной таблицы прерываний IDT.

Где располагается дескрипторная таблица прерываний IDT?

Её расположение определяется содержимым 5-байтового внутреннего регистра процессора IDTR. Формат регистра IDTR полностью аналогичен формату регистра GDTR, для его загрузки используется команда LIDT. Так же, как регистр GDTR содержит 24-битовый физический адрес таблицы GDT и её предел, так и регистр IDTR содержит 24-битовый физический адрес дескрипторной таблицы прерываний IDT и её предел.

Регистр IDTR обычно загружают перед переходом в защищённый режим. Разумеется, это можно сделать и потом, находясь в защищённом режиме. Однако для этого программа должна работать в привилегированном нулевом кольце.

Исключения в защищённом режиме

Для обработки особых ситуаций - исключений - разработчики процессора i80286 зарезервировали 31 номер прерывания. В таблице 3 приведён полный список зарезервированных прерываний защищённого режима.

Таблица 4. Зарезервированные прерывания защищённого режима.

00hОшибка при выполнении команды деления.
01hПрерывание для пошаговой работы, используется отладчиками.
02hНемаскируемое прерывание.
03hПрерывание по точке останова для отладчиков.
04hПереполнение, генерируется командой INTO, если установлен флаг OF.
05hГенерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона.
06hНедействительный код операции, или длина команды больше 10 байт.
07hОтсутствие арифметического сопроцессора.
08hДвойная ошибка, вырабатывается в том случае, если при обработке исключения возникло ещё одно исключение. Если во время обработки этого прерывания возникает третье исключение, процессор переходит в состояние отключения, что приводит к перезапуску процессора.
09hПревышение сегмента арифметическим сопроцессором.
0AhНедействительный сегмент состояния задачи TSS.
0BhОтсутствие сегмента. Вырабатывается при попытке использовать для адресации дескриптор, у которого бит присуствия сегмента в памяти P сброшен в 0. Это прерывание используется для реализации механизма виртуальной памяти. В этом случае по прерыванию 0Bh операционная система может выполнить подкачку отсутствующего сегмента в память.
0ChИсключение при работе со стеком. Может возникать в случае отсутствия сегмента стека в памяти или в случае переполнения (антипереполнения) стека.
0DhИсключение по защите памяти. Возникает при любых попытках получения доступа к сегментам памяти, если программа обладает недостаточным уровнем привилегий.
0EhОтказ страницы для процессоров i80386 или i80486, зарезервировано для i80286.
0FhЗарезервировано.
10hИсключение сопроцессора.
11h - 1AhЗарезервированы.

Перед тем, как передать управление обработчику исключения, для многих зарезервированных прерываний процессор помещает в стек 16-битовый код ошибки. Этот код ошибки программа может проанализировать и тем самым получить некоторую дополнительную информацию об ошибке.

Формат кода ошибки приведён на рис. 13.

Рис. 13. Формат кода ошибки процессора i80286.

Поле индекса содержит индекс дескриптора, при обращении к которому произошла ошибка. Поле I, равное 1, означает, что этот индекс относится к таблице IDT. В этом случае произошла ошибка при обработке прерывания или исключения.

Если бит I равен 0, поле TI выбирает таблицу дескрипторов (GDT или LDT) по аналогии с соответствующим полем селектора.

Бит EXT устанавливается в том случае, когда ошибка произошла не в результате выполнения текущей команды, а по внешним относительно выполняемой программы причинам. Например, при обработке аппаратного прерывания от устройства ввода/вывода произошло обращение к отсутствующему в памяти сегменту (у которого в дескрипторе сброшен бит присутствия P).

Как мы только что говорили, коды ошибок включаются в стек не для всех исключений. Программа сможет проанализировать этот код только для следующих исключений:

  • 08h - двойная ошибка;
  • 0Ah - недействительный TSS;
  • 0Bh - отсутствие сегмента в памяти;
  • 0Ch - исключение при работе со стеком;
  • 0Dh - исключение по защите памяти.

Заметим, что аналога коду ошибки для зарезервированных прерываний в реальном режиме нет.

Кроме того, новым при обработке прерываний в защищённом режиме является свойство повторной запускаемости исключений. Свойством повторной запускаемости обладают не все исключения.

Что такое повторная запускаемость?

Поясним это на конкретном примере. Пусть в нашей системе реализована виртуальная память. Программа в некоторый момент времени обратилась к отсутствующему в оперативной памяти сегменту, выдав какую-либо команду, например MOV или ADD.

Возникло исключение 0Bh - отсутствие сегмента в памяти. Обработчик этого исключения, входящий в состав операционной системы выполнил свопинг соответствующего сегмента в оперативную память. Что дальше? А дальше было бы неплохо повторить выполнение прерванной команды!

Это можно сделать, так как для всех повторно запускаемых исключений (кроме 03h - прерывание по точке останова и 04h - переполнение) в стек включается адрес не следующей за прерванной командой, а адрес первого байта команды, которая вызвала исключение. Выполнив команду IRET, программа обработки исключения вновь передаст управление прерванной команде.

Свойством повторной запускаемости обладает большинство зарезервированных прерываний, кроме следующих:

  • 01h - прерывание для пошаговой работы;
  • 08h - двойная ошибка;
  • 09h - превышение сегмента сопроцессором;
  • 0Dh - исключение по защите памяти;
  • 10h - исключение сопроцессора.

Обработка аппаратных прерываний

Вспомните диапазон номеров прерываний, используемый в реальном режиме в компьютерах IBM PC: для обработки прерываний IRQ0-IRQ7 используются номера прерываний от 08h до 0Fh, а для IRQ8-IRQ15 - от 70h до 77h.

Но в защищённом режиме номера от 08h до 0Fh зарезервированы для обработки исключений!

К счастью, имеется простой способ перепрограммирования контроллера прерываний на любой другой диапазон номеров векторов аппаратных прерываний. Например, аппаратные прерывания можно расположить сразу за прерываниями, зарезервированными для обработки исключений.

После возврата процессора в реальный режим необходимо восстановить состояния контроллера прерываний. Если при подготовке к возврату в реальный режим мы записали в CMOS-память байт состояния отключения со значением 5, после сброса BIOS сам перепрограммирует контроллер прерываний для работы в реальном режиме и нам не надо об этом беспокоиться. В противном случае программа должна установить правильные номера для аппаратных прерываний реального режима.

3.3. Программа, которая работает с прерываниями

Следующая программа, которую мы вам представим, выполняет все необходимые действия, связанные с обработкой прерываний и исключений в защищённом режиме.

Наша первая программа (листинг 1) работала в защищённом режиме с запрещёнными прерываниями. Она как бы пролетала через неизведанное с завязанными глазами и сразу же возвращалась в хорошо освоенный реальный режим.

Теперь же программа имеет возможность "осмотреться" и может реагировать на такие события, как прерывания от клавиатуры и таймера. Кроме того, в случае возникновения исключений вам будет выдана некоторая диагностика, включающая код исключения, код ошибки и содержимое регистров процессора на момент возникновения исключения.

Таблица IDT содержит дескрипторы для обработчиков исключений и аппаратных прерываний. Вентили исключений содержат ссылки на программы с именами exc_00...exc_1F. Вслед за вентилями исключений в таблице IDT следуют вентили аппаратных прерываний. Из них задействованы только IRQ0 и IRQ1 - прерывания от таймера и клавиатуры.

Те аппаратные прерывания, которые нас не интересуют, приводят к выдаче в контроллеры прерывания команды конца прерывания. Для таких прерываний предусмотрены заглушки - процедуры с именами dummy_iret0 и dummy_iret1. Первая заглушка относится к первому контроллеру прерывания, вторая - ко второму.

Вслед за вентилями аппаратных прерываний мы поместили в таблицу IDT вентиль программного прерывания int 30h, предназначенного для организации взаимодействия с клавиатурой на манер прерывания BIOS INT 16h. При выдаче прерывания int 30h вызывается процедура с именем Int_30h_Entry.

После запуска программа переходит в защищённый режим и размаскирует прерывания от таймера и клавиатуры. Далее она вызывает в цикле прерывание int 30h (ввод символа с клавиатуры), и выводит на экран скан-код нажатой клавиши и состояние переключающих клавиш (таких, как CapsLock, Ins, и т.д.).

Если окажется нажатой клавиша ESC, программа выходит из цикла. Далее в тексте программы следует ряд команд, закрытых символом комментария. Эти команды приводят к исключению. Вы можете попробовать их работу, удалив соответствующий символ комментария. При возникновении исключения на экран будет выведена диагностика, о чём позаботится обработчик исключений.

Перед завершением работы программа устанавливает реальный режим процессора и стирает экран.

Обработчик аппаратного прерывания клавиатуры - процедура с именем Keyb_int. После прихода прерывания она выдаёт короткий звуковой сигнал, считывает и анализирует скан-код клавиши, вызвавшей прерывание. Скан-коды классифицируются на обычные и расширенные (для 101-клавишной клавиатуры). В отличие от прерывания BIOS INT 16h, мы для простоты не стали реализовывать очередь, а ограничились записью полученного скан-кода в глобальную ячейку памяти key_code. Причём прерывания, возникающие при отпускании клавиш игнорируются.

Запись скан-кода в ячейку key_code выполняет процедура Keyb_PutQ. После записи эта процедура устанавливает признак того, что была нажата клавиша - записывает значение 0FFh в глобальную переменную key_flag.

Программное прерывание int 30h опрашивает состояние key_flag. Если этот флаг оказывается установленным, он сбрасывается, вслед за чем обработчик int 30h записывает в регистр AX скан-код нажатой клавиши, в регистр BX - состояние переключающих клавиш на момент нажатия клавиши, код которой передан в регистре AX.

Для того чтобы не загромождать программу второстепенными деталями, мы не стали перекодировать скан-код в код ASCII, вы сможете при необходимости сделать это сами.

Обработчик прерываний таймера - процедура с именем Timer_int. Эта процедура примерно раз в секунду выдаёт звуковой сигнал, чем её действия и ограничиваются.

Процедура rdump выводит на экран содержимое регистров процессора и может быть использована для отладки программы.

Обратите внимание на использованный способ возврата процессора в реальный режим:

 DATASEG
 
 ; Пустой дескриптор для выполнения возврата
 ; процессора в реальный режим через перевод
 ; его в состояние отключения.
 
 null_idt idt_struc <> 
 
 CODESEG
 
 PROC    set_rmode       NEAR
 
         mov     [real_sp],sp
 
 ; Переводим процессор в состояние отключения,
 ; это эквивалентно аппаратному сбросу, но
 ; выполняется быстрее.
 ; Сначала мы загружаем IDTR нулями, затем
 ; выдаём команду прерывания.
 
         lidt    [FWORD null_idt]
         int     3
 
 rwait:
         hlt
         jmp     rwait
 
 LABEL   shutdown_return FAR
 
 
 
 
 

Регистр IDTR загружается нулями, следовательно, предел дескрипторной таблицы прерываний равен нулю. После этого мы выдаём команду программного прерывания. При обработке прерывания возникает исключение, так как регистр IDTR инициализирован неправильно. Но это исключение не может быть обработано по той же причине, что вызывает новое исключение. Теперь процессор переходит уже в состояние отключения и выполняет рестарт в реальном режиме. Что нам и требовалось получить!

Для удобства мы вынесли в отдельный файл tiny-os.inc все необходимые определения структур данных и констант (листинг 2). Файл tiny-os.asm содержит саму программу, текст которой приведён в листинге 3.

 Листинг 2. Структуры данных и константы.
 -----------------------------------------------------------
 
 ; ------------------------------------------------------------
 ; Определения структур данных и констант
 ; ------------------------------------------------------------
 
 STRUC   desc_struc      ; структура дескриптора
         limit   dw      0       ; предел
         base_l  dw      0       ; мл. слово физического адреса
         base_h  db      0       ; ст. байт физического адреса
         access  db      0       ; байт доступа
         rsrv            dw      0       ; зарезервировано
 ENDS            desc_struc
 
 STRUC   idt_struc               ; вентиль прерывания
         destoff dw      0       ; смещение обработчика
         destsel dw      0       ; селектор обработчика
         nparams db      0       ; кол-во параметров
         assess  db       0       ; байт доступа
         rsrv       dw     0       ; зарезервировано
 ENDS            idt_struc
 
 STRUC   idtr_struc              ; регистр IDTR
         idt_lim dw      0       ; предел IDT
         idt_l   dw      0       ; мл. слово физического адреса
         idt_h   db      0       ; ст. байт физического адреса
         rsrv            db      0       ; зарезервировано
 ENDS            idtr_struc
 
 ; ---------------------------------------------------------------
 ; Биты байта доступа
 
 ACC_PRESENT     EQU     10000000b ; сегмент есть в памяти
 ACC_CSEG                EQU     00011000b ; сегмент кода
 ACC_DSEG                EQU     00010000b ; сегмент данных
 ACC_EXPDOWN     EQU     00000100b ; сегмент расширяется вниз
 ACC_CONFORM     EQU     00000100b ; согласованный сегмент
 ACC_DATAWR      EQU     00000010b ; разрешена запись
 ACC_INT_GATE    EQU     00000110b ; вентиль прерывания
 ACC_TRAP_GATE   EQU     00000111b ; вентиль исключения
 
 ; ------------------------------------------------------------
 ; Типы сегментов
 
 ; сегмент данных
 
 DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR
 
 ; сегмент кода
 
 CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM
 
 ; сегмент стека
 
 STACK_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR OR ACC_EXPDOWN
 
 ; байт доступа сегмента таблицы IDT
 
 IDT_ACC         =       DATA_ACC
 
 ; байт доступа вентиля прерывания
 
 INT_ACC         =       ACC_PRESENT OR ACC_INT_GATE
 
 ; байт доступа вентиля исключения
 
 TRAP_ACC        =       ACC_PRESENT OR ACC_TRAP_GATE
 
 ; ------------------------------------------------------------
 ; Константы
 
 STACK_SIZE      EQU     0400    ; размер стека
 B_DATA_SIZE     EQU     0300    ; размер области данных BIOS
 B_DATA_ADDR     EQU     0400    ; адрес области данных BIOS
 MONO_SEG                EQU     0b000   ; сегмент видеопамяти 
                                                 ;  монохромного видеоадаптера
 COLOR_SEG               EQU     0b800   ; сегмент видеопамяти
                                                 ; цветного видеоадаптера
 CRT_SIZE                EQU     4000    ; размер сегмента видеопамяти
                                         ;  цветного видеоадаптера
 MONO_SIZE               EQU     1000    ; размер сегмента видеопамяти
                                         ;  монохромного видеоадаптера
 
 CRT_LOW         EQU     8000    ; мл. байт физического адреса
                                         ;  сегмента видеопамяти
                                         ;  цветного видеоадаптера
 MONO_LOW                EQU     0000    ; мл. байт физического адреса
                                         ;  сегмента видеопамяти
                                         ;  монохромного видеоадаптера
 
 CRT_SEG         EQU     0Bh     ; ст. байт физического адреса
                                         ;  сегмента видеопамяти
 CMOS_PORT               EQU     70h     ; порт для доступа к CMOS-памяти
 PORT_6845               EQU     0063h   ; адрес области данных BIOS,
                                                 ; где записано значение адреса
                                                 ; порта контроллера 6845
 COLOR_PORT      EQU     03d4h   ; порт цветного видеоконтроллера
 MONO_PORT               EQU     03b4h   ; порт монохромного видеоконтроллера
 STATUS_PORT     EQU     64h             ; порт состояния клавиатуры
 SHUT_DOWN               EQU     0feh            ; команда сброса процессора
 VIRTUAL_MODE    EQU     0001h   ; бит перехода в защищённый режим
 A20_PORT                EQU     0d1h    ; команда управления линией A20
 A20_ON          EQU     0dfh    ; открыть A20
 A20_OFF         EQU     0ddh    ; закрыть A20
 KBD_PORT_A      EQU     60h     ; адреса клавиатурных
 KBD_PORT_B      EQU     61h     ;   портов
 INT_MASK_PORT   EQU     21h     ; порт для маскирования прерываний
 EOI                     EQU     20      ; команда конца прерывания
 MASTER8259A     EQU     20      ; первый контроллер прерываний
 SLAVE8259A      EQU     0a0     ; второй контроллер прерываний
 
 ; ------------------------------------------------------------
 ; Селекторы, определённые в таблице GDT
 
 DS_DESCR                =       (gdt_ds - gdt_0)
 CS_DESCR                =       (gdt_cs - gdt_0)
 SS_DESCR                =       (gdt_ss - gdt_0)
 BIOS_DESCR      =       (gdt_bio - gdt_0)
 CRT_DESCR               =       (gdt_crt - gdt_0)
 MDA_DESCR               =       (gdt_mda - gdt_0)
 
 ; ------------------------------------------------------------
 ; Маски и инверсные маски для клавиш
 
 L_SHIFT         equ     0000000000000001b
 NL_SHIFT                equ     1111111111111110b
 
 R_SHIFT         equ     0000000000000010b
 NR_SHIFT                equ     1111111111111101b
 
 L_CTRL          equ     0000000000000100b
 NL_CTRL         equ     1111111111111011b
 
 R_CTRL          equ     0000000000001000b
 NR_CTRL         equ     1111111111110111b
 
 L_ALT           equ     0000000000010000b
 NL_ALT          equ     1111111111101111b
 
 R_ALT           equ     0000000000100000b
 NR_ALT          equ     1111111111011111b
 
 CAPS_LOCK               equ     0000000001000000b
 SCR_LOCK                equ     0000000010000000b
 
 NUM_LOCK                equ     0000000100000000b
 INSERT          equ     0000001000000000b
 
 Листинг 3. Демонстрация обработки прерываний и исключений
                 в защищённом режиме для процессора 80286
 -----------------------------------------------------------
 
 IDEAL
 RADIX   16
 P286
 MODEL   LARGE
 
 include 'tiny-os.inc'
 
 STACK   STACK_SIZE
 
 DATASEG
 DSEG_BEG = THIS WORD
 
         real_ss dw      ?
         real_sp dw      ?
         real_es dw      ?
 
 GDT_BEG = $
 LABEL   gdtr            WORD
 
 gdt_0   desc_struc <0,0,0,0,0>
 gdt_gdt         desc_struc <GDT_SIZE-1,,,DATA_ACC,0>
 gdt_idt         desc_struc <IDT_SIZE-1,,,IDT_ACC,0>
 gdt_ds  desc_struc <DSEG_SIZE-1,,,DATA_ACC,0>
 gdt_cs  desc_struc <CSEG_SIZE-1,,,CODE_ACC,0>
 gdt_ss  desc_struc <STACK_SIZE-1,,,DATA_ACC,0>
 gdt_bio         desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0>
 gdt_crt         desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0>
 gdt_mda         desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0>
 
 GDT_SIZE = ($ - GDT_BEG)
 
 ; Область памяти для загрузки регистра IDTR
 
 idtr    idtr_struc      <IDT_SIZE,,,0>
 
 ; Таблица дескрипторов прерываний
 
 IDT_BEG = $
 
 ; ---------------------- Вентили исключений --------------------
 
 idt     idt_struc <OFFSET exc_00,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_01,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_02,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_03,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_04,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_05,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_06,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_07,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_08,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_09,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0A,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0B,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0C,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0D,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0E,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_0F,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_10,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_11,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_12,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_13,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_14,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_15,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_16,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_17,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_18,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_19,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1A,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1B,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1C,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1D,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1E,CS_DESCR,0,TRAP_ACC,0>
         idt_struc <OFFSET exc_1F,CS_DESCR,0,TRAP_ACC,0>
 
 ; --------------- Вентили аппаратных прерываний ---------------
 
 ; int 20h-IRQ0
 
         idt_struc <OFFSET Timer_int,CS_DESCR,0,INT_ACC,0>
 
 ; int 21h-IRQ1
 
         idt_struc <OFFSET Keyb_int,CS_DESCR,0,INT_ACC,0>
 
 ; int 22h, 23h, 24h, 25h, 26h, 27h-IRQ2-IRQ7
 
         idt_struc 6 dup (<OFFSET dummy_iret0,CS_DESCR,0,INT_ACC,0>)
 
 ; int 28h, 29h, 2ah, 2bh, 2ch, 2dh, 2eh, 2fh-IRQ8-IRQ15
 
         idt_struc 8 dup (<OFFSET dummy_iret1,CS_DESCR,0,INT_ACC,0>)
 
 
 ; -------------------- Вентиль прерывания --------------------
 
 ; int 30h 
                 idt_struc       <OFFSET Int_30h_Entry,CS_DESCR,0,INT_ACC,0>
 
 
 IDT_SIZE        = ($ - IDT_BEG)
 
 CODESEG
 
 PROC    start
 
         mov     ax,DGROUP
         mov     ds,ax
         call    set_crt_base
         mov     bh, 77h
         call    clrscr
 
 ; Устанавливаем защищённый режим
 
         call    set_pmode       
         call    write_hello_msg 
 
 ; Размаскируем прерывания от таймера и клавиатуры
 
         in      al,INT_MASK_PORT
         and     al,0fch
         out     INT_MASK_PORT,al
 
 ; Ожидаем нажатия на клавишу <ESC>
 
 charin:
         int     30h     ; ожидаем нажатия на клавишу
                         ; AX - скан-код клавиши,
                         ; BX - состояние переключающих клавиш
         cmp     al, 1   ; если <ESC> - выход из цикла
         jz      continue
 
         push    bx              ; выводим скан-код на экран
         mov     bx, 0301h       ; координаты вывода
         call    Print_Word
         pop     bx
 
         mov     ax, bx          ; выводим состояние
         push    bx              ; переключающих клавиш
         mov     bx, 0306h
         call    Print_Word
         pop     bx
 
         jmp     charin
 
 ; Следующий байт находится в сегменте кода.
 ; Он используется нами для демонстрации возникновения
 ; исключения при попытке записи в сегмент кода.
 
 wrong1  db ?
 
 continue:
 
 ; После нажатия на клавишу <ESC> выходим в это место
 ; программы. Следующие несколько строк демонстрируют
 ; команды, которые вызывают исключение. Вы можете
 ; попробовать их, если уберёте символ комментария
 ; из соответствующей строки.
 
 ; Попытка записи за конец сегмента данных. Метка wrong
 ; находится в самом конце программы.
         mov     [wrong], al
 
 ; Попытка записи в сегмент кода.
 ;       mov     [wrong1], al
 
 ; Попытка извлечения из пустого стека.
 ;       pop     ax
 
 ; Загрузка в сегментный регистр неправильного селектора.
 ;       mov     ax, 1280h
 ;       mov     ds, ax
 
 ; Прямой вызов исключения при помощи команды прерывания.
 ;       int     1
 
 
         call    set_rmode       ; установка реального режима
 
         mov     bh, 07h         ; стираем экран и
         call    clrscr          ; выходим в DOS
         mov     ah,4c
         int     21h
 
 ENDP    start
 
 
 MACRO setgdtentry
         mov     [(desc_struc bx).base_l],ax
         mov     [(desc_struc bx).base_h],dl
 ENDM
 
 ; -------------------------------------------
 ; Установка защищённого режима
 ; -------------------------------------------
 
 PROC    set_pmode       NEAR
 
         mov     ax,DGROUP
         mov     dl,ah
         shr     dl,4
         shl     ax,4
         mov     si,ax
         mov     di,dx
         add     ax,OFFSET gdtr
         adc     dl,0
         mov     bx,OFFSET gdt_gdt
         setgdtentry
 
 ; Заполняем дескриптор в GDT, указывающий на
 ; дескрипторную таблицу прерываний
 
         mov     ax,si   ; загружаем 24-битовый адрес сегмента
         mov     dx,di   ; данных
         add     ax,OFFSET idt   ; адрес дескриптора для IDT
         adc     dl,0
         mov     bx,OFFSET gdt_idt
         setgdtentry
 
 ; Заполняем структуру для загрузки регистра IDTR
 
         mov     bx,OFFSET idtr
         mov     [(idtr_struc bx).idt_l],ax
         mov     [(idtr_struc bx).idt_h],dl
 
         mov     bx,OFFSET gdt_ds
         mov     ax,si
         mov     dx,di
         setgdtentry
 
         mov     bx,OFFSET gdt_cs
         mov     ax,cs
         mov     dl,ah
         shr     dl,4
         shl     ax,4
         setgdtentry
 
         mov     bx,OFFSET gdt_ss
         mov     ax,ss
         mov     dl,ah
         shr     dl,4
         shl     ax,4
         setgdtentry
 
 ; готовим возврат в реальный режим
 
         push    ds
         mov     ax,40
         mov     ds,ax
         mov     [WORD 67],OFFSET shutdown_return
         mov     [WORD 69],cs
         pop     ds
 
         cli
         mov     al,8f
         out     CMOS_PORT,al
         jmp     del1
 del1:
         mov     al,5
         out     CMOS_PORT+1,al
 
         mov     ax,[rl_crt]     ; сегмент видеопамяти
         mov     es,ax
 
         call    enable_a20      ; открываем линию A20
 
         mov     [real_ss],ss    ; сохраняем сегментные
         mov     [real_es],es    ; регистры
 
 ; -------- Перепрограммируем контроллер прерываний --------
 
 ; Устанавливаем для IRQ0-IRQ7 номера прерываний 20h-27h
 
         mov     dx,MASTER8259A
         mov     ah,20h
         call    set_int_ctrlr
 
 ; Устанавливаем для IRQ8-IRQ15 номера прерываний 28h-2Fh
 
         mov     dx,SLAVE8259A
         mov     ah,28h
         call    set_int_ctrlr
 
 ; Загружаем регистры IDTR и GDTR
 
         lidt    [FWORD idtr]
         lgdt    [QWORD gdt_gdt]
 
 ; Переключаемся в защищённый режим
 
         mov     ax,VIRTUAL_MODE
         lmsw    ax
 
 ;       jmp     far flush
         db      0ea
         dw      OFFSET flush
         dw      CS_DESCR
 LABEL   flush   FAR
 
 ; Загружаем селекторы в сегментные регистры
 
         mov     ax,SS_DESCR
         mov     ss,ax
         mov     ax,DS_DESCR
         mov     ds,ax
 
 ; Разрешаем прерывания
 
         sti
 
         ret
 ENDP    set_pmode
 
 ; --------------------------------
 ; Возврат в реальный режим
 ; --------------------------------
 
 DATASEG
 
 ; Пустой дескриптор для выполнения возврата
 ; процессора в реальный режим через перевод
 ; его в состояние отключения.
 
 null_idt idt_struc <> 
 
 CODESEG
 
 PROC    set_rmode       NEAR
 
         mov     [real_sp],sp
 
 ; Переводим процессор в состояние отключения,
 ; это эквивалентно аппаратному сбросу, но
 ; выполняется быстрее.
 ; Сначала мы загружаем IDTR нулями, затем
 ; выдаём команду прерывания.
 
         lidt    [FWORD null_idt]
         int     3
 
 ; Это старый способ сброса процессора через
 ; контроллер клавиатуры.
 ;       mov     al,SHUT_DOWN
 ;       out     STATUS_PORT,al
 
 rwait:
         hlt
         jmp     rwait
 
 LABEL   shutdown_return FAR
 
         in      al,INT_MASK_PORT
         and     al,0
         out     INT_MASK_PORT,al
 
         mov     ax,DGROUP
         mov     ds,ax
         assume  ds:DGROUP
 
         cli
         mov     ss,[real_ss]
         mov     sp,[real_sp]
         mov     ax,000dh
         out     CMOS_PORT,al
         sti
         mov     es,[real_es]
         call    disable_a20
         ret
 ENDP    set_rmode
 
 
 ; -------------------------------------------------
 ; Обработка исключений
 ; -------------------------------------------------
 
 ; Обработчики исключений. Записываем в AX номер
 ; исключения и передаём управление процедуре
 ; shutdown
 
 LABEL   exc_00  WORD
         mov     ax,0
         jmp     shutdown
 LABEL   exc_01  WORD
         mov     ax,1
         jmp     shutdown
 LABEL   exc_02  WORD
         mov     ax,2
         jmp     shutdown
 LABEL   exc_03  WORD
         mov     ax,3
         jmp     shutdown
 LABEL   exc_04  WORD
         mov     ax,4
         jmp     shutdown
 LABEL   exc_05  WORD
         mov     ax,5
         jmp     shutdown
 LABEL   exc_06  WORD
         mov     ax,6
         jmp     shutdown
 LABEL   exc_07  WORD
         mov     ax,7
         jmp     shutdown
 LABEL   exc_08  WORD
         mov     ax,8
         jmp     shutdown
 LABEL   exc_09  WORD
         mov     ax,9
         jmp     shutdown
 LABEL   exc_0A  WORD
         mov     ax,0ah
         jmp     shutdown
 LABEL   exc_0B  WORD
         mov     ax,0bh
         jmp     shutdown
 LABEL   exc_0C  WORD
         mov     ax,0ch
         jmp     shutdown
 LABEL   exc_0D  WORD
         mov     ax,0dh
         jmp     shutdown
 LABEL   exc_0E  WORD
         mov     ax,0eh
         jmp     shutdown
 LABEL   exc_0F  WORD
         mov     ax,0fh
         jmp     shutdown
 LABEL   exc_10  WORD
         mov     ax,10h
         jmp     shutdown
 LABEL   exc_11  WORD
         mov     ax,11h
         jmp     shutdown
 LABEL   exc_12  WORD
         mov     ax,12h
         jmp     shutdown
 LABEL   exc_13  WORD
         mov     ax,13h
         jmp     shutdown
 LABEL   exc_14  WORD
         mov     ax,14h
         jmp     shutdown
 LABEL   exc_15  WORD
         mov     ax,15h
         jmp     shutdown
 LABEL   exc_16  WORD
         mov     ax,16h
         jmp     shutdown
 LABEL   exc_17  WORD
         mov     ax,17h
         jmp     shutdown
 LABEL   exc_18  WORD
         mov     ax,18h
         jmp     shutdown
 LABEL   exc_19  WORD
         mov     ax,19h
         jmp     shutdown
 LABEL   exc_1A  WORD
         mov     ax,1ah
         jmp     shutdown
 LABEL   exc_1B  WORD
         mov     ax,1bh
         jmp     shutdown
 LABEL   exc_1C  WORD
         mov     ax,1ch
         jmp     shutdown
 LABEL   exc_1D  WORD
         mov     ax,1dh
         jmp     shutdown
 LABEL   exc_1E  WORD
         mov     ax,1eh
         jmp     shutdown
 LABEL   exc_1F  WORD
         mov     ax,1fh
         jmp     shutdown
 
 DATASEG
 
 exc_msg db "Exception ...., ....:.... code ..... Press any key... "
 
 CODESEG
 
 ; -----------------------------------------------
 ; Вывод на экран номера исключения, кода ошибки,
 ; дампа регистров и возврат в реальный режим.
 ; -----------------------------------------------
 
 PROC    shutdown        NEAR
 
         call    rdump   ; дамп регистров процессора
 
         push    ax
         call    beep    ; звуковой сигнал
 
 ; Выводим сообщение об исключении
 
         mov     ax,[vir_crt]
         mov     es,ax
         mov     bx,1d
         mov     ax,4
         mov     si,OFFSET exc_msg
         mov     dh,74h
         mov     cx, SIZE exc_msg
         call    writexy
         pop     ax
 
         mov     bx, 040bh       ; номер исключения
         call    Print_Word
 
         pop     ax
         mov     bx, 0420h       ; код ошибки
         call    Print_Word
 
         pop     ax
         mov     bx, 0416h       ; смещение
         call    Print_Word
 
         pop     ax
         mov     bx, 0411h       ; селектор
         call    Print_Word
 
         call    set_rmode       ; возвращаемся в реальный режим
 
         mov     ax, 0   ; ожидаем нажатия на клавишу
         int     16h
 
         mov     bh, 07h
         call    clrscr
         mov     ah,4Ch
         int     21h
 
 ENDP    shutdown
 
 ; -------------------------------------------------
 ; Перепрограммирование контроллера прерываний
 ;       На входе: DX - порт контроллера прерывания
 ;                 AH - начальный номер прерывания
 ; -------------------------------------------------
 
 PROC    set_int_ctrlr   NEAR
 
         mov     al,11
         out     dx,al
         jmp     SHORT $+2
         mov     al,ah
         inc     dx
         out     dx,al
         jmp     SHORT $+2
         mov     al,4
         out     dx,al
         jmp     SHORT $+2
         mov     al,1
         out     dx,al
         jmp     SHORT $+2
         mov     al,0ff
         out     dx,al
         dec     dx
         ret
 ENDP    set_int_ctrlr
 
 ; -------------------------------
 ; Разрешение линии A20
 ; -------------------------------
 
 PROC    enable_a20      NEAR
         mov     al,A20_PORT
         out     STATUS_PORT,al
         mov     al,A20_ON
         out     KBD_PORT_A,al
         ret
 ENDP    enable_a20
 
 ; -------------------------------
 ; Запрещение линии A20
 ; -------------------------------
 
 PROC    disable_a20     NEAR
         mov     al,A20_PORT
         out     STATUS_PORT,al
         mov     al,A20_OFF
         out     KBD_PORT_A,al
         ret
 ENDP    disable_a20
 
 ; ---------- Обработчик аппаратных прерываний IRQ2-IRQ7
 
 PROC    dummy_iret0      NEAR
         push    ax
 
 ; Посылаем сигнал конца прерывания в первый контроллер 8259A
 
         mov     al,EOI
         out     MASTER8259A,al
         pop     ax
         iret
 ENDP    dummy_iret0
 
 ; ---------- Обработчик аппаратных прерываний IRQ8-IRQ15
 
 PROC    dummy_iret1      NEAR
         push    ax
 
 ; Посылаем сигнал конца прерывания в первый 
 ; и второй контроллеры 8259A
 
         mov     al,EOI
         out     MASTER8259A,al
         out     SLAVE8259A,al
         pop     ax
         iret
 ENDP    dummy_iret1
 
 ; ------------------------------------------
 ; Процедура выдаёт короткий звуковой сигнал
 ; ------------------------------------------
 
 PROC    beep    NEAR
         push    ax bx cx
 
         in      al,KBD_PORT_B
         push    ax
         mov     cx,80
 beep0:
         push    cx
         and     al,11111100b
         out     KBD_PORT_B,al
         mov     cx,60
 idle1:
         loop    idle1
         or      al,00000010b
         out     KBD_PORT_B,al
         mov     cx,60
 idle2:
         loop    idle2
         pop     cx
         loop    beep0
 
         pop     ax
         out     KBD_PORT_B,al
 
         pop     cx bx ax
         ret
 ENDP    beep
 
 ; ------------------------------------------------
 ; Процедура задерживает выполнение программы
 ; на некоторое время, зависящее от быстродействия
 ; процессора.
 ; ------------------------------------------------
 
 PROC    pause           NEAR
         push    cx
         mov     cx,10
 ploop0:
         push    cx
         xor     cx,cx
 ploop1:
         loop    ploop1
         pop     cx
         loop    ploop0
         pop     cx
         ret
 ENDP    pause
 
 
 ; ------------------------------------------
 ;       Процедуры для работы с клавиатурой
 ; ------------------------------------------
 
 
 DATASEG
 
         key_flag        db      0
         key_code        dw      0
         ext_scan        db      0
         keyb_status     dw      0
 
 CODESEG
 
 ; ----------------------------------------------
 ; Обработчик аппаратного прерывания клавиатуры
 ; ----------------------------------------------
 
 PROC    Keyb_int         NEAR
 
         call    beep    ; выдаём звуковой сигнал
 
         push    ax
         mov     al, [ext_scan]  ; расширенный скан-код
         cmp     al, 0           ; или обычный ?
         jz      normal_scan1
 
 ; --------- обработка расширенного скан-кода -------------
 
         cmp     al, 0e1h                ; это клавиша <Pause>?
         jz      pause_key
 
         in      al, 60h         ; вводим скан-код
 
         cmp     al, 2ah         ; игнорируем префикс 2Ah
         jz      intkeyb_exit_1
 
         cmp     al, 0aah                ; игнорируем отпускание
         jz      intkeyb_exit_1  ; клавиш
 
         mov     ah, [ext_scan]  ; записываем скан-код и
         call    Keyb_PutQ               ; расширенный скан-код
                                         ; в "очередь", состоящую
                                         ; из одного слова
 
         mov     al, 0           ; сбрасываем признак
         mov     [ext_scan], al  ; получения расширенного
         jmp     intkeyb_exit    ; скан-кода
 
 pause_key:                      ; обработка клавиши <Pause>
 
         in      al, 60h         ; вводим скан-код
         cmp     al, 0c5h                ; если это код <Pause>,
         jz      pause_key1      ; записываем его в очередь,
         cmp     al, 45h         ; иначе игнорируем
         jz      pause_key1
         jmp     intkeyb_exit
 
 pause_key1:
         mov     ah, [ext_scan]  ; запись в очередь
         call    Keyb_PutQ               ; кода клавиши <Pause>
 
         mov     al, 0           ; сбрасываем признак 
         mov     [ext_scan], al  ; получения расширенного
         jmp     intkeyb_exit    ; скан-кода
 
 ; --------- обработка обычного скан-кода -------------
 
 normal_scan1:
 
         in      al, 60h         ; вводим скан-код
 
         cmp     al, 0feh                ; игнорируем FEh
         jz      intkeyb_exit
 
         cmp     al, 0e1h                ; расширенный скан-код?
         jz      ext_key         ; если да, то на обработку
                                         ; расширенного скан-кода
         cmp     al, 0e0h        
         jnz     normal_scan
 
 ext_key:
         mov     [ext_scan], al  ; устанавливаем признак
         jmp     intkeyb_exit    ; расширенного скан-кода
 
 ; Сброс признака расширенного скан-кода и выход
 
 intkeyb_exit_1:
         mov     al, 0
         mov     [ext_scan], al
         jmp     intkeyb_exit
 
 ; Запись нормального скан-кода в очередь и выход
         
 normal_scan:
         mov     ah, 0
         call    Keyb_PutQ
 
 intkeyb_exit:
 
         in      al, 61h         ; разблокируем клавиатуру
         mov     ah, al
         or      al, 80h
         out     61h, al
         xchg    ah, al
         out     61h, al
 
         mov     al,EOI          ; посылаем сигнал конца
         out     MASTER8259A,al  ; прерывания
 
         pop     ax
         sti
         iret
 ENDP    Keyb_int
 
 
 ; ---------------------------------------------------
 ; Запись скан-кода и расширенного скан-кода в
 ; "буфер", состоящий из одного слова.
 ; ---------------------------------------------------
 
 PROC    Keyb_PutQ       NEAR
 
         push    ax
         mov     [key_code], ax  ; записываемый код
 
 ; ------- Обрабатываем переключающие клавиши ---------
 
         cmp     ax, 002ah               ; L_SHIFT down
         jnz     @@kb1
         mov     ax, [keyb_status]
         or      ax, L_SHIFT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb1:
         cmp     ax, 00aah               ; L_SHIFT up
         jnz     @@kb2
         mov     ax, [keyb_status]
         and     ax, NL_SHIFT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb2:
         cmp     ax, 0036h               ; R_SHIFT down
         jnz     @@kb3
         mov     ax, [keyb_status]
         or      ax, R_SHIFT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb3:
         cmp     ax, 00b6h               ; R_SHIFT up
         jnz     @@kb4
         mov     ax, [keyb_status]
         and     ax, NR_SHIFT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb4:
         cmp     ax, 001dh               ; L_CTRL down
         jnz     @@kb5
         mov     ax, [keyb_status]
         or      ax, L_CTRL
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb5:
         cmp     ax, 009dh               ; L_CTRL up
         jnz     @@kb6
         mov     ax, [keyb_status]
         and     ax, NL_CTRL
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb6:
         cmp     ax, 0e01dh      ; R_CTRL down
         jnz     @@kb7
         mov     ax, [keyb_status]
         or      ax, R_CTRL
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb7:
         cmp     ax, 0e09dh      ; R_CTRL up
         jnz     @@kb8
         mov     ax, [keyb_status]
         and     ax, NR_CTRL
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb8:
         cmp     ax, 0038h               ; L_ALT down
         jnz     @@kb9
         mov     ax, [keyb_status]
         or      ax, L_ALT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb9:
         cmp     ax, 00b8h               ; L_ALT up
         jnz     @@kb10
         mov     ax, [keyb_status]
         and     ax, NL_ALT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb10:
         cmp     ax, 0e038h      ; R_ALT down
         jnz     @@kb11
         mov     ax, [keyb_status]
         or      ax, R_ALT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb11:
         cmp     ax, 0e0b8h      ; R_ALT up
         jnz     @@kb12
         mov     ax, [keyb_status]
         and     ax, NR_ALT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb12:
         cmp     ax, 003ah               ; CAPS_LOCK up
         jnz     @@kb13
         mov     ax, [keyb_status]
         xor     ax, CAPS_LOCK
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb13:
         cmp     ax, 00bah               ; CAPS_LOCK down
         jnz     @@kb14
         jmp     keyb_putq_exit
 @@kb14:
         cmp     ax, 0046h               ; SCR_LOCK up
         jnz     @@kb15
         mov     ax, [keyb_status]
         xor     ax, SCR_LOCK
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb15:
         cmp     ax, 00c6h               ; SCR_LOCK down
         jnz     @@kb16
         jmp     keyb_putq_exit
 @@kb16:
         cmp     ax, 0045h               ; NUM_LOCK up
         jnz     @@kb17
         mov     ax, [keyb_status]
         xor     ax, NUM_LOCK
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb17:
         cmp     ax, 00c5h               ; NUM_LOCK down
         jnz     @@kb18
         jmp     keyb_putq_exit
 @@kb18:
         cmp     ax, 0e052h      ; INSERT up
         jnz     @@kb19
         mov     ax, [keyb_status]
         xor     ax, INSERT
         mov     [keyb_status], ax
         jmp     keyb_putq_exit
 @@kb19:
         cmp     ax, 0e0d2h      ; INSERT down
         jnz     @@kb20
         jmp     keyb_putq_exit
 @@kb20:
 
         test    ax, 0080h               ; фильтруем отжатия клавиш
         jnz     keyb_putq_exit
 
         mov     al, 0ffh                ; устанавиваем признак
         mov     [key_flag], al  ; готовности для чтения
                                         ; символа из "буфера"
 keyb_putq_exit:
         pop     ax
         ret
 ENDP    Keyb_PutQ
 
 ; -----------------------------------------------------
 ; Программное прерывание, предназначенное для чтения
 ; символа из буфера клавиатуры. По своим функциям
 ; напоминает прерывание INT 16h реального режима.
 ; В AX возвращается скан-код нажатой клавиши,
 ; в BX - состояние переключающих клавиш.
 ; -----------------------------------------------------
 
 PROC    Int_30h_Entry    NEAR
 
         push    dx                      ; запрещаем прерывания и
         cli                             ; сбрасываем признак 
         mov     al, 0           ; готовности скан-кода
         mov     [key_flag], al  ; в буфере клавиатуры
 
 ; Ожидаем прихода прерывания от клавиатуры.
 ; Процедура клавиатурного прерывания установит
 ; признак в переменной key_flag.
 
 keyb_int_wait:
         sti                             ; разрешаем прерывания
         nop                             ; ждём прерывание
         nop
         cli                             ; запрещаем прерывания
         mov     al, [key_flag]  ; и опрашиваем флаг
         cmp     al, 0           ; готовности скан-кода
         jz      keyb_int_wait
 
         mov     al, 0           ; сбрасываем флаг
         mov     [key_flag], al  ; готовности
 
         mov     ax, [key_code]           ; записываем скан-код
         mov     bx, [keyb_status]       ; и состояние переключающих
                                                  ; клавиш
 
         sti                             ; разрешаем прерывания
         pop     dx
         iret
 ENDP    Int_30h_Entry
 
 
 ; -------------------------------------------
 ;       TIMER section
 ; -------------------------------------------
 
 DATASEG
 
         timer_cnt dw    0
 
 CODESEG
 
 PROC    Timer_int        NEAR
         cli
         push    ax
 
 ; Увеличиваем содержимое счётчика времени
 
         mov     ax, [timer_cnt]
         inc     ax
         mov     [timer_cnt], ax
 
 ; Примерно раз в секунду выдаём звуковой сигнал
 
         test    ax, 0fh
         jnz     timer_exit
 
         call    beep
 
 timer_exit:
 
 ; Посылаем команду конца прерывания
 
         mov     al,EOI
         out     MASTER8259A,al
 
         pop     ax
         sti
         iret
 ENDP    Timer_int
 
 
 ; --------------------------------------------------
 ; Процедуры обслуживания видеоконтроллера
 ; --------------------------------------------------
 
 DATASEG
         columns db      80d
         rows    db      25d
         rl_crt  dw      COLOR_SEG
         vir_crt dw      CRT_DESCR
         curr_line       dw      0d
         text_buf        db      "          "
 CODESEG
 
 ; -----------------------------------------
 ; Определение адреса видеопамяти
 ; -----------------------------------------
 
 PROC    set_crt_base    NEAR
         mov     ax,40
         mov     es,ax
         mov     bx,[WORD es:4a]
         mov     [columns],bl
         mov     bl,[BYTE es:84]
         inc     bl
         mov     [rows],bl
         mov     bx,[WORD es:PORT_6845]
         cmp     bx,COLOR_PORT
         je      color_crt
         mov     [rl_crt],MONO_SEG
         mov     [vir_crt],MDA_DESCR
 color_crt:
         ret
 ENDP    set_crt_base
 
 ; -------------------------------------
 ; Запись строки в видеопамять
 ; -------------------------------------
 
 PROC    writexy         NEAR
         push    si
         push    di
         mov     dl,[columns]
         mul     dl
         add     ax,bx
         shl     ax,1
         mov     di,ax
         mov     ah,dh
 write_loop:
         lodsb   
         stosw
         loop    write_loop      
         pop     di
         pop     si
         ret
 ENDP    writexy
 
 ; ---------------------------------------
 ; Стирание экрана (в реальном режиме)
 ; ---------------------------------------
 
 PROC    clrscr          NEAR
         xor     cx,cx   
         mov     dl,[columns]
         mov     dh,[rows]
         mov     ax,0600
         int     10
         ret
 ENDP    clrscr
 
 DATASEG
 
 hello_msg db " Protected mode monitor *TINY/OS*,
                  v.1.1 for CPU 80286  ¦ © Frolov A.V., 1992 "
 
 CODESEG
 
 ; ------------------------------------
 ; Вывод начального сообщения 
 ; в защищённом режиме
 ; ------------------------------------
 
 PROC    write_hello_msg NEAR
         mov     ax,[vir_crt]
         mov     es,ax
         mov     si,OFFSET hello_msg
         mov     bx,0
         mov     ax,[curr_line]
         inc     [curr_line]
         mov     cx,SIZE hello_msg
         mov     dh,30h
         call    writexy
         call    beep
         ret
 ENDP    write_hello_msg
 
 ; ----------------------------------------------
 ; Процедура выводит на экран содержимое AX
 ;       (x,y) = (bh, bl)
 ; ----------------------------------------------
 
 PROC Print_Word near
 
         push ax
      push bx
         push dx
 
         push ax
         mov cl,8
         rol ax,cl
         call Byte_to_hex
         mov     [text_buf], dh
         mov     [text_buf+1], dl
 
         pop ax
         call Byte_to_hex
         mov     [text_buf+2], dh
         mov     [text_buf+3], dl
 
         mov     si, OFFSET text_buf
         mov     dh, 70h
         mov     cx, 4
         mov     al, bh
         mov     ah, 0
 
         mov     bh, 0
         call    writexy
         
         pop dx
         pop bx
         pop ax
         ret
 ENDP Print_Word
 
 
 DATASEG
 
 tabl db '0123456789ABCDEF'
 
 CODESEG
 
 ; -----------------------------------------
 ; Преобразование байта в шестнадцатеричный
 ; символьный формат
 ; al - входной байт
 ; dx - выходное слово
 ; -----------------------------------------
 
 PROC Byte_to_hex near
 
         push    cx
         push    bx
 
         mov     bx, OFFSET tabl
 
         push    ax
         and     al,0fh
         xlat
         mov     dl,al
 
         pop     ax
         mov     cl,4
         shr     al,cl
         xlat
         mov     dh,al
 
         pop bx
         pop cx
         ret
 
 ENDP Byte_to_hex
 
 DATASEG
 
 reg_title       db " CS    IP    AX    BX    CX    DX    SP    BP    SI    DI   "
 ;                    ....  ....  ....  ....  ....  ....  ....  ....
 sreg_title      db " DS    ES    SS    FLAGS                                    "
 ;                    ....  ....  ....  ....  ....  ....  ....  ....
 
 CODESEG
 
 ; ------------------------------------------------
 ; Вывод на экран содержимого регистров процессора
 ; ------------------------------------------------
 
 PROC    rdump NEAR
 
         pushf
         pusha
 
         mov     di, es
 
         mov     ax,[vir_crt]
         mov     es,ax
         mov     si,OFFSET reg_title
         mov     bx,1                    ; (X,Y) = (AX,BX)
         mov     ax,6
         mov     cx,SIZE reg_title
         mov     dh,1fh                  ; чёрный на голубом фоне
         call    writexy
 
 ; Выводим содержимое всех регистров
 
         mov     ax,cs          ; cs
         mov     bx, 0702h
         call    Print_Word
 
         mov     bp, sp
 
         mov     ax, [bp+18d]     ; ip
         mov     bx, 0708h
         call Print_Word
 
         mov     bx, 070eh
         mov     ax,[bp+14d]     ; ax
         call    Print_Word
 
         mov     bx, 0714h
         mov     ax,[bp+8d]      ; bx
         call Print_Word
 
         mov     bx, 071ah
         mov     ax,[bp+12d]     ; cx
         call Print_Word
 
         mov     bx, 0720h
         mov     ax,[bp+10d]     ; dx
         call Print_Word
 
         mov     ax,bp
         add     ax,20d          ; sp
         mov     bx, 0726h
         call Print_Word
 
         mov     ax,[bp+4d]      ; bp
         mov     bx, 072ch
         call Print_Word
 
         mov     bx, 0732h
         mov     ax,[bp+2]       ; si
         call Print_Word
 
         mov     bx, 0738h
         mov     ax, [bp]        ; di
         call Print_Word
 
         mov     si,OFFSET sreg_title
         mov     bx,1
         mov     ax,8
         mov     cx,SIZE sreg_title
         mov     dh,1fh
         call    writexy
 
         mov     bx, 0902h
         mov     ax, ds          ; ds
         call Print_Word
 
         mov     bx, 0908h
         mov     ax, di          ; es
         call Print_Word
 
         mov     bx, 090eh
         mov     ax,ss           ; ss
         call Print_Word
 
         mov     bx, 0914h
         mov     ax, [bp+16d]    ; flags
         call Print_Word
 
 ; Восстанавливаем содержимое регистров
 
         popa
         popf
         ret
 ENDP    rdump
 
 
 CSEG_SIZE       = ($ - start)
 
 DATASEG
 
 DSEG_SIZE       = ($ - DSEG_BEG)
 
 wrong   db      ?
         END     start
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье