我在这里想要实现的是 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/