The topics in this chapter cover the details of using the timer and time-of-day facilities of IRIX. The emphasis is on high-precision timing facilities used by real-time programs in Challenge/Onyx systems.
As described under “Timer Interrupts (Itimers)”, IRIX supports software interval timers of three kinds. All three are used the same way:
The three kinds differ in the time base they use, the precision with which you can specify the intervals, and in the signals they send (see Table 3-2).
Of the three, only the ITIMER_REAL, which measures elapsed time, is useful for real-time processes. The signal it generates, SIGALRM, is always delivered as soon as possible. (Other signals are not delivered until a scheduling interval occurs; see “Signal Delivery and Latency”.)
![]() | Note: Interval timers are not normally used with the Frame Scheduler. See “Using Timers with the Frame Scheduler” |
Example 5-1 shows an outline of code to initialize a repeating timer signal.
The example begins by defining a signal-handling function, uponSigalrm(). This handler performs the V (count-up, revive) operation on a semaphore. When the main process has completed its work for one interval and is ready to wait until the next interval, it will perform the P (count-down, deplete) operation on the same semaphore. (This is one of many possibilities for interaction between the signal handler and the main process. For another method based on sigsuspend(2), see “Interprocess Communication” in Appendix A on page 220.)
The periodic timer is initialized in the function setUpTimer(). It establishes uponSigalrm() as the signal handler. Then it initializes the itimer, using a period passed as an argument.
It takes time for the kernel to deliver the SIGALRM that notifies your program at the end of the interval. (The issue of signal latency in general is discussed under “Signal Delivery and Latency”.) The signal latency is less for SIGALRM than for other signals, since the kernel initiates a scheduling cycle immediately after the timer interrupt, without waiting for the end of a fixed time slice. When the program is running or ready to run, in a CPU that has been restricted and isolated (as discussed in Chapter 6), the latency is fairly short and consistent from one signal to the next (however, it is not advisable to use a repeating itimer as the time base for a real-time program). Under less favorable conditions, signal latency can be variable and sometimes lengthy relative to a fast timer frequency.
The IRIX kernel can be asked to implement itimers for many processes at once, each interval having a different length and starting at a different time. The kernel's method differs depending on the hardware architecture:
In earlier multiprocessor architectures, there is no hardware support for interval timers, and the kernel has to rely on frequent, periodic interrupts as a time base.
In the Challenge/Onyx and POWER-Challenge architecture, each CPU has a clock comparator that the kernel can program to cause an interrupt after a specific interval has elapsed.
In systems without a hardware clock comparator, the kernel manages interval timers using a periodic interrupt, as follows.
The kernel keeps active itimerval structures in a list.
The kernel arranges to be interrupted at a regular interval of length T.
On each timer interrupt, T is deducted from the it_value field of each active itimerval structure.
If the result is negative, the kernel processes the timer event: it sends the signal, and either removes the timer from the list or restarts it with a new interval, depending on itimerval.it_interval.
The key point is the value of the interval T at which the kernel updates timers. No timer interval can be shorter than T. The smaller the value of T, the more frequently the kernel must inspect all timers.
By default, T is one second divided by the value HZ defined in /usr/include/sys/param.h. In all recent versions of IRIX, HZ=100, so T, the minimum itimer interval, is 10 milliseconds. For normal processes—that is, processes not running at a nondegrading priority in the real-time band—no interval shorter than 10 milliseconds can be scheduled. The requested interval is rounded up to whole multiples of 10 milliseconds.
In the Challenge/Onyx and POWER-Challenge architectures, each CPU has a hardware-operated cycle counter and a hardware comparator that generates an interrupt when the comparator register matches the cycle counter. In these systems, the kernel can manage interval timers with the minimum number of interrupts.
The kernel keeps active itimerval structures in a list, sorted by ascending time until expiration.
The kernel calculates the cycle counter value at which the next interval timer will expire, and sets this value in the comparator register.
When the interval expires, an interrupt occurs.
The kernel processes the timer event: it sends the signal, and either removes the timer from the list or restarts it with a new interval, depending on itimerval.it_interval.
In the Challenge/Onyx systems, the number of timer interrupts the kernel must handle depends only on the number of active timer requests and their repetition rates. If there are many timers, or if there are repeating timers with very short intervals, there will be many interrupts. Normally there are fewer interrupts than in a system without a clock comparator.
A process running at a nondegrading, real-time priority (see “Setting a Nondegrading Real-Time Priority”) can make use of an interval that is shorter than the HZ frequency (10 milliseconds). The actual interval that elapses is different in different versions of IRIX.
Timer requests from processes that are not running under a nondegrading real-time priority are, in all systems, rounded up to the HZ interval.
In systems with a clock comparator, beginning with IRIX version 5.3, the fasthz tuning parameter has no effect. Timer requests from real-time processes are rounded up to the nearest timer unit—21 nanoseconds in the Challenge/Onyx system.
In IRIX 6.0, IRIX 5.2, and earlier versions, the minimum effective timer resolution for real-time processes is set by the fasthz system tuning parameter. Any timer request is rounded up to the nearest multiple of the fasthz interval.
You can inspect the current value of fasthz using the command
grep fasthz /var/sysgen/mtune/kernel |
The default value for a Challenge/Onyx system is 1000. That is, the minimum effective timer interval for a real-time process is 1 millisecond.
The value of fasthz can be set, within limits, using the systune command (see the systune(1) reference page). The higher it is set, the more precise your timer intervals can be.
You may need to experiment with different fasthz values in order to produce a repeatable, short interval. On a Challenge/Onyx system the hardware clock interval is 21 nanoseconds, which does not divide evenly into most fasthz intervals. The fast timer support code uses integer division, and it has some difficult problems with truncation.
For example, suppose you want to set an itimer to define a 60 Hz frame rate. You would set an itimer with a value of 16,667 microseconds.
Experience has shown that the default fasthz value of 1000 does not give good results with a 16.67 millisecond itimer. In IRIX version 5.2, this combination “jitters” on either side of the target interval. In version 5.3, itimers are guaranteed always to delay at least the specified time. While in 5.3 the interval is not shorter than 16.67 milliseconds, it is sometimes longer. Changing to a fasthz of 2400 (a multiple of the desired interval rate) does not solve the problem, which was due to integer truncation in the timer routine. However, a fasthz frequency slightly less than a multiple of the desired frame rate, 2390, does produce a dependable 16.7 millisecond interval.
![]() | Note: Itimers are not recommended as a way to schedule for a high frame rate. When designing a real-time program for a high frame rate the REACT/Pro Frame Scheduler offers a much more reliable and accurate way to schedule repetitive processes. |
Normally, interval timer interrupts are taken by the CPU in which the itimer was initialized. Beginning with IRIX 5.3, when you isolate a particular CPU (see “Isolating a CPU From TLB Interrupts”), all itimers pending for that CPU are retargeted to the assigned Clock CPU (see “Assigning the Clock Processor”). However, any new itimer requests made in an isolated CPU after it has been isolated are taken on that CPU.
For a continuously-running real-time process, it is generally best to take interval timer interrupts on the CPU where the process runs. This helps to reduce the impact of timer handling on other CPUs, and helps to reduce the time to deliver the SIGALRM.
There is a kernel tuning parameter, itimer_on_clkcpu (you can find it in /var/sysgen/mtune/kernel), which if set forces all timer interrupts to be taken on the CPU that is the clock (dispatching) CPU. This parameter can hurt the performance of timer signal delivery. It should only be set when it is crucial that a process see an exact, consistent relationship between itimer intervals and time of day stamps.
In systems that have no clock comparator, the kernel implements fast timers by shortening the periodic timer interval to the fasthz frequency when necessary. When a real-time process sets a timer at an interval that is not a multiple of HZ, the kernel initiates periodic timer interrupts at the fasthz rate, for example 1000 times per second.
The frequent interrupts needed to support fast timers cause an overhead load of several percent of the power of one CPU. In these systems you should try to design your real-time application to use intervals that are multiples of 10 milliseconds, so that the fast interrupt rate is not needed. When that is not feasible, you can assign the interrupt to a particular CPU (see “Assigning the fasthz Processor”).
Beginning with IRIX 5.3, when you isolate a particular CPU, all pending itimers for that CPU are retargeted to the assigned Clock CPU. However, in IRIX 6.0, 5.2, and earlier, existing interval timers are not moved; they remain on the original CPUs, which continued to receive interrupts for those timers. If the itimers were repeating, the interrupts continued indefinitely.
Certain daemons set up repeating interval timers when they are initialized. These timers continue to interrupt the CPUs on which the daemons were running when they set the timers. In releases before 5.3, the only way to be sure that a CPU will never receive fast timer interrupts is to restrict that CPU early in the boot process (see “Restricting a CPU From Scheduled Work”). For example, a shell script named S05restrict could be placed in /etc/rc2.d containing the line
mpadmin -r 1 #set CPU 1 to restricted state |
and additional lines as required to isolate all CPUs used for real-time work.
There are two sources of timestamps, as described earlier in “Timestamps”. They differ in their accessibility, precision, and accuracy.
The gettimeofday() function returns the time as two long integers in a timeval structure. (For details of the call, refer to the gettimeofday(3) reference page.)
struct timeval {
long tv_sec; /* seconds since Jan. 1, 1970 */
long tv_usec; /* additional microseconds */
}
|
The nominal resolution of this timestamp is 1 microsecond. However, it is not practical for the kernel to update an internal timestamp with this frequency. The actual timestamp value is updated at intervals that are convenient to the kernel. The intervals are not regular; however they are never greater than 10 milliseconds.
This does not mean that successive calls to gettimeofday() return the same value. On the contrary, experimentation reveals that successive calls always return different values. Usually the difference is 1 in the tv_usec field. However, when the kernel updates the timestamp between two successive calls, the difference is greater. See the sample program under “Getting the Time of Day Stamp”. A similar program, using two child processes getting the time alternately, showed that it was impossible to get the identical timestamp value even in different processes.
Normally an IRIX system uses one of the time-synchronization daemons, either timed or timeslave, to keep the local clock accurate. These daemons use adjtime() to adjust the time of day when necessary. (See the timed(1), timeslave(1), and adjtime(2)reference pages.) Thus the timestamp returned by gettimeofday() should reflect the true local time, with an inaccuracy of at worst -10 milliseconds.
Either the date command or the time daemon can adjust the current time by a negative increment. As a result, gettimeofday() can in rare circumstances return duplicate values (see the date(1) reference page).
All Silicon Graphics systems have a free-running counter that is updated by hardware at a high frequency. You can map the image of this counter into the process address space, then sample its value as an integer. (For a discussion on mapping segments of memory, see “The Segment Mapping Function mmap()” and the following topics.)
The precision of the cycle counter depends on the hardware system. In the Challenge/Onyx line it is a 64-bit integer. The frequency that it counts also varies with the system. In the Challenge/Onyx, it is 21 nanoseconds (47.6 MHz).
You obtain the virtual address of the cycle counter (in the kernel's virtual address space) using syssgi(), in a call that also returns the clock precision in picoseconds (1e-12 seconds, millionths of a microsecond). See the syssgi(2) reference page. For an example program, see “Mapping and Reading the Cycle Counter”.
Since the update frequency of the cycle counter is close to the maximum CPU instruction rate, it is not possible to read the same value from it twice.
However, the cycle counter is simply a free-running hardware device; it is not synchronized with any corrected time base. Its drift rate can be as high as 1 part in 10,000—100 microseconds per second, or approximately 8 seconds per day.
The two timestamp sources can be compared on several different attributes, listed in Table 5-1.
Table 5-1. Comparison of Timestamp Functions
Timestamp | Overhead | Precision | Accuracy | Drift |
|---|---|---|---|---|
gettimeofday() | System call (100s of instructions) | 1 microsecond | +0, -10 milliseconds | 1 part in 10,000 short-term; corrected to negligible amount over long periods |
cycle counter | negligible | 21 nanoseconds (Challenge) | instruction cycle time | 1 part in 10,000, varying |
Because gettimeofday() is synchronized to a time standard, you must use it when you want to record the actual time of an event, and when times recorded in one machine will be compared to times recorded in another. You should use it to measure durations of minutes and longer; in those cases its possible error of up to -10 milliseconds becomes less important than its resistance to long-term drift.
Because the cycle counter can be sampled with negligible overhead and has very high precision, you should use it whenever you want to measure durations of seconds or less, and whenever you simply want a source of unduplicated, monotonically-increasing, unsigned numbers to provide unique key values.