Почему программируемый интервальный таймер не показывает правильные значения времени при использовании Int 0x21 с услугой 0x2c - PullRequest
2 голосов
/ 20 апреля 2019

То, чего я пытаюсь достичь, это подключить прерывание от программируемого интервала таймера (int 8) для отображения текущего времени на экране (видеопамять 0xb800), а затем нажмите клавишу, чтобы приостановить этот таймер, и нажмите эту же клавишу, чтобы возобновить этот таймер.

Пока я просто хочу, чтобы на экране отображалось время, и оно продолжало работать бесконечно (в цикле).

Ниже приведен мой код, позвольте мне объяснить, что я делаю и с какой проблемой я сталкиваюсь, у меня есть подпрограмма DisplayUpdatedTime , которая вызывает Int0x21 со службой 0x2c который возвращает часы в ch, минуты в cl и секунды dh, затем сохраняет значения в часах, минутах и ​​секундах в переменных памяти и вызывает PrintByte. Подпрограмма PrintByte преобразует байты в регистре Al в соответствующие им ASCII и выводит их на экран.

Итак, проблема, с которой я сейчас сталкиваюсь, заключается в том, что при вызове DisplayUpdatedTime в моей подпрограмме Interrupt для int 8 отображается время выполнения моей Программы, но оно никогда не обновляется, несмотря на запуск пустого бесконечного цикла. (см. код, чтобы понять идею), но когда я запускаю процедуру DisplayUpdatedTime в цикле, а не вызываю ее в процедуре прерывания (int 8), она работает нормально, и я получаю обновление таймера каждую секунду.

Мой вопрос, почему моя подпрограмма работает нормально, когда она вызывается в независимом цикле, а не когда я вызываю ее в службе прерываний?

DisplayUpdatedTime:
pusha
push es
push word 0xb800
pop es
sti ;enable interrupts just in case the function is called from another 
;interrupt
mov ah, 0x2c
int 0x21
xor ax,ax
mov al, ch ;place hours
mov byte [cs:Hours],al ; save the current hours
mov di,140
call PrintByte
add di,4
mov word [es:di],0x073A ;ASCII of Colon 0x3A
add di,2
mov al,cl ;place minutes
mov byte [cs:Minutes],al ; save the current Minutes
call PrintByte
add di,4
mov word [es:di],0x073A
add di,2
mov al,dh;place seconds
mov byte [cs:Seconds],al ; save the current Seconds
call PrintByte
pop es
popa
ret


;take argument in al to prints and prints at current location of di
PrintByte:
pusha 
push es
push word 0xb800
pop es
mov bl,10
div bl
mov dh, 0x07
;quotient in AL, Remainder in AH
add al, 0x30 ;adding hex 30 to convert to ascii
mov dl, al
mov word [es:di],dx
add di,2
add ah,0x30
mov dl, ah
mov word [es:di],dx
mov ax,0
pop es
popa
ret`


timmerInterrupt:
push ax

call DisplayUpdatedTime

mov al,0x20 ; send EOI to PIC
out 0x20,al
pop ax
iret

Это работает

start: 
l1:
    call DisplayUpdatedTime
jmp l1                                   

Это не работает Почему?

start: 

xor ax,ax
mov es,ax ; point to IVT base

cli                                       
mov word [es:8*4], timmerInterrupt ;hook int 8            
mov [es:8*4+2], cs                       
sti    

l1:
jmp l1

Ответы [ 2 ]

3 голосов
/ 21 апреля 2019

Мой вопрос, почему моя подпрограмма работает нормально, когда она вызывается в независимом цикле, а не когда я вызываю ее в службе прерываний?

Int 08h вызывается системными часами примерно 18,2 раза в секунду. Поскольку это прерывание вызывается каждые 55 мсек, обработчик для него должен выполняться как можно быстрее. Поэтому в этом обработчике прерываний не рекомендуется выполнять большую работу.

Вполне возможно и очень вероятно, что DOS может быть занят, когда активирован int 08h. Если это происходит и ваш обработчик замены вызывает функцию DOS, вы получаете то, что называется повторным входом. Но с учетом того, что по замыслу DOS нет рецидивирующих проблем, со временем возникнут проблемы!

Ваш код замены для int 08h также отклоняет большую часть работы, которую необходимо выполнить здесь:

  • Продвижение индикатора времени дня в 00:40: 006 Ч
  • Обеспечение автоматического отключения дисков
  • Вызов вектора прерывания ловушки пользователя 1Ch
  • Подтверждение прерывания
  • ...

Это причины, по которым ваша программа должна перехватывать прерывание 1Ch . Он имеет тот же самый приоритет, что и int 08h, но ваше взаимодействие с ним намного проще.
Обычный способ справиться с этим важным обработчиком - просто установить флаг, который основная программа может выбрать для любой обработки, которая потребуется позже, когда все будет сохранено в отношении прерываний.

Ниже приведен пример этого:

; --------------------------------------- Code section
Start: 
    mov     [SaveInt1C + 2], cs          ; Completing the far pointer

    push    es
    push    0
    pop     es                           ; Point to IVT base
    mov     eax, [SaveInt1C]
    xchg    [es:1Ch*4], eax              ; Hook int 1Ch
    mov     [SaveInt1C], eax             ; Save vector so it can be restored!
    pop     es

MainLoop:
    cmp     byte [cs:TimerFlag], -1      ; Is flag set ?
    jne     NoTick
    not     byte [cs:TimerFlag]          ; Reset flag -1 -> 0
    call    DisplayUpdatedTime
NoTick:

    ... everything else in your program ...

    jmp     MainLoop

Quit:
    mov     eax, [SaveInt1C]
    push    0
    pop     ds                           ; Point to IVT base
    mov     [1Ch*4], eax                 ; Restore int 1Ch

    mov     ax, 4C00h                    ; DOS.Terminate
    int     21h

TimerInterrupt:
    mov     byte [cs:TimerFlag], -1      ; Set flag
    iret                                 ; Complete take-over
TimerFlag   db 0

; --------------------------------------- Data section
SaveInt1C          dw TimerInterrupt, 0
EnableTimerDisplay db -1

... и затем нажмите кнопку, чтобы приостановить этот таймер, и нажмите эту же кнопку, чтобы возобновить этот таймер.

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

  • Проверка наличия ключа

        mov     ah, 01h                  ; BIOS.TestKey
        int     16h                      ; -> AX ZF
    
  • Если да, то получить его

        jz      NoKey
        mov     ah, 00h                  ; BIOS.GetKey
        int     16h                      ; -> AX
    
  • Если это назначенный ключ, например, p затем переключить бит разрешения

        or      al, 32                   ; LCase
        cmp     al, 'p'
        jne     NotMyKey
        not     byte [EnableTimerDisplay]
    
  • Call DisplayUpdatedTime на основе этого бита включения

        cmp     byte [cs:TimerFlag], -1      ; Is flag set ?
        jne     NoTick
        not     byte [cs:TimerFlag]          ; Reset flag -1 -> 0
        cmp     byte [EnableTimerDisplay], -1
        jne     NoTick
        call    DisplayUpdatedTime
    NoTick:
    

Обычно есть два способа перехвата прерывания:

  • Полностью замените ваш код замены на iret
  • Цепочка к предыдущему обработчику:

    • с использованием jmp far [...] вместо iret
    • , используя call far [...] и заканчивая iret

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

Пример 1 с использованием позднего связывания со старым обработчиком:

; --------------------------------------- Code section
Start: 
    mov     [cs:SaveInt1C + 2], cs       ; Completing the far pointer

    push    es
    push    0
    pop     es                           ; Point to IVT base
    mov     eax, [cs:SaveInt1C]
    xchg    [es:1Ch*4], eax              ; Hook int 1Ch
    mov     [cs:SaveInt1C], eax          ; Save vector so it can be restored!
    pop     es

MainLoop:
    cmp     byte [cs:TimerFlag], -1      ; Is flag set ?
    jne     NoTick
    not     byte [cs:TimerFlag]          ; Reset flag -1 -> 0
    call    DisplayUpdatedTime
NoTick:

    ... everything else in your program ...

    jmp     MainLoop

Quit:
    mov     eax, [cs:SaveInt1C]
    push    0
    pop     ds                           ; Point to IVT base
    mov     [1Ch*4], eax                 ; Restore int 1Ch

    mov     ax, 4C00h                    ; DOS.Terminate
    int     21h

TimerInterrupt:
    mov     byte [cs:TimerFlag], -1      ; Set flag
    jmp far [cs:SaveInt1C]               ; Chaining to old handler
TimerFlag   db 0
SaveInt1C   dw TimerInterrupt, 0

; --------------------------------------- Data section
EnableTimerDisplay db -1

Пример 2 с использованием раннего связывания со старым обработчиком:

; --------------------------------------- Code section
Start: 
    mov     [cs:SaveInt1C + 2], cs       ; Completing the far pointer

    push    es
    push    0
    pop     es                           ; Point to IVT base
    mov     eax, [cs:SaveInt1C]
    xchg    [es:1Ch*4], eax              ; Hook int 1Ch
    mov     [cs:SaveInt1C], eax          ; Save vector so it can be restored!
    pop     es

MainLoop:
    cmp     byte [cs:TimerFlag], -1      ; Is flag set ?
    jne     NoTick
    not     byte [cs:TimerFlag]          ; Reset flag -1 -> 0
    call    DisplayUpdatedTime
NoTick:

    ... everything else in your program ...

    jmp     MainLoop

Quit:
    mov     eax, [cs:SaveInt1C]
    push    0
    pop     ds                           ; Point to IVT base
    mov     [1Ch*4], eax                 ; Restore int 1Ch

    mov     ax, 4C00h                    ; DOS.Terminate
    int     21h

TimerInterrupt:
    pushf
    call far [cs:SaveInt1C]              ; Chaining to old handler
    mov     byte [cs:TimerFlag], -1      ; Set flag
    iret
TimerFlag   db 0
SaveInt1C   dw TimerInterrupt, 0

; --------------------------------------- Data section
EnableTimerDisplay db -1
1 голос
/ 21 апреля 2019

Время BIOS обновляется прерыванием таймера, поэтому, как только вы перехватите прерывание таймера, время не будет обновлено. Возможно, ваш обработчик прерываний и подпрограмма отображения работают отлично, но время, возвращаемое int 21h, никогда не меняется. Прерывание крюка вместо 1ч; это предусмотрено именно для этой цели. (В качестве альтернативы вы можете сохранить исходный обработчик прерывания 8 и вызвать его из своего обработчика.)

...