abrash-black-book/03-04.md
2013-12-30 20:26:41 +11:00

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.