[Назад] [Далее]

5.8.2. Прерывания от внешних устройств

Прерывания от внешних устройств, или аппаратные прерывания — это то, что понимается под термином «прерывание». Внешние устройства (клавиатура, дисковод, таймер, звуковая карта и т.д.) подают сигнал, по которому процессор прерывает выполнение программы и передает управление на обработчик прерывания. Всего на персональных компьютерах используется 15 аппаратных прерываний, хотя теоретически возможности архитектуры позволяют довести их число до 64.

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

Самые полезные для программ аппаратные прерывания — прерывания системного таймера и клавиатуры. Так как их стандартные обработчики выполняют множество функций, от которых зависит работа системы, их нельзя заменять полностью, как мы делали это с обработчиком 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 секунды, то есть половину времени между прерываниями, вся система будет работать в два раза медленнее. А если еще одна программа с таким же долгим обработчиком перехватит это прерывание, система остановится совсем. Именно поэтому обработчики прерываний принято писать исключительно на ассемблере.


п»ї
"target=_blank><\/a>") //-->