144 lines
No EOL
7.9 KiB
Markdown
144 lines
No EOL
7.9 KiB
Markdown
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. |