assembly DOS.GetSystemTime函数2Ch不准确

js81xvg6  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(118)

DOS.GetSystemTime function 2Ch以小时(CH)、分钟(CL)、秒(DH)和百分之一秒(DL)的形式返回当前时间,结果显示这些“百分之一秒”更像是“二十分之一秒”。
我已经包含了一个连续询问这个DOS函数的程序,只显示唯一的时间戳。结果令人失望。我怎样才能获得真正的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
; ----------------------

上述程序的典型输出为:

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函数2Ch进行延迟,然后发现这一不准确性造成了问题:Delay program using int 21h with ah = 2Ch .

0x6upsns

0x6upsns1#

当DOS收到系统时间请求时,它将读取BIOS维护的计时器滴答声,并迅速将其值转换为相应的小时、分钟和秒。这个计算有一个余数,DOS将其返回为"百分之一秒"值。这永远不会像0.01秒那样精确,因为计时器滴答声每0.054925秒才改变一次。
每次Programmable Interval Timer(PIT)的通道0产生中断8(IRQ0)时,BIOS保持在0040:006C的32位定时器递增。该中断是内部计数器刚刚递减65536次的结果。一次这样的递减需要0.838095 µ sec,因为PIT的工作频率为14.31818 MHz/12 = 1.193182 MHz。
我们可以读取此内部计数器,并将其用作新的"超级计数器"的最低有效16位,如果我们将其与0040:006C处的32位计数器组合在一起。
由于我们的目标是仅获得0.01秒的精度,因此计算仅使用内部计数器的高8位和BIOS定时器变量的低24位。
代码假定PIT通道0在模式2(速率发生器)或模式3(方波发生器)下工作,频率除数为65536。由于代码使用回读命令,因此还假定PIT不是8253(已废弃),而是至少8254。
一天中的滴答数为1573041,比您预期的多1,请参见Why did some BIOSes have the timer tick wrap-around at 1800B1h instead of at 1800B0h?
| 基本 ios |年份|总刻度|坑|#0模式|
| - ------|- ------|- ------|- ------|- ------|
| 获奖模块化BIOS v4.51 PG|一九九六年|小行星1573040|小行星8254|三个|
| Phoenix BIOS 4.00版本6.00|一九九九年|* * 1573041**|小行星8254|三个|
| 康柏系统ROM 686P9 v1.11|二○ ○二年|小行星1573040|小行星8254|三个|
| Phoenix科技有限公司v1.23|二○ ○七年|* * 1573041**|小行星8254|* * 二**|
| AMI BIOS|二○一○年|小行星1573040|小行星8254|* * 二**|
| 剂量盒0.74|二○一○年|(注 )|小行星8254|三个|
DOSBox does not make the BIOS timer wraparound at midnight与普通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位乘积的低半部分的简单问题。
这是8086汇编代码,将与FASM一起汇编:

; 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
; ----------------------

相关问题