| [Назад] [Далее] | ![]() |
Прерывания от внешних устройств, или аппаратные прерывания — это то, что понимается под термином «прерывание». Внешние устройства (клавиатура, дисковод, таймер, звуковая карта и т.д.) подают сигнал, по которому процессор прерывает выполнение программы и передает управление на обработчик прерывания. Всего на персональных компьютерах используется 15 аппаратных прерываний, хотя теоретически возможности архитектуры позволяют довести их число до 64.
Рассмотрим их кратко в порядке убывания приоритетов (прерывание имеет более высокий приоритет, и это означает, что, пока не завершился его обработчик, прерывания с низкими приоритетами будут ждать своей очереди).
IRQ0 (INT 8) — прерывание системного таймера. Это прерывание вызывается 18,2 раза в секунду. Стандартный обработчик этого прерывания вызывает INT 1Ch при каждом вызове, так что, если программе необходимо только регулярно получать управление, а не перепрограммировать таймер, рекомендуется использовать прерывание 1Ch.
IRQ1 (INT 9) — прерывание клавиатуры. Это прерывание вызывается при каждом нажатии и отпускании клавиши на клавиатуре. Стандартный обработчик этого прерывания выполняет довольно много функций, начиная с перезагрузки по Ctrl-Alt-Del и заканчивая помещением кода клавиши в буфер клавиатуры BIOS.
IRQ2 — к этому входу на первом контроллере прерываний подключены аппаратные прерывания IRQ8 – IRQ15, но многие BIOS перенаправляют IRQ9 на INT 0Ah.
IRQ8 (INT 70h) — прерывание часов реального времени. Это прерывание вызывается часами реального времени при срабатывании будильника и если они установлены на генерацию периодического прерывания (в последнем случае IRQ8 вызывается 1024 раза в секунду).
IRQ9 (INT 0Ah или INT 71h) — прерывание обратного хода луча. Вызывается некоторыми видеоадаптерами при обратном ходе луча. Часто используется дополнительными устройствами (например, звуковыми картами, SCSI-адаптерами и т.д.).
IRQ10 (INT 72h) — используется дополнительными устройствами.
IRQ11 (INT 73h) — используется дополнительными устройствами.
IRQ12 (INT 74h) — мышь на системах PS используется дополнительными устройствами.
IRQ13 (INT 02h или INT 75h) — ошибка математического сопроцессора. По умолчанию это прерывание отключено как на FPU, так и на контроллере прерываний.
IRQ14 (INT 76h) — прерывание первого IDE-контроллера «операция завершена».
IRQ15 (INT 77h) — прерывание второго IDE-контроллера «операция завершена».
IRQ3 (INT 0Bh) — прерывание последовательного порта COM2 вызывается, если порт COM2 получил данные.
IRQ4 (INT 0Ch) — прерывание последовательного порта СОМ1 вызывается, если порт СОМ1 получил данные.
IRQ5 (INT 0Dh) — прерывание LPT2 используется дополнительными устройствами.
IRQ6 (INT 0Eh) — прерывание дисковода «операция завершена».
IRQ7 (INT 0Fh) — прерывание LPT1 используется дополнительными устройствами.
Самые полезные для программ аппаратные прерывания — прерывания системного таймера и клавиатуры. Так как их стандартные обработчики выполняют множество функций, от которых зависит работа системы, их нельзя заменять полностью, как мы делали это с обработчиком INT 5. Следует обязательно вызвать предыдущий обработчик, передав ему управление следующим образом (если его адрес сохранен в переменной old_handler, как в предыдущих примерах):
pushf
call old_handler
Эти две команды выполняют действие, аналогичное команде INT (сохранить флаги в стеке и передать управление подобно команде call), так что, когда обработчик завершится командой IRET, управление вернется в нашу программу. Так удобно вызывать предыдущий обработчик в начале собственного. Другой способ — простая команда jmp:
jmp cs:old_handler
приводит к тому, что, когда старый обработчик выполнит команду IRET, управление сразу же перейдет к прерванной программе. Этот способ применяют, если нужно, чтобы сначала отработал новый обработчик, а потом он передал бы управление старому.
Посмотрим, как работает перехват прерывания от таймера на следующем примере:
; timer.asm
; демонстрация перехвата прерывания системного таймера: вывод текущего времени
; в левом углу экрана
.model tiny
.code
.186 ; для pusha/popa и сдвигов
org 100h
start proc near
; сохранить адрес предыдущего обработчика прерывания 1Ch
mov ax,351Ch ; АН = 35h, AL = номер прерывания
int 21h ; функция DOS: определить адрес обработчика
mov word ptr old_int1Ch,bx ; прерывания
mov word ptr old_int1Ch+2,es ; (возвращается в ES:BX)
; установить наш обработчик
mov ax,251Ch ; АН = 25h, AL = номер прерывания
mov dx,offset int1Ch_handler ; DS:DX - адрес обработчика
int 21h ; установить обработчик прерывания 1Ch
; здесь размещается собственно программа, например вызов command.com
mov ah,1
int 21h ; ожидание нажатия на любую клавишу
; конец программы
; восстановить предыдущий обработчик прерывания 1Ch
mov ax,251Ch ; АН = 25h, AL = номер прерывания
mov dx,word ptr old_int1Ch+2
mov ds,dx
mov dx,word ptr cs:old_int1Ch ; DS:DX - адрес обработчика
int 21h
ret
old_int1Ch dd ? ; здесь хранится адрес предыдущего обработчика
start_position dw 0 ; позиция на экране, в которую выводится текущее время
start endp
; обработчик для прерывания 1Ch
; выводит текущее время в позицию start_position на экране
; (только в текстовом режиме)
int1Ch_handler proc far
pusha ; обработчик аппаратного прерывания
push es ; должен сохранять ВСЕ регистры
push ds
push cs ; на входе в обработчик известно только
pop ds ; значение регистра CS
mov ah,02h ; Функция 02h прерывания 1Ah:
int 1Ah ; чтение времени из RTC,
jc exit_handler ; если часы заняты - в другой раз
; AL = час в BCD-формате
call bcd2asc ; преобразовать в ASCII,
mov byte ptr output_line[2],ah ; поместить их в
mov byte ptr output_line[4],al ; строку output_line
mov al,cl ; CL = минута в BCD-формате
call bcd2asc
mov byte ptr output_line[10],ah
mov byte ptr output_line[12],al
mov al,dh ; DH = секунда в BCD-формате
call bcd2asc
mov byte ptr output_line[16],ah
mov byte ptr output_line[18],al
mov cx,output_line_l ; число байт в строке - в СХ
push 0B800h
pop es ; адрес в видеопамяти
mov di,word ptr start_position ; в ES:DI
mov si,offset output_line ; адрес строки в DS:SI
cld
rep movsb ; скопировать строку
exit_handler:
pop ds ; восстановить все регистры
pop es
popa
jmp cs:old_int1Ch ; передать управление предыдущему обработчику
; процедура bcd2asc
; преобразует старшую цифру упакованного BCD-числа из AL в ASCII-символ,
; который будет помещен в АН, а младшую цифру - в ASCII-символ в AL
bcd2asc proc near
mov ah,al
and al,0Fh ; оставить младшие 4 бита в AL
shr ah,4 ; сдвинуть старшие 4 бита в АН
or ах,3030h ; преобразовать в ASCII-символы
ret
bcd2asc endp
; строка " 00h 00:00 " с атрибутом 1Fh (белый на синем) после каждого символа
output_line db ' ',1Fh,'0',1Fh,'0',1Fh,'h',1Fh
db ' ',1Fh,'0',1Fh,'0',1Fh,':',1Fh
db '0',1Fh,'0',1Fh,' ',1Fh
output_line_l equ $ - output_line
int1Ch_handler endp
end start
Если в этом примере вместо ожидания нажатия на клавишу поместить какую-нибудь программу, работающую в текстовом режиме, например tinyshell из главы 1.3, она выполнится как обычно, но в правом верхнем углу будет постоянно показываться текущее время, то есть такая программа будет осуществлять два действия одновременно. Именно для этого и применяется механизм аппаратных прерываний — они позволяют процессору выполнять одну программу, в то время как отдельные программы следят за временем, считывают символы из клавиатуры и помещают их в буфер, получают и передают данные через последовательные и параллельные порты и даже обеспечивают многозадачность, переключая процессор между разными задачами по прерыванию системного таймера.
Разумеется, обработка прерываний не должна занимать много времени: если прерывание происходит достаточно часто (например, прерывание последовательного порта может происходить 28 800 раз в секунду), его обработчик обязательно должен выполняться за более короткое время. Если, например, обработчик прерывания таймера будет выполняться 1/32,4 секунды, то есть половину времени между прерываниями, вся система будет работать в два раза медленнее. А если еще одна программа с таким же долгим обработчиком перехватит это прерывание, система остановится совсем. Именно поэтому обработчики прерываний принято писать исключительно на ассемблере.