Прерывания (Interrupt) – предусмотренный в системе механизм изменения естественного порядка выполнения программ с целью обработки возникающих событий и для организации взаимодействия программ. В однозадачных системах прерывания можно рассматривать как элемент многозадачности.
В архитектуре ПЭВМ на основе процессоров x86 (реальный режим) выделяются внутренние прерывания процессора – исключения (exception), возникающие при исполнении инструкций, маскируемые и немаскируемые внешние прерывания. Среди исключений выделяются вызванные специальными инструкциями генерации прерываний int; их принято выделять как программные прерывания. Всего определено 256 прерываний, назначение части из которых документировано и закреплено за определенными источниками или программами-обработчиками, но большинство считаются зарезервированными для использования ОС и прикладными программами.
Для любых прерываний обработка их непосредственно процессором заканчивается выборкой адреса обработчика этого прерывания и передачей ему управления. Адреса обработчиков принято назвать векторами прерываний, они хранятся в таблице векторов, которая занимает первый физический килобайт адресного пространства x86.
Обработчик прерывания – часть кода, подпрограмма, ассоциированная с данным прерыванием и получающая управление при его возникновении. С одним прерыванием может ассоциироваться более одного обработчика, которые в таком случае образуют каскад (цепочку).
Вызов обработчика отличается от обращения к обычным подпрограммам тем, что в стеке помимо адреса возврата сохраняется также и регистр флагов. При выходе из обработчика его надо или восстановить из стека (инструкция процессора iret или retf), или отбросить (инструкция возврата с очисткой стека ret 2). Вызов обработчика и возврат из него всегда дальние (far) – адресá обработчика и точки возврата включают и смещение, и сегмент.
Резидентная программа, или TSR (Terminate-and-Stay-Resident) – в отличие от обычной, называемой транзитной, целиком или частично остается в памяти после того, как возвращает управление системе. Очевидно, выделение резидентных программ имеет смысл только в однозадачных системах.
Как правило, для резидентных программ установка обработчиков и перехват прерываний с целью их обработки и/или модификации – основной способ взаимодействия с другими программами.
В общем случае обработчик должен:
– сохранить текущий контекст выполнения, так как его вызов может произойти в любой момент времени и на фоне любой другой программы (если иметь в виду обработчик в резидентной программе);
– подготовить контекст для своего выполнения, в том числе проверить условия активизации;
– выполнить специфические функции, связанные с назначением обработчика;
– возможно, обратиться к сохраненному вектору прерывания;
– восстановить контекст, включая приведение в требуемое состояние аппаратных средств и контроллера прерываний, и вернуть управление.
Можно выделить несколько основных схем включения обработчика прерывания в каскад (цепочку).
1. Замещение – новый обработчик полностью перекрывает старый.
New_Handler PROC FAR
push …;сохранение контекста
…;функции обработчика
pop …;восстановление контекста
iret;возврат из обработчика
New_Handler ENDP
2. Предобработка – новый обработчик выполняет свои функции (например проверяет условия и модифицирует параметры) и затем передает управление старому.
New_Handler PROC FAR
push …;сохранение контекста
…;функции обработчика
pop …;восстановление контекста
jmp dword ptr cs:old_handler_off;передача управления старому обработчику
New_Handler ENDP
3. Постобработка – новый обработчик обращается к старому, вновь получает управление после его завершения и затем выполняет свои функции (например, модифицирует результат)
New_Handler PROC FAR
push …;сохранение контекста
…;функции обработчика
pop …;восстановление контекста
pushf;эмуляция «обычного» вызова обработчика
call dword ptr cs:old_handler_off; обращение к старому обработчику
iret;возврат из обработчика
New_Handler ENDP
4. Комбинированный – например, проверка условий, в зависимости от успешности которой выполняются подготовка параметров, вызов старого обра–ботчика и модификация результатов.
New_Handler PROC FAR
push …;сохранение контекста
…;функции обработчика
pop …;восстановление контекста
pushf;эмуляция «обычного» вызова обработчика
call dword ptr cs:old_handler_off;обращение к старому обработчику
push …
…;продолжение функций обработчика
pop …
iret;возврат из обработчика
New_Handler ENDP
Кроме показанных, есть более изощренные способы организации пере–ходов и вызовов процедур. Например, можно разместить ячейки, хранящие адрес, непосредственно в коде процедуры-обработчика, предварив их кодом инструкции call far или jmp far с прямой адресацией. Бóльший практический интерес представляет следующая конструкция:
pushf
push <segment_to_jmp>
push <offset_to_jmp>
retf
Её результат эквивалентен «обычному» jmp far, но в отличие от косвенной адресации точки перехода не требуется обязательное наличие переменной, содержащей готовый адрес. Загружаемые в стек компоненты адреса могут быть константами, вычисляемыми значениями, находиться в регистрах и так далее в любых сочетаниях. Такой прием полезен при ограничениях на использование переменных: например, код может находиться в ПЗУ и не иметь сегмента данных, стек же, очевидно, всегда будет только в ОЗУ. Аналогичным образом получается и эквивалент инструкции call far.
Если новый обработчик был установлен на внешнее прерывание и нет передачи управления старому обработчику, то перед выходом необходимо разрешить работу внешнего контроллера прерываний. В противном случае новые прерывания останутся заблокированы.
mov al, 20h
out 20h, al
Однако если обращение к старому обработчику есть, то он уже выполняет эту операцию, и дублировать ее в своем коде не следует.
С осторожностью следует относиться и к разрешению или запрету прерываний посредством флага IF. Стандартно он сбрасывается перед передачей управления обработчику, а после завершения восстанавливается предыдущее значение всего регистра флагов. Аккуратность требуется, если сохраненные флаги игнорируются (возврат командой ret 2), а новое содержимое регистра FLAGS формируется самим обработчиком.
MS-DOS обеспечивает следующие основные функции прерывания int 21h для работы с прерываниями, их обработчиками и резидентными программами.
AH = 25h – установка вектора прерывания. Вход: AL – номер вектора, DS:DX – адрес нового устанавливаемого обработчика.
AH = 35h – получение вектора прерывания. Вход: AL – номер вектора. Выход: ES:BX – адрес текущего обработчика.
AH = 31h – завершение программы с сохранением ее резидентной. Вход: AL – код возврата, DX – размер оставляемой в памяти части программы (в параграфах).
Таким образом, получаем следующий «обобщенный» каркас резидентной программы, содержащей обработчик прерывания.
; начало резидентной части
Start:
jmp Init
; область переменных
old_handler_off DW?
old_handler_seg DW?
; код обработчиков
New_Handler PROC FAR
…
call dword ptr cs:old_handler_off
…
iret
New_Handler ENDP
; конец резидентной части
; транзитная часть – секция инициализации
Init:
…;другие подготовительные операции
; получение и сохранение адреса старого обработчика
mov ah, 35h
mov al, <номер_вектора>
int 21h
mov cs:old_handler_off, bx
mov cs:old_handler_seg, es
; установка нового обработчика
mov ah, 25h
mov al, <номер_вектора>
mov dx, seg New_Handler
mov ds, dx
lea dx, New_Handler
int 21h
…;другие завершающие операции
; вычисление размера резидентной части и завершение программы
; с сохранением резидентной части в памяти ( TSR)
mov ax, 3100h
mov dx, offset Init
add dx, 0Fh
shr dx, 1
shr dx, 1
shr dx, 1
shr dx, 1
int 21h
END Init
Метка Start: и инструкция jmp в начале программы избыточны – при явном указании метки при директиве END выполнение программы будет начинаться именно с нее. Однако этот элемент оформления может быть легко забыт, тогда стартовой точкой программы становится первая объявленная метка. Кроме того, в формате исполняемого файла COM (модель памяти TINY) возможности управлять начальной меткой нет, управление всегда передается на начало программы. Одновременно для COM-формата потребуется директива ORG 100h для пропуска сегмента PSP.
Драйверы – особый тип резидентных программ. Первоначально предназначались для управления внешними устройствами, в настоящее время обеспечивают функции ввода-вывода и работу логических устройств, как являющихся отображением реальных, так и виртуальных. В различных ОС для драйверов существуют специфические технологии и требования. Драйверы MS‑DOS достаточно просты и представляют собой резидентные программы, загружаемые ранее любых прикладных программ (включая командный интерпретатор), имеющие определенную внутреннюю структуру и поддерживающие определенный интерфейс.
Интерфейс с драйвером опосредован системой: прикладная программа обращается к устройству, система транслирует это обращение в запрос к драйверу, который исполняет его и возвращает результаты. Для прикладной программы все преобразования прозрачны. Драйвер включает в себя заголовок, очередь запросов, программу стратегии и программу прерывания. Заголовок – структура с фиксированным набором полей, расположенная в начале кода драйвера и исчерпывающим образом описывающая его. Передаваемый драйверу запрос в виде структуры временно сохраняется стратегией в одноместной очереди, затем управление передается программе прерывания, которая и обеспечивает его выполнение. Перечень функций драйвера фиксирован: инициализация, чтение, запись и так далее, но поддерживаться могут не все из них. Адреса соответствующих процедур сводятся в таблицу в заголовке драйвера.
Кроме интерфейса запросов, драйвер при инициализации, подобно обычным резидентным программам, может устанавливать обработчики прерываний.
В MS-DOS блочными считаются устройства внешней памяти, на которых может быть размещена файловая система. Прочие устройства относятся к символьным и выглядят как файлы, не связанные ни с каким логическим диском. Блочные драйверы существенно сложнее символьных.
Контрольные вопросы
1. Понятия прерывания и его обработчика.
2. Таблица векторов прерываний.
3. Особенности обработки аппаратных и программных прерываний.
4. Основные прерывания BIOS и операционной системы DOS.
5. Способы перехвата и обработки (перекрытия) прерываний.
6. Каркас обработчиков прерываний.
7. Резидентные программы.
8. Каркас резидентной программы.
9. Обработка прерывания клавиатуры, таймера.
10. Обработка программных прерываний.
Варианты заданий
4.3.1. Перекрыть прерывание клавиатуры и сделать так, чтобы одна из букв (например “a”) подменялась другой (например “b”).
4.3.2. Перекрыть прерывание клавиатуры и сделать так, чтобы все согласные буквы игнорировались.
4.3.3. Перекрыть прерывание клавиатуры и сделать так, чтобы все гласные буквы заменялись на следующие по алфавиту.
4.3.4. Перекрыть прерывание клавиатуры и сделать так, чтобы вместо каждой введенной цифры вводилось две. Например, вместо “1” получалось “11”.
4.3.5. Перекрыть прерывание клавиатуры и сделать так, чтобы все введенные цифры заменялись на следующие по порядку.
4.3.6. Перекрыть прерывание клавиатуры и сделать так, чтобы регистр вводимых букв менялся с нижнего на верхний (или наоборот).
4.3.7. Перекрыть прерывание клавиатуры и сделать так, чтобы все вводимые цифры суммировались в переменной summa.
4.3.8. Перекрыть прерывание клавиатуры и сделать так, чтобы пробелы игнорировались.
4.3.9. Перекрыть прерывание клавиатуры и сделать так, чтобы вместо пробела вводился символ ввода.
4.3.10. Перекрыть прерывание таймера и сделать так, чтобы с заданным периодом (например, каждые пять секунд) на экран выводился заданный текст (например “go”).
4.3.11. Модифицировать прерывание 21h так, чтобы при выводе строки на экран функцией 09h регистр букв в строке менялся с верхнего на нижний (или наоборот).
4.3.12. Модифицировать прерывание 21h так, чтобы при выводе строки на экран функцией 09h в строке подсчитывалась сумма всех цифр, и эта сумма выводилась на экране вместо строки.
4.3.13. Модифицировать прерывание 21h так, чтобы при выводе строки на экран функцией 09h вместо строки показывалось число слов в ней.
4.3.14. Модифицировать прерывание 21h так, чтобы при вводе строки функцией 0Ah во введенной строке удалялись все пробелы.
4.3.15. Написать драйвер виртуального символьного устройства – генератор псевдослучайной последовательности (повышенной сложности).
При загрузке драйвер инициализирует внутреннюю переменную случайным значением (можно использовать текущее время) и обновляет ее при каждом считывании и периодически – по истечении заданных интервалов времени. Алгоритм генерации псевдослучайной последовательности может быть выбран произвольно. При чтении из созданного устройства возвращается содержимое переменной. Необходимо обеспечить корректное считывание многобайтных значений.
16. Написать драйвер виртуального символьного устройства – текущие дата и время (повышенной сложности).
При считывании из созданного логического устройства драйвер получает дату и время, используя функции BIOS или DOS, и возвращает его программе, читающей из устройства.
Лабораторная работа №5
Сложные обработчики
и взаимодействие резидентных программ
Цели работы:
1) ознакомиться со сложными и нестандартными ситуациями, возникающими при разработке и функционировании обработчиков;
2) научиться решать разрешать коллизии при перекрытии прерываний и взаимодействии обработчиков;
3) научиться организовывать интерфейс с резидентными программами.