156 lines
8.5 KiB
Markdown
156 lines
8.5 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](03-03.html) [Table of Contents](index.html) [Next](03-05.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
In fact, the Zen timer shown in Listing 3.1 can only time intervals of
|
||
up to about 54 ms in length, since that is the period of time that can
|
||
be measured by timer 0 before its count turns over and repeats.
|
||
fifty-four ms may not seem like a very long time, but even a CPU as slow
|
||
as the 8088 can perform more than 1,000 divides in 54 ms, and division
|
||
is the single instruction that the 8088 performs most slowly. If a
|
||
measured period turns out to be longer than 54 ms (that is, if timer 0
|
||
has counted down and turned over), the Zen timer will display a message
|
||
to that effect. A long-period Zen timer for use in such cases will be
|
||
presented later in this chapter.
|
||
|
||
The Zen timer determines whether timer 0 has turned over by checking to
|
||
see whether an IRQ0 interrupt is pending. (Remember, interrupts are off
|
||
while the Zen timer runs, so the timer interrupt cannot be recognized
|
||
until the Zen timer stops and enables interrupts.) If an IRQ0 interrupt
|
||
is pending, then timer 0 has turned over and generated a timer
|
||
interrupt. Recall that **ZTimerOn** initially sets timer 0 to 0, in
|
||
order to allow for the longest possible period—about 54 ms—before timer
|
||
0 reaches 0 and generates the timer interrupt.
|
||
|
||
Now we’re ready to look at the ways in which the Zen timer can introduce
|
||
inaccuracy into the system clock. Since timer 0 is initially set to 0 by
|
||
the Zen timer, and since the system clock ticks only when timer 0 counts
|
||
off 54.925 ms and reaches 0 again, an average inaccuracy of one-half of
|
||
54.925 ms, or about 27.5 ms, is incurred each time the Zen timer is
|
||
started. In addition, a timer interrupt is generated when timer 0 is
|
||
switched from mode 3 to mode 2, advancing the system clock by up to
|
||
54.925 ms, although this only happens the first time the Zen timer is
|
||
run after a warm or cold boot. Finally, up to 54.925 ms can again be
|
||
lost when **ZTimerOff** is called, since that routine again sets the
|
||
timer count to zero. Net result: The system clock will run up to 110 ms
|
||
(about a ninth of a second) slow each time the Zen timer is used.
|
||
|
||
Potentially far greater inaccuracy can be incurred by timing code that
|
||
takes longer than about 110 ms to execute. Recall that all interrupts,
|
||
including the timer interrupt, are disabled while timing code with the
|
||
Zen timer. The 8259 interrupt controller is capable of remembering at
|
||
most one pending timer interrupt, so all timer interrupts after the
|
||
first one during any given Zen timing interval are ignored.
|
||
Consequently, if a timing interval exceeds 54.9 ms, the system clock
|
||
effectively stops 54.9 ms after the timing interval starts and doesn’t
|
||
restart until the timing interval ends, losing time all the while.
|
||
|
||
The effects on the system time of the Zen timer aren’t a matter for
|
||
great concern, as they are temporary, lasting only until the next warm
|
||
or cold boot. System that have batteryclocks, (AT-style machines; that
|
||
is, virtually all machines in common use) automatically reset the
|
||
correct time whenever the computer is booted, and systems without
|
||
battery-clocks prompt for the correct date and time when booted.
|
||
Also,repeated use of the Zen timer usually makes the system clock slow
|
||
by at most a total of a few seconds, unless code that takes much longer
|
||
than 54 ms to run is timed (in which case the Zen timer will notify you
|
||
that the code is too long to time).
|
||
|
||
Nonetheless, it’s a good idea to reboot your computer at the end of each
|
||
session with the Zen timer in order to make sure that the system clock
|
||
is correct.
|
||
|
||
### Stopping the Zen Timer {#Heading8}
|
||
|
||
At some point after **ZTimerOn** is called, **ZTimerOff** must always be
|
||
called to mark the end of the timing interval. **ZTimerOff** saves the
|
||
context of the calling program, latches and reads the timer 0 count,
|
||
converts that count from the countdown value that the timer maintains to
|
||
the number of counts elapsed since **ZTimerOn** was called, and stores
|
||
the result. Immediately after latching the timer 0 count—and before
|
||
enabling interrupts—**ZTimerOff** checks the 8259 interrupt controller
|
||
to see if there is a pending timer interrupt, setting a flag to mark
|
||
that the timer overflowed if there is indeed a pending timer interrupt.
|
||
|
||
After that, **ZTimerOff** executes just the overhead code of
|
||
**ZTimerOn** and **ZTimerOff** 16 times, and averages and saves the
|
||
results in order to determine how many of the counts in the timing
|
||
result just obtained were incurred by the overhead of the Zen timer
|
||
rather than by the code being timed.
|
||
|
||
Finally, **ZTimerOff** restores the context of the calling program,
|
||
including the state of the interrupt flag that was in effect when
|
||
**ZTimerOn** was called to start timing, and returns.
|
||
|
||
One interesting aspect of **ZTimerOff** is the manner in which timer 0
|
||
is stopped in order to read the timer count. We don’t actually have to
|
||
stop timer 0 to read the count; the 8253 provides a special latched read
|
||
feature for the specific purpose of reading the count while a time is
|
||
running. (That’s a good thing, too; we’ve no documented way to stop
|
||
timer 0 if we wanted to, since its gate input isn’t connected. Later in
|
||
this chapter, though, we’ll see that timer 0 can be stopped after all.)
|
||
We simply tell the 8253 to latch the current count, and the 8253 does so
|
||
without breaking stride.
|
||
|
||
### Reporting Timing Results {#Heading9}
|
||
|
||
**ZTimerReport** may be called to display timing results at any time
|
||
after both **ZTimerOn** and **ZTimerOff** have been called.
|
||
**ZTimerReport** first checks to see whether the timer overflowed
|
||
(counted down to 0 and turned over) before **ZTimerOff** was called; if
|
||
overflow did occur, **ZTimerOff** prints a message to that effect and
|
||
returns. Otherwise, **ZTimerReport** subtracts the reference count
|
||
(representing the overhead of the Zen timer) from the count measured
|
||
between the calls to **ZTimerOn** and **ZTimerOff**, converts the result
|
||
from timer counts to microseconds, and prints the resulting time in
|
||
microseconds to the standard output.
|
||
|
||
Note that **ZTimerReport** need not be called immediately after
|
||
**ZTimerOff**. In fact, after a given call to **ZTimerOff,
|
||
ZTimerReport** can be called at any time right up until the next call to
|
||
**ZTimerOn**.
|
||
|
||
You may want to use the Zen timer to measure several portions of a
|
||
program while it executes normally, in which case it may not be
|
||
desirable to have the text printed by **ZTimerReport** interfere with
|
||
the program’s normal display. There are many ways to deal with this. One
|
||
approach is removal of the invocations of the DOS print string function
|
||
(INT 21H with AH equal to 9) from **ZTimerReport**, instead running the
|
||
program under a debugger that supports screen flipping (such as Turbo
|
||
Debugger or CodeView), placing a breakpoint at the start of
|
||
**ZTimerReport**, and directly observing the count in microseconds as
|
||
**ZTimerReport** calculates it.
|
||
|
||
A second approach is modification of **ZTimerReport** to place the
|
||
result at some safe location in memory, such as an unused portion of the
|
||
BIOS data area.
|
||
|
||
A third approach is alteration of **ZTimerReport** to print the result
|
||
over a serial port to a terminal or to another PC acting as a terminal.
|
||
Similarly, many debuggers can be run from a remote terminal via a serial
|
||
link.
|
||
|
||
Yet another approach is modification of **ZTimerReport** to send the
|
||
result to the printer via either DOS function 5 or BIOS interrupt 17H.
|
||
|
||
A final approach is to modify **ZTimerReport** to print the result to
|
||
the auxiliary output via DOS function 4, and to then write and load a
|
||
special device driver named **AUX**, to which DOS function 4 output
|
||
would automatically be directed. This device driver could send the
|
||
result anywhere you might desire. The result might go to the secondary
|
||
display adapter, over a serial port, or to the printer, or could simply
|
||
be stored in a buffer within the driver, to be dumped at a later time.
|
||
(Credit for this final approach goes to Michael Geary, and thanks go to
|
||
David Miller for passing the idea on to me.)
|
||
|
||
You may well want to devise still other approaches better suited to your
|
||
needs than those I’ve presented. Go to it! I’ve just thrown out a few
|
||
possibilities to get you started.
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](03-03.html) [Table of Contents](index.html) [Next](03-05.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|