【问题标题】:Why doesn't Programmable Interval Timer show correct Time values when using Int 0x21 with service 0x2c为什么可编程间隔计时器在使用 Int 0x21 和服务 0x2c 时不显示正确的时间值
【发布时间】:2019-09-10 15:32:36
【问题描述】:

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

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

下面是我的代码,让我解释一下我在做什么以及我面临的问题,我有子程序 DisplayUpdatedTime 调用 Int0x21 服务 0x2c 以 ch 为单位返回小时,以 cl 为单位返回分钟,以 dh 为单位返回秒,然后将小时、分钟和秒的值保存在内存变量中并调用 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

【问题讨论】:

    标签: assembly nasm x86-16


    【解决方案1】:

    我的问题为什么我的子例程在独立循环中调用它而不是在中断服务中调用它时工作正常?

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

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

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

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

    【讨论】:

    • 非常感谢您的出色解释!但是您能解释一下“重入”是什么意思吗,或者指出我可以从中获得更多信息的来源
    • @Shehroz 这个wikipedia article 很好地解释了它。第一段就足够了。
    【解决方案2】:

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

    【讨论】:

    • 不要忘记 DOS 提供的大多数功能都不是可重入的,并且不能期望在 IRQ 处理程序中正常工作(除非您测试“当前在 DOS 中”标志以确保 IRQ 没有中断 DOS)。
    • @Brendan,谢谢。我想到了这一点,但我认为这不是问题,因为他的主循环没有做任何事情。但无论如何我应该在答案中提到它。
    • 谢谢,请您详细说明一下“(或者,您可以保存中断 8 的原始处理程序并从您的处理程序中调用它。)”
    • @Shehroz 我在回答中详细阐述了从您的处理程序调用它的主题。查看今天的编辑。
    猜你喜欢
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2020-03-31
    相关资源
    最近更新 更多