Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
714
03-07.md
Normal file
714
03-07.md
Normal file
|
|
@ -0,0 +1,714 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](03-06.html) [Table of Contents](index.html) [Next](03-08.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
While permitting the timer interrupt to occur allows long intervals to
|
||||
be timed, that same interrupt makes the long-period Zen timer less
|
||||
accurate than the precision Zen timer, since the time the BIOS spends
|
||||
handling timer interrupts during the timing interval is included in the
|
||||
time measured by the long-period timer. Likewise, any other interrupts
|
||||
that occur during the timing interval, most notably keyboard and mouse
|
||||
interrupts, will increase the measured time.
|
||||
|
||||
The long-period Zen timer has some of the same effects on the system
|
||||
time as does the precision Zen timer, so it’s a good idea to reboot the
|
||||
system after a session with the long-period Zen timer. The long-period
|
||||
Zen timer does not, however, have the same potential for introducing
|
||||
major inaccuracy into the system clock time during a single timing run
|
||||
since it leaves interrupts enabled and therefore allows the system clock
|
||||
to update normally.
|
||||
|
||||
#### Stopping the Clock {#Heading13}
|
||||
|
||||
There’s a potential problem with the long-period Zen timer. The problem
|
||||
is this: In order to measure times longer than 54 ms, we must maintain
|
||||
not one but two timing components, the timer 0 count and the BIOS
|
||||
time-of-day count. The time-of-day count measures the passage of 54.9 ms
|
||||
intervals, while the timer 0 count measures time within those 54.9 ms
|
||||
intervals. We need to read the two time components simultaneously in
|
||||
order to get a clean reading. Otherwise, we may read the timer count
|
||||
just before it turns over and generates an interrupt, then read the BIOS
|
||||
time-of-day count just after the interrupt has occurred and caused the
|
||||
time-of-day count to turn over, with a resulting 54 ms measurement
|
||||
inaccuracy. (The opposite sequence—reading the time-of-day count and
|
||||
then the timer count—can result in a 54 ms inaccuracy in the other
|
||||
direction.)
|
||||
|
||||
The only way to avoid this problem is to stop timer 0, read both the
|
||||
timer and time-of-day counts while the timer is stopped, and then
|
||||
restart the timer. Alas, the gate input to timer 0 isn’t
|
||||
program-controllable in the PC, so there’s no documented way to stop the
|
||||
timer. (The latched read feature we used in Listing 3.1 doesn’t stop the
|
||||
timer; it latches a count, but the timer keeps running.) What should we
|
||||
do?
|
||||
|
||||
As it turns out, an undocumented feature of the 8253 makes it possible
|
||||
to stop the timer dead in its tracks. Setting the timer to a new mode
|
||||
and waiting for an initial count to be loaded causes the timer to stop
|
||||
until the count is loaded. Surprisingly, the timer count remains
|
||||
readable and correct while the timer is waiting for the initial load.
|
||||
|
||||
In my experience, this approach works beautifully with fully
|
||||
8253-compatible chips. However, there’s no guarantee that it will always
|
||||
work, since it programs the 8253 in an undocumented way. What’s more,
|
||||
IBM chose not to implement compatibility with this particular 8253
|
||||
feature in the custom chips used in PS/2 computers. On PS/2 computers,
|
||||
we have no choice but to latch the timer 0 count and then stop the BIOS
|
||||
count (by disabling interrupts) as quickly as possible. We’ll just have
|
||||
to accept the fact that on PS/2 computers we may occasionally get a
|
||||
reading that’s off by 54 ms, and leave it at that.
|
||||
|
||||
I’ve set up Listing 3.5 so that it can assemble to either use or not use
|
||||
the undocumented timer-stopping feature, as you please. The **PS2**
|
||||
equate selects between the two modes of operation. If **PS2** is 1 (as
|
||||
it is in Listing 3.5), then the latch-and-read method is used; if
|
||||
**PS2** is 0, then the undocumented timer-stop approach is used. The
|
||||
latch-and-read method will work on all PC-compatible computers, but may
|
||||
occasionally produce results that are incorrect by 54 ms. The timer-stop
|
||||
approach avoids synchronization problems, but doesn’t work on all
|
||||
computers.
|
||||
|
||||
**LISTING 3.5 LZTIMER.ASM**
|
||||
|
||||
;
|
||||
; The long-period Zen timer. (LZTIMER.ASM)
|
||||
; Uses the 8253 timer and the BIOS time-of-day count to time the
|
||||
; performance of code that takes less than an hour to execute.
|
||||
; Because interrupts are left on (in order to allow the timer
|
||||
; interrupt to be recognized), this is less accurate than the
|
||||
; precision Zen timer, so it is best used only to time code that takes
|
||||
; more than about 54 milliseconds to execute (code that the precision
|
||||
; Zen timer reports overflow on). Resolution is limited by the
|
||||
; occurrence of timer interrupts.
|
||||
;
|
||||
; By Michael Abrash
|
||||
;
|
||||
; Externally callable routines:
|
||||
;
|
||||
; ZTimerOn: Saves the BIOS time of day count and starts the
|
||||
; long-period Zen timer.
|
||||
;
|
||||
; ZTimerOff: Stops the long-period Zen timer and saves the timer
|
||||
; count and the BIOS time-of-day count.
|
||||
;
|
||||
; ZTimerReport: Prints the time that passed between starting and
|
||||
; stopping the timer.
|
||||
;
|
||||
; Note: If either more than an hour passes or midnight falls between
|
||||
; calls to ZTimerOn and ZTimerOff, an error is reported. For
|
||||
; timing code that takes more than a few minutes to execute,
|
||||
; either the DOS TIME command in a batch file before and after
|
||||
; execution of the code to time or the use of the DOS
|
||||
; time-of-day function in place of the long-period Zen timer is
|
||||
; more than adequate.
|
||||
;
|
||||
; Note: The PS/2 version is assembled by setting the symbol PS2 to 1.
|
||||
; PS2 must be set to 1 on PS/2 computers because the PS/2’s
|
||||
; timers are not compatible with an undocumented timer-stopping
|
||||
; feature of the 8253; the alternative timing approach that
|
||||
; must be used on PS/2 computers leaves a short window
|
||||
; during which the timer 0 count and the BIOS timer count may
|
||||
; not be synchronized. You should also set the PS2 symbol to
|
||||
; 1 if you’re getting erratic or obviously incorrect results.
|
||||
;
|
||||
; Note: When PS2 is 0, the code relies on an undocumented 8253
|
||||
; feature to get more reliable readings. It is possible that
|
||||
; the 8253 (or whatever chip is emulating the 8253) may be put
|
||||
; into an undefined or incorrect state when this feature is
|
||||
; used.
|
||||
;
|
||||
; ******************************************************************
|
||||
; * If your computer displays any hint of erratic behavior *
|
||||
; * after the long-period Zen timer is used, such as the floppy*
|
||||
; * drive failing to operate properly, reboot the system, set *
|
||||
; * PS2 to 1 and leave it that way! *
|
||||
; ******************************************************************
|
||||
;
|
||||
; Note: Each block of code being timed should ideally be run several
|
||||
; times, with at least two similar readings required to
|
||||
; establish a true measurement, in order to eliminate any
|
||||
; variability caused by interrupts.
|
||||
;
|
||||
; Note: Interrupts must not be disabled for more than 54 ms at a
|
||||
; stretch during the timing interval. Because interrupts
|
||||
; are enabled, keys, mice, and other devices that generate
|
||||
; interrupts should not be used during the timing interval.
|
||||
;
|
||||
; Note: Any extra code running off the timer interrupt (such as
|
||||
; some memory-resident utilities) will increase the time
|
||||
; measured by the Zen timer.
|
||||
;
|
||||
; Note: These routines can introduce inaccuracies of up to a few
|
||||
; tenths of a second into the system clock count for each
|
||||
; code section timed. Consequently, it’s a good idea to
|
||||
; reboot at the conclusion of timing sessions. (The
|
||||
; battery-backed clock, if any, is not affected by the Zen
|
||||
; timer.)
|
||||
;
|
||||
; All registers and all flags are preserved by all routines.
|
||||
;
|
||||
|
||||
Code segment word public ‘CODE’
|
||||
assume cs: Code, ds:nothing
|
||||
public ZTimerOn, ZTimerOff, ZTimerReport
|
||||
|
||||
;
|
||||
; Set PS2 to 0 to assemble for use on a fully 8253-compatible
|
||||
; system; when PS2 is 0, the readings are more reliable if the
|
||||
; computer supports the undocumented timer-stopping feature,
|
||||
; but may be badly off if that feature is not supported. In
|
||||
; fact, timer-stopping may interfere with your computer’s
|
||||
; overall operation by putting the 8253 into an undefined or
|
||||
; incorrect state. Use with caution!!!
|
||||
;
|
||||
; Set PS2 to 1 to assemble for use on non-8253-compatible
|
||||
; systems, including PS/2 computers; when PS2 is 1, readings
|
||||
; may occasionally be off by 54 ms, but the code will work
|
||||
; properly on all systems.
|
||||
;
|
||||
; A setting of 1 is safer and will work on more systems,
|
||||
; while a setting of 0 produces more reliable results in systems
|
||||
; which support the undocumented timer-stopping feature of the
|
||||
; 8253. The choice is yours.
|
||||
;
|
||||
PS2 equ1
|
||||
;
|
||||
; Base address of the 8253 timer chip.
|
||||
;
|
||||
BASE_8253 equ40h
|
||||
;
|
||||
; The address of the timer 0 count registers in the 8253.
|
||||
;
|
||||
TIMER_0_8253 equBASE_8253 + 0
|
||||
;
|
||||
; The address of the mode register in the 8253.
|
||||
;
|
||||
MODE_8253 equBASE_8253 + 3
|
||||
;
|
||||
; The address of the BIOS timer count variable in the BIOS
|
||||
; data segment.
|
||||
;
|
||||
TIMER_COUNT equ46ch
|
||||
;
|
||||
; Macro to emulate a POPF instruction in order to fix the bug in some
|
||||
; 80286 chips which allows interrupts to occur during a POPF even when
|
||||
; interrupts remain disabled.
|
||||
;
|
||||
MPOPF macro
|
||||
local p1, p2
|
||||
jmp short p2
|
||||
p1: iret ;jump to pushed address & pop flags
|
||||
p2: pushcs ;construct far return address to
|
||||
call p1 ; the next instruction
|
||||
endm
|
||||
|
||||
;
|
||||
; Macro to delay briefly to ensure that enough time has elapsed
|
||||
; between successive I/O accesses so that the device being accessed
|
||||
; can respond to both accesses even on a very fast PC.
|
||||
;
|
||||
DELAY macro
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
jmp $+2
|
||||
endm
|
||||
|
||||
StartBIOSCountLow dw ? ;BIOS count low word at the
|
||||
; start of the timing period
|
||||
StartBIOSCountHigh dw ? ;BIOS count high word at the
|
||||
; start of the timing period
|
||||
EndBIOSCountLow dw ? ;BIOS count low word at the
|
||||
; end of the timing period
|
||||
EndBIOSCountHigh dw ? ;BIOS count high word at the
|
||||
; end of the timing period
|
||||
EndTimedCount dw ? ;timer 0 count at the end of
|
||||
; the timing period
|
||||
ReferenceCount dw ? ;number of counts required to
|
||||
; execute timer overhead code
|
||||
;
|
||||
; String printed to report results.
|
||||
;
|
||||
OutputStr labelbyte
|
||||
db 0dh, 0ah, ‘Timed count: ‘
|
||||
TimedCountStr db10 dup (?)
|
||||
db’ microseconds’, 0dh, 0ah
|
||||
db ‘$’
|
||||
;
|
||||
; Temporary storage for timed count as it’s divided down by powers
|
||||
; of ten when converting from doubleword binary to ASCII.
|
||||
;
|
||||
CurrentCountLow dw ?
|
||||
CurrentCountHigh dw ?
|
||||
;
|
||||
; Powers of ten table used to perform division by 10 when doing
|
||||
; doubleword conversion from binary to ASCII.
|
||||
;
|
||||
PowersOfTenlabelword
|
||||
dd 1
|
||||
dd 10
|
||||
dd 100
|
||||
dd 1000
|
||||
dd 10000
|
||||
dd 100000
|
||||
dd 1000000
|
||||
dd 10000000
|
||||
dd 100000000
|
||||
dd 1000000000
|
||||
PowersOfTenEnd labelword
|
||||
;
|
||||
; String printed to report that the high word of the BIOS count
|
||||
; changed while timing (an hour elapsed or midnight was crossed),
|
||||
; and so the count is invalid and the test needs to be rerun.
|
||||
;
|
||||
TurnOverStrlabelbyte
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************’
|
||||
db 0dh, 0ah
|
||||
db’* Either midnight passed or an hour or more passed *’
|
||||
db 0dh, 0ah
|
||||
db’* while timing was in progress. If the former was *’
|
||||
db 0dh, 0ah
|
||||
db’* the case, please rerun the test; if the latter *’
|
||||
db 0dh, 0ah
|
||||
db’* was the case, the test code takes too long to *’
|
||||
db 0dh, 0ah
|
||||
db’* run to be timed by the long-period Zen timer. *’
|
||||
db 0dh, 0ah
|
||||
db ‘* Suggestions: use the DOS TIME command, the DOS *’
|
||||
db 0dh, 0ah
|
||||
db ‘* time function, or a watch. *’
|
||||
db 0dh, 0ah
|
||||
db ‘****************************************************’
|
||||
db 0dh, 0ah
|
||||
db’$’
|
||||
|
||||
;********************************************************************
|
||||
;* Routine called to start timing. *
|
||||
;********************************************************************
|
||||
|
||||
ZTimerOn proc near
|
||||
|
||||
;
|
||||
; Save the context of the program being timed.
|
||||
;
|
||||
push ax
|
||||
pus hf
|
||||
;
|
||||
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
||||
; linear counting rather than count-by-two counting. Also stops
|
||||
; timer 0 until the timer count is loaded, except on PS/2
|
||||
; computers.
|
||||
;
|
||||
mov al,00110100b ;mode 2
|
||||
out MODE_8253,al
|
||||
;
|
||||
; Set the timer count to 0, so we know we won’t get another
|
||||
; timer interrupt right away.
|
||||
; Note: this introduces an inaccuracy of up to 54 ms in the system
|
||||
; clock count each time it is executed.
|
||||
;
|
||||
DELAY
|
||||
subal,al
|
||||
outTIMER_0_8253,al ;lsb
|
||||
DELAY
|
||||
outTIMER_0_8253,al ;msb
|
||||
;
|
||||
; In case interrupts are disabled, enable interrupts briefly to allow
|
||||
; the interrupt generated when switching from mode 3 to mode 2 to be
|
||||
; recognized. Interrupts must be enabled for at least 210 ns to allow
|
||||
; time for that interrupt to occur. Here, 10 jumps are used for the
|
||||
; delay to ensure that the delay time will be more than long enough
|
||||
; even on a very fast PC.
|
||||
;
|
||||
pushf
|
||||
sti
|
||||
rept 10
|
||||
jmp $+2
|
||||
endm
|
||||
MPOPF
|
||||
;
|
||||
; Store the timing start BIOS count.
|
||||
; (Since the timer count was just set to 0, the BIOS count will
|
||||
; stay the same for the next 54 ms, so we don’t need to disable
|
||||
; interrupts in order to avoid getting a half-changed count.)
|
||||
;
|
||||
push ds
|
||||
subax, ax
|
||||
movds, ax
|
||||
movax, ds:[TIMER_COUNT+2]
|
||||
movcs: [StartBIOSCountHigh],ax
|
||||
movax, ds:[TIMER_COUNT]
|
||||
movcs: [StartBIOSCountLow],ax
|
||||
pop ds
|
||||
;
|
||||
; Set the timer count to 0 again to start the timing interval.
|
||||
;
|
||||
mov al,00110100b ;set up to load initial
|
||||
out MODE_8253,al ; timer count
|
||||
DELAY
|
||||
subal, al
|
||||
out TIMER_0_8253,al; load count lsb
|
||||
DELAY
|
||||
out TIMER_0_8253,al; load count msb
|
||||
;
|
||||
; Restore the context of the program being timed and return to it.
|
||||
;
|
||||
MPOPF
|
||||
popax
|
||||
ret
|
||||
|
||||
ZTimerOnendp
|
||||
|
||||
;********************************************************************
|
||||
;* Routine called to stop timing and get count. *
|
||||
;********************************************************************
|
||||
|
||||
ZTimerOff procnear
|
||||
|
||||
;
|
||||
; Save the context of the program being timed.
|
||||
;
|
||||
pushf
|
||||
pushax
|
||||
pushcx
|
||||
;
|
||||
; In case interrupts are disabled, enable interrupts briefly to allow
|
||||
; any pending timer interrupt to be handled. Interrupts must be
|
||||
; enabled for at least 210 ns to allow time for that interrupt to
|
||||
; occur. Here, 10 jumps are used for the delay to ensure that the
|
||||
; delay time will be more than long enough even on a very fast PC.
|
||||
;
|
||||
sti
|
||||
rept 10
|
||||
jmp $+2
|
||||
endm
|
||||
|
||||
;
|
||||
; Latch the timer count.
|
||||
;
|
||||
|
||||
if PS2
|
||||
|
||||
mov al,00000000b
|
||||
out MODE_8253,al ;latch timer 0 count
|
||||
;
|
||||
; This is where a one-instruction-long window exists on the PS/2.
|
||||
; The timer count and the BIOS count can lose synchronization;
|
||||
; since the timer keeps counting after it’s latched, it can turn
|
||||
; over right after it’s latched and cause the BIOS count to turn
|
||||
; over before interrupts are disabled, leaving us with the timer
|
||||
; count from before the timer turned over coupled with the BIOS
|
||||
; count from after the timer turned over. The result is a count
|
||||
; that’s 54 ms too long.
|
||||
;
|
||||
|
||||
else
|
||||
|
||||
;
|
||||
; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count
|
||||
; load, which stops timer 0 until the count is loaded. (Only works
|
||||
; on fully 8253-compatible chips.)
|
||||
;
|
||||
mov al,00110100b; mode 2
|
||||
out MODE_8253,al
|
||||
DELAY
|
||||
mov al,00000000b ;latch timer 0 count
|
||||
out MODE_8253,al
|
||||
|
||||
endif
|
||||
|
||||
cli ;stop the BIOS count
|
||||
;
|
||||
; Read the BIOS count. (Since interrupts are disabled, the BIOS
|
||||
; count won’t change.)
|
||||
;
|
||||
push ds
|
||||
sub ax,ax
|
||||
mov ds,ax
|
||||
mov ax,ds:[TIMER_COUNT+2]
|
||||
mov cs:[EndBIOSCountHigh],ax
|
||||
mov ax,ds:[TIMER_COUNT]
|
||||
mov cs:[EndBIOSCountLow],ax
|
||||
pop ds
|
||||
;
|
||||
; Read the timer count and save it.
|
||||
;
|
||||
in al,TIMER_0_8253 ;lsb
|
||||
DELAY
|
||||
mov ah,al
|
||||
in al,TIMER_0_8253 ;msb
|
||||
xchg ah,al
|
||||
neg ax ;convert from countdown
|
||||
; remaining to elapsed
|
||||
; count
|
||||
mov cs:[EndTimedCount],ax
|
||||
;
|
||||
; Restart timer 0, which is still waiting for an initial count
|
||||
; to be loaded.
|
||||
;
|
||||
|
||||
ife PS2
|
||||
|
||||
DELAY
|
||||
mov al,00110100b ;mode 2, waiting to load a
|
||||
; 2-byte count
|
||||
out MODE_8253,al
|
||||
DELAY
|
||||
sub al,al
|
||||
out TIMER_0_8253,al ;lsb
|
||||
DELAY
|
||||
mov al,ah
|
||||
out TIMER_0_8253,al ;msb
|
||||
DELAY
|
||||
|
||||
endif
|
||||
|
||||
sti;let the BIOS count continue
|
||||
;
|
||||
; Time a zero-length code fragment, to get a reference for how
|
||||
; much overhead this routine has. Time it 16 times and average it,
|
||||
; for accuracy, rounding the result.
|
||||
;
|
||||
mov cs:[ReferenceCount],0
|
||||
mov cx,16
|
||||
cli ;interrupts off to allow a
|
||||
; precise reference count
|
||||
RefLoop:
|
||||
call ReferenceZTimerOn
|
||||
call ReferenceZTimerOff
|
||||
loop RefLoop
|
||||
sti
|
||||
add cs:[ReferenceCount],8; total + (0.5 * 16)
|
||||
mov cl,4
|
||||
shr cs:[ReferenceCount],cl;(total) / 16 + 0.5
|
||||
;
|
||||
; Restore the context of the program being timed and return to it.
|
||||
;
|
||||
popcx
|
||||
popax
|
||||
MPOPF
|
||||
ret
|
||||
|
||||
ZTimerOff endp
|
||||
|
||||
;
|
||||
; Called by ZTimerOff to start the timer for overhead measurements.
|
||||
;
|
||||
|
||||
ReferenceZTimerOnprocnear
|
||||
;
|
||||
; Save the context of the program being timed.
|
||||
;
|
||||
pushax
|
||||
pushf
|
||||
;
|
||||
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
||||
; linear counting rather than count-by-two counting.
|
||||
;
|
||||
mov al,00110100b ;mode 2
|
||||
out MODE_8253,al
|
||||
;
|
||||
; Set the timer count to 0.
|
||||
;
|
||||
DELAY
|
||||
sub al,al
|
||||
out TIMER_0_8253,al ;lsb
|
||||
DELAY
|
||||
out TIMER_0_8253,al ;msb
|
||||
;
|
||||
; Restore the context of the program being timed and return to it.
|
||||
;
|
||||
MPOPF
|
||||
popax
|
||||
ret
|
||||
|
||||
ReferenceZTimerOnendp
|
||||
|
||||
;
|
||||
; Called by ZTimerOff to stop the timer and add the result to
|
||||
; ReferenceCount for overhead measurements. Doesn’t need to look
|
||||
; at the BIOS count because timing a zero-length code fragment
|
||||
; isn’t going to take anywhere near 54 ms.
|
||||
;
|
||||
|
||||
ReferenceZTimerOff procnear
|
||||
;
|
||||
; Save the context of the program being timed.
|
||||
;
|
||||
pushf
|
||||
pushax
|
||||
pushcx
|
||||
|
||||
;
|
||||
; Match the interrupt-window delay in ZTimerOff.
|
||||
;
|
||||
sti
|
||||
rept10
|
||||
jmp$+2
|
||||
endm
|
||||
|
||||
mov al,00000000b
|
||||
out MODE_8253,al ;latch timer
|
||||
;
|
||||
; Read the count and save it.
|
||||
;
|
||||
DELAY
|
||||
in al,TIMER_0_8253 ;lsb
|
||||
DELAY
|
||||
mov ah,al
|
||||
in al,TIMER_0_8253 ;msb
|
||||
xchg ah,al
|
||||
neg ax ;convert from countdown
|
||||
; remaining to elapsed
|
||||
; count
|
||||
add cs:[ReferenceCount],ax
|
||||
;
|
||||
; Restore the context and return.
|
||||
;
|
||||
popcx
|
||||
popax
|
||||
MPOPF
|
||||
ret
|
||||
|
||||
ReferenceZTimerOff endp
|
||||
|
||||
;********************************************************************
|
||||
;* Routine called to report timing results. *
|
||||
;********************************************************************
|
||||
|
||||
ZTimerReportprocnear
|
||||
|
||||
pushf
|
||||
push ax
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
push si
|
||||
push di
|
||||
push ds
|
||||
;
|
||||
push cs ;DOS functions require that DS point
|
||||
pop ds ; to text to be displayed on the screen
|
||||
assume ds :Code
|
||||
;
|
||||
; See if midnight or more than an hour passed during timing. If so,
|
||||
; notify the user.
|
||||
;
|
||||
mov ax,[StartBIOSCountHigh]
|
||||
cmp ax,[EndBIOSCountHigh]
|
||||
jz CalcBIOSTime ;hour count didn’t change,
|
||||
; so everything’s fine
|
||||
inc ax
|
||||
cmp ax,[EndBIOSCountHigh]
|
||||
jnz TestTooLong ;midnight or two hour
|
||||
; boundaries passed, so the
|
||||
; results are no good
|
||||
mov ax,[EndBIOSCountLow]
|
||||
cmp ax,[StartBIOSCountLow]
|
||||
jb CalcBIOSTime ;a single hour boundary
|
||||
; passed--that’s OK, so long as
|
||||
; the total time wasn’t more
|
||||
; than an hour
|
||||
|
||||
;
|
||||
; Over an hour elapsed or midnight passed during timing, which
|
||||
; renders the results invalid. Notify the user. This misses the
|
||||
; case where a multiple of 24 hours has passed, but we’ll rely
|
||||
; on the perspicacity of the user to detect that case.
|
||||
;
|
||||
TestTooLong:
|
||||
mov ah,9
|
||||
mov dx,offset TurnOverStr
|
||||
int 21h
|
||||
jmp short ZTimerReportDone
|
||||
;
|
||||
; Convert the BIOS time to microseconds.
|
||||
;
|
||||
CalcBIOSTime:
|
||||
mov ax,[EndBIOSCountLow]
|
||||
sub ax,[StartBIOSCountLow]
|
||||
mov dx,54925 ;number of microseconds each
|
||||
; BIOS count represents
|
||||
mul dx
|
||||
mov bx,ax ;set aside BIOS count in
|
||||
mov cx,dx ; microseconds
|
||||
;
|
||||
; Convert timer count to microseconds.
|
||||
;
|
||||
mov ax,[EndTimedCount]
|
||||
mov si,8381
|
||||
mul si
|
||||
mov si,10000
|
||||
div si ;* .8381 = * 8381 / 10000
|
||||
;
|
||||
; Add timer and BIOS counts together to get an overall time in
|
||||
; microseconds.
|
||||
;
|
||||
add bx,ax
|
||||
adc cx,0
|
||||
;
|
||||
; Subtract the timer overhead and save the result.
|
||||
;
|
||||
mov ax,[ReferenceCount]
|
||||
mov si,8381 ;convert the reference count
|
||||
mul si ; to microseconds
|
||||
mov si,10000
|
||||
div si;* .8381 = * 8381 / 10000
|
||||
sub bx,ax
|
||||
sbb cx,0
|
||||
mov [CurrentCountLow],bx
|
||||
mov [CurrentCountHigh],cx
|
||||
;
|
||||
; Convert the result to an ASCII string by trial subtractions of
|
||||
; powers of 10.
|
||||
;
|
||||
mov di,offset PowersOfTenEnd - offset PowersOfTen - 4
|
||||
mov si,offset TimedCountStr
|
||||
CTSNextDigit:
|
||||
mov bl,’0’
|
||||
CTSLoop:
|
||||
mov ax,[CurrentCountLow]
|
||||
mov dx,[CurrentCountHigh]
|
||||
sub ax,PowersOfTen[di]
|
||||
sbb dx,PowersOfTen[di+2]
|
||||
jc CTSNextPowerDown
|
||||
inc bl
|
||||
mov [CurrentCountLow],ax
|
||||
mov [CurrentCountHigh],dx
|
||||
jmp CTSLoop
|
||||
CTSNextPowerDown:
|
||||
mov [si],bl
|
||||
inc si
|
||||
sub di,4
|
||||
jns CTSNextDigit
|
||||
;
|
||||
;
|
||||
; Print the results.
|
||||
;
|
||||
mov ah,9
|
||||
mov dx,offset OutputStr
|
||||
int 21h
|
||||
;
|
||||
ZTimerReportDone:
|
||||
pop ds
|
||||
pop di
|
||||
pop si
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
pop ax
|
||||
MPOPF
|
||||
ret
|
||||
|
||||
ZTimerReport endp
|
||||
|
||||
Code ends
|
||||
end
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](03-06.html) [Table of Contents](index.html) [Next](03-08.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue