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

标签 assembly nasm x86-16

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

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

下面是我的代码,让我解释一下我在做什么以及我面临的问题,我有子例程DisplayUpdatedTime,它使用服务0x2c调用Int0x21 返回 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

最佳答案

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
  • 确认中断
  • ...

这些就是为什么您的程序应该 Hook 中断 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.

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

  • 测试 key 是否可用

        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:
    

基本上有两种方法来 Hook 中断:

  • 通过使用 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

关于assembly - 为什么在将 Int 0x21 与服务 0x2c 一起使用时,可编程间隔计时器不显示正确的时间值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55777480/

相关文章:

c - 为什么我们需要针对不同的平台(例如 Windows/Linux)进行编译?

windows - 阴影空间示例

assembly - 在 CodeView 中单步执行时 EBX 的高位清零

assembly - x86组件可在哪些CPU上运行?

assembly - BIOS int 10h 在 QEMU 上打印垃圾

gcc - 约束 "Rah"和 "Ral"在扩展内联汇编中意味着什么?

c - 在GDB中使用命令 'x/20x $esp',栈是怎么工作的?

windows - 是否可以在不链接到 c 库且不使用 ExitProcess() 的情况下从汇编代码创建 Windows exe?

c - 为什么 GCC 在使用 int foo asm ("ebx") 将 var 固定到寄存器时放置空操作推送/弹出?

assembly - 如何通过 x86 BIOS 调用移动光标?