为什么在将 Int 0x21 与服务 0x2c 一起使用时,可编程间隔计时器不显示正确的时间值

Why doesn't Programmable Interval Timer show correct Time values when using Int 0x21 with service 0x2c

我想在这里实现的是挂钩可编程间隔计时器中断(int 8)以在屏幕上显示当前时间(视频内存0xb800)然后按一个键暂停该计时器并按相同的键恢复那个计时器。

暂时我只想让时间显示在屏幕上并无限期地保持 运行ning(循环)。

下面是我的代码,让我解释一下我在做什么以及我面临的问题,我有子程序 DisplayUpdatedTime 调用 Int0x21 with service 0x2c which returns hours in ch, minutes in cl and seconds dh, then saving the values in Hour, Minutes and Seconds in Memory Variables and calls PrintByte. PrintByte 子例程将 al 寄存器中的字节转换为相应的 ASCII 并将它们打印在屏幕上。

所以我现在面临的问题是,在我的 int 8 中断例程中调用 DisplayUpdatedTime 时,显示了我的程序执行的时间,但尽管 运行 有一个空的无限循环,但从未更新过. (查看代码以了解想法),但是当我 运行 循环中的 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

BIOS时间由定时器中断更新,所以一旦你挂上定时器中断时间就不会更新。可能您的中断处理程序和显示例程工作正常,但 int 21h 返回的时间永远不会改变。挂钩中断 1ch 代替;它正是为此目的而提供的。 (或者,您可以保存中断 8 的原始处理程序并从您的处理程序中调用它。)

My Question why does my subroutine works fine when calling it in an Independent loop not when I call it in a Interrupt service?

Int 08h 被系统时钟调用大约每秒 18.2 次。因为这个中断每 55 毫秒调用一次,它的处理程序必须尽快执行。因此,在此中断处理程序中做大量工作不是一个好主意。

int 08h 被激活时,DOS 被占用是完全有可能的。如果发生这种情况并且您的替换处理程序调用 DOS 函数,您将得到所谓的重入。但是考虑到 DOS 的设计是不可重入的,问题最终还是会出现!

您的 int 08h 替换代码也忽略了此处需要完成的大部分工作:

  • 在 0040h:006Ch
  • 推进时间指示器
  • 为磁盘提供自动电机关闭功能
  • 调用用户hook中断向量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

... and then press a key to pause that timer and press the same key to resume that timer.

不要试图将其压缩到中断处理程序中。
接下来是您可以从主程序循环中执行的操作:

  • 测试密钥是否可用

        mov     ah, 01h                  ; BIOS.TestKey
        int     16h                      ; -> AX ZF
    
  • 如果是,那就取

        jz      NoKey
        mov     ah, 00h                  ; BIOS.GetKey
        int     16h                      ; -> AX
    
  • 如果是指定键 e.g. p 然后切换启用位

        or      al, 32                   ; LCase
        cmp     al, 'p'
        jne     NotMyKey
        not     byte [EnableTimerDisplay]
    
  • 根据这个启用位调用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