|
|
|
|
|
|
|
System clocks are usually based on periodic hardware interrupts. The kernel maintains the current time and advances it at every interrupt [2,16]. The structure of the clock hardware of typical Intel PCs is shown in Figure 3.1. An oscillator generates clock pulses that drive a counter C. Every time the counter reaches the value of a programmable latch L, an interrupt is generated and the counter is reset. At each of these ticks the kernel then increases the internal variable jiffies, which simply counts clock ticks, as well as the variable xtime, which represents current time (as a struct timeval).
Time can be queried via the gettimeofday function. If it would simply return the value of xtime, just the accuracy of the timer interrupt frequency could be provided. However, gettimeofday queries the hardware counter C that is used for interrupt generation and includes the time passed since the last update of xtime.
The alarm clock functionality, however, relies on the interrupts and cannot provide a higher accuracy than the interrupt frequency, which is 10ms. The next section discusses how this timer granularity can be improved. A different approach has been taken in the UTIME project at the University of Kansas [17]. In their system, for every event the hardware latch L is reprogrammed to generate the next interrupt exactly at the right time.
needs to be adapted for different values of HZ. It does not
work at all, if timer interrupts are more frequent than once in
1ms. For this case, some internal variables would require additional
bits.
Let us consider the graph for 1ms blocking time. For timer granularities of less than 1ms the error is about 2ms, for other granularities about 1ms. Assuming, the system could not block for a shorter time than the tick size, an error of 1ms and 0, respectively, would be expexted. The implementation of select explains the actual behavior:
![]()
Apart from these implementation peculiarities, the timer granularity is reflected in the accuracy provided to the user. This relation also exists inside the kernel, where events can be scheduled only in ticks, too.
The overhead introduced by more frequent timer interrupts is shown in
Figure 3.3 for tick granularities of 100
s,
300
s, 1ms, 3ms, and 10ms. The solid line gives the
average time of 100 runs of an empty loop executed one million times,
the dashed line gives the time of a kernel make. At a tick granularity
of 100
s there is an overhead of 10.2% compared to
10ms tick size for the loop, and 11.9% for the kernel make. With
interrupts every 1ms the overhead is 3.1% and 3.2%, respectively.
The tick size is also used as the scheduling time slice. Hence, a higher interrupt frequency also introduces more context switches between processes. This overhead is not reflected in the experiment described. Nonetheless, it seems reasonable to choose a tick size of 1ms for our system. For instance, the token holding time of 1ms, which turned out to be a good choice in Chapter 2, would not be possible otherwise.
|
|
|
|
| Rainer Koster, 6/18/1998 |