[英]The DOS.GetSystemTime function 2Ch is not accurate
DOS.GetSystemTime function 2Ch以小时 (CH)、分钟 (CL)、秒 (DH) 和百分之一秒 (DL) 的形式返回当前时间。 事实证明,这些“百分之一秒”更像是“二十分之一秒”。
我已经包含了一个连续询问这个 DOS function 的程序,只显示唯一的时间戳。 结果令人失望。 如何获得真正的 0.01 秒读数?
ORG 256
Begin:
mov bh, -1
Main:
mov ah, 01h ; BIOS.CheckKeystroke
int 16h ; -> AX ZF
jz Work
mov ah, 00h ; BIOS.GetKeystroke
int 16h ; -> AX
Pause:
mov ah, 00h ; BIOS.GetKeystroke
int 16h ; -> AX
cmp al, 27 ; ESC
jne Work
ret ; TerminateProgram
Work:
call DOSTime ; -> CX DX
cmp bh, dl
je Main ; Hundredths didn't change
mov bh, dl
push dx ; (1)
mov bl, ':'
mov al, ch ; Hours
call PrintTrio ; -> (AX DX)
mov al, cl ; Minutes
call PrintTrio ; -> (AX DX)
pop cx ; (1)
mov bl, '.'
mov al, ch ; Seconds
call PrintTrio ; -> (AX DX)
mov bl, 13
mov al, cl ; Hundredths
call PrintTrio ; -> (AX DX)
mov dl, 10
mov ah, 02h ; DOS.PrintCharacter
int 21h
jmp Main
; ----------------------
; IN (al,bl) OUT () MOD (ax,dx)
PrintTrio:
aam
add ax, '00'
push ax ; (1)
mov dl, ah
mov ah, 02h ; DOS.PrintCharacter
int 21h
pop dx ; (1)
mov ah, 02h ; DOS.PrintCharacter
int 21h
mov dl, bl
mov ah, 02h ; DOS.PrintCharacter
int 21h
ret
; ----------------------
; IN () OUT (cx,dx)
DOSTime:
push ax
mov ah, 2Ch ; DOS.GetSystemTime
int 21h ; -> CX DX
pop ax
ret
; ----------------------
上述程序中的典型 output 是:
17:15:25.84 17:15:25.89 17:15:25.95 17:15:26.00 17:15:26.06 17:15:26.11 17:15:26.17 17:15:26.22 17:15:26.28 17:15:26.33 17:15:26.39 17:15:26.44 17:15:26.50 17:15:26.55 17:15:26.60 17:15:26.66 17:15:26.71 17:15:26.77 17:15:26.82 17:15:26.88 17:15:26.93 17:15:26.99 17:15:27.04 17:15:27.10
这是另一个程序员提出的问题,他想使用 DOS function 2Ch 进行延迟,然后发现这个问题不准确: Delay program using int 21h with ah = 2Ch 。
当DOS得到系统时间请求时,它会读取BIOS维护的timer tick,并迅速将其值转换成相应的时、分、秒。 此计算有余数,DOS 将其作为“百分之一秒”值返回。 鉴于计时器滴答仅每 0.054925 秒更改一次,这永远不会像 0.01 秒一样准确。
每次可编程间隔定时器(PIT) 的通道 0 生成中断 8 (IRQ0) 时,BIOS 维护在 0040:006C 的 32 位定时器递增。 该中断是内部计数器递减 65536 次的结果。 一次这样的递减需要 0.838095 微秒,因为 PIT 以 14.31818 MHz / 12 = 1.193182 MHz 运行。
如果我们将它与我们在 0040:006C 处找到的 32 位计数器结合起来,我们可以读取这个内部计数器并将其用作新“超级计数器”的最低有效 16 位。
因为我们的目标只是获得 0.01 秒的精度,所以计算只使用内部计数器的高 8 位和 BIOS 定时器变量的低 24 位。
该代码假定 PIT 通道 0 在模式 2(速率发生器)或模式 3(方波发生器)下运行,分频器为 65536。因为该代码使用回读命令,所以还假定 PIT 不是8253(过时),但至少是 8254。
一天中的刻度数取为 1573041,比您预期的多 1。 请参阅为什么某些 BIOS 在 1800B1h 而不是 1800B0h 处具有定时器滴答循环? .
BIOS | 年 | 总计 | 坑 | #0 模式 |
---|---|---|---|---|
Award 模块化 BIOS v4.51 PG | 1996年 | 1573040 | 8254 | 3个 |
凤凰 BIOS 4.00 版本 6.00 | 1999 | 1573041 | 8254 | 3个 |
康柏系统 ROM 686P9 v1.11 | 2002年 | 1573040 | 8254 | 3个 |
凤凰科技有限公司 v1.23 | 2007年 | 1573041 | 8254 | 2个 |
BIOS | 2010 | 1573040 | 8254 | 2个 |
DOSBox 0.74 | 2010 | (*) | 8254 | 3个 |
(*) 与普通的 BIOS 不同, DOSBox 不会在午夜使 BIOS 定时器回绕。 MyTime程序为此做出了规定。
1800B0FFh .. 8639999
1 .. 8639999 / 1800B0FFh
N .. N * (8639999 / 1800B0FFh)
避免繁琐的划分。
从(8639999 / 1800B0FFh)
= (x / 100000000h)
我们得到:
x = 8639999 / 1800B0FFh * 100000000h
x = 92149630
最大值为 1800B0FFh 的新“超级计数器”首先将其乘以 92149630 (057E177Eh),然后除以 4294967296 (100000000h),将其转换为百分之一秒,这很简单,只需丢弃 64 的低半部分位产品。
这是要用 FASM 汇编的 8086 汇编代码:
; IN () OUT (cx,dx)
MyTime: ; Assumes 8254 PIT
push ax bx si di bp
push ds ; (1)
pushf ; (2)
xor ax, ax
mov ds, ax
mov si, 046Ch ; Address of the BIOS.TimerTick
cli
mov cx, [si+1] ; CX:BH = [0,1573040]
mov bh, [si]
sti
nop
nop
.ReDo:
cli
mov al, 11_00_001_0b ; Read-back latched count word and status byte
out 43h, al ; for PIT channel 0
jmp $+2
in al, 40h ; Get status byte in AH
mov ah, al
in al, 40h ; Get count word in DX
mov dl, al
in al, 40h
sti
mov dh, al
test dx, dx ; Don't accept 0
jz .ReDo
not dx ; Make upcounting
mov al, ah
and al, 00111111b
cmp al, 00110100b ; Is it lowbyte/highbyte mode 2 for PIT channel 0 ?
je .Mode2
.Mode3:
inc dx ; (-Count + ~OutputPin * 65536) / 2
shl ah, 1
cmc
rcr dx, 1
.Mode2:
cli
cmp bh, [si]
je .GotIt
test dx, dx
js .GotIt
mov cx, [si+1] ; CX:BH = [0,1573040]
mov bh, [si]
.GotIt:
popf ; (2)
pop ds ; (1)
mov bl, dh ; -> CX:BX
mov ax, 057Eh ; 92149630 = 057E177Eh
mul cx
mov si, ax
mov di, dx
mov ax, 057Eh
mul bx
mov bp, ax
add si, dx
adc di, 0
mov ax, 177Eh
mul cx
add bp, ax
adc si, dx
adc di, 0
mov ax, 177Eh
mul bx
add bp, dx
adc si, 0
adc di, 0
mov ax, si
mov dx, di
mov bx, 6000
div bx ; -> AX is BigMinutes, DX is BigHundredths
mov bl, 60
div bl ; -> AL is Hours, AH is Minutes
.DOSBox: ; DOSBox does not reset the BIOS.TimerTick
sub al, 24
jnb .DOSBox
add al, 24
mov ch, al ; Hours [0,23]
mov cl, ah ; Minutes [0,59]
mov ax, dx
mov bl, 100
div bl ; -> AL is Seconds, AH is Hundredths
mov dh, al ; Seconds [0,59]
mov dl, ah ; Hundredths [0,99]
pop bp di si bx ax
ret
; ----------------------
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.