A Peek Inside the Clock

Last month we introduced a few functions that allow your applications to retrieve the current time from Linux. We discussed how one might implement a simple function that causes an application to wait for a specific amount of time before continuing execution. We also looked at the alarm() function, which keeps time for you, and how you might use alarm() instead of the timing functions to allow a program to wait for a specific amount of time without needlessly executing any instructions while waiting.

Last month we introduced a few functions that allow your applications to retrieve the current time from Linux. We discussed how one might implement a simple function that causes an application to wait for a specific amount of time before continuing execution. We also looked at the alarm() function, which keeps time for you, and how you might use alarm() instead of the timing functions to allow a program to wait for a specific amount of time without needlessly executing any instructions while waiting.

Our previous column started with two sections, “Getting the Time” and “Using the Time.” This month, we’ll again start with the same two topics, but instead of referring to the time functions we talked about previously, we’ll now take a look at how the Linux kernel itself deals with time.

Getting the Time

The fact that an operating system must deal closely with timing mechanisms is probably something overlooked by most computer users (and perhaps even most programmers!). We take for granted that we can simply get the time from our computers — after all, reading a clock should be easy for a sophisticated machine, right?

The answer to this question is less obvious than it first appears. Think about what you are looking for when you look at a clock. Essentially, you want to know approximately what time it is. Most of the time, it matters little to you whether it’s 12:04 or 12:06. A minute here or there doesn’t matter. However, the story is much different for a computer. With processor speeds now in the gigahertz, literally billions of things are happening within your computer every second. To computers, every nanosecond can be important. As a result, the timing machinery in a computer is much more sophisticated than the simple wall clocks in our offices.

Most Intel PCs have three different mechanisms for keeping time. The first is the Real Time Clock (RTC), which most of you are probably familiar with. This is the clock that contains a battery on your motherboard so that it runs continually, even when your machine is off and not plugged in. It is the main source of wall clock time for the system when it starts up. Linux only uses this clock to set the time and date for the machine. It doesn’t use this clock for the other timing functions we’ve discussed.

Linux provides access to the RTC through the /dev/rtcdevice file. To see a demonstration of that device file in use, look in /proc/rtc(using a utility like cat or more). This will show you the information that the RTC communicates to the system at various intervals when requested.

The second and third types of timing hardware are what Linux uses to keep track of all important timing issues in the system. The second type of timing mechanism (provided in all Intel processors since the Pentium) is the Time Stamp Counter (TSC) — a special register that is incremented on every cycle of the processor. The assembly instruction, rdtsc (which stands for read TSC, of course), is used to read the value of this register. This mechanism provides more accurate timing than the real time clock because, for example, on a 1 GHz machine, the register’s value is updated once every nanosecond. As we will see later, Linux uses this register to get a much more accurate measurement of time.

The third piece of timing hardware is called the Programmable Interval Timer (PIT). This device can be set to issue interrupts at any given time in the future. Much like you use your alarm clock to wake you up after eight hours of sleep (on a good night!), Linux uses the PIT to wake it up every few milliseconds to perform some task.

Using the Time

How does Linux use all of these pieces of hardware to keep things running smoothly on your machine? Let’s look at what happens when you first turn on your Linux box. Linux uses the real time clock to set its own clock so that when you run xclock (or something like that), the correct time appears. It then must determine the frequency of the processor so it can effectively use the TSC to keep the time. Linux needs to perform this calibration because the same kernel image may be run on processors of different speeds. Listing One shows the kernel code that performs the calibration.




Listing One: Calibration of the Time Stamp Counter (Found in ‘arch/i386/kernel/time.c’)


static unsigned long __init calibrate_tsc(void)
{
/* Set the Gate high, disable speaker */
outb((inb(0×61) & ~0×02) | 0×01, 0×61);

/*
* Now let’s take care of CTC channel 2
*
* Set the Gate high, program CTC channel 2 for mode 0,
* (interrupt on terminal count mode), binary count,
* load 5 * LATCH count, (LSB and MSB) to begin countdown.
*/
outb(0xb0, 0×43); /* binary, mode 0, LSB/MSB, Ch 2 */
outb(CALIBRATE_LATCH & 0xff, 0×42); /* LSB of count */
outb(CALIBRATE_LATCH >> 8, 0×42); /* MSB of count */

{
unsigned long startlow, starthigh;
unsigned long endlow, endhigh;
unsigned long count;

rdtsc(startlow,starthigh);
count = 0;
do {
count++;
} while ((inb(0×61) & 0×20) == 0);
rdtsc(endlow,endhigh);

last_tsc_low = endlow;

/* Error: ECTCNEVERSET */
if (count <= 1)
goto bad_ctc;
/* 64-bit subtract – gcc just messes up with long longs */
__asm__(“subl %2,%0\n\t”
“sbbl %3,%1″
:”=a” (endlow), “=d” (endhigh)
:”g” (startlow), “g” (starthigh),
“0″ (endlow), “1″ (endhigh));

/* Error: ECPUTOOFAST */
if (endhigh)
goto bad_ctc;

/* Error: ECPUTOOSLOW */
if (endlow <= CALIBRATE_TIME)
goto bad_ctc;

__asm__(“divl %2″
:”=a” (endlow), “=d” (endhigh)
:”r” (endlow), “0″ (0), “1″ (CALIBRATE_TIME));

return endlow;
}
/*
* The CTC wasn’t reliable: we got a hit on the very first read,
* or the CPU was so fast/slow that the quotient wouldn’t fit in
* 32 bits..
*/
bad_ctc:
return 0;
}

The first few statements (calls to outb) set the PIT to send an interrupt to the kernel at a certain point in the future (about 50 milliseconds). Next, the code in the block below that reads the initial value of the TSC (using the rdtsc instruction), loops until an interrupt is received from the PIT, and records the end value of the TSC. From these two values, and knowing the amount of time that passed between the two reads (thanks to the PIT), the kernel can calculate the speed of the processor. With this information, Linux can use the more accurate measure of time when it is necessary.

For most timing operations, the PIT is sufficient enough to use and is set up to notify the kernel every 10 milliseconds. It is this notification from the PIT that allows Linux to be as responsive as it is. If you have ever wondered how Linux so effectively switches between the many tasks you are doing, it is because of the PIT. In fact, the smaller the increment of the PIT, the more responsive the operating system becomes.

Of course, this responsiveness has a cost. Each time the kernel goes through its up-keeping routine, it is taking away time that could be used to execute other programs. On most machines, Linux uses 10 millisecond intervals, but the version of Linux on Compaq’s Alpha uses one millisecond. The variable that contains information about the interval is the macro HZ and is defined in the asm include files for each different architecture. See Listing Two for the different architectures and their corresponding definitions. HZ is a frequency measurement, so taking 1/HZ tells you how long the interval between interrupts from the PIT will be. Notice that for all of the architectures, a value of 100 is used (corresponding to 10 millisecond intervals), with the exception of the Alpha and IA64, which use a value of 1024.




Listing Two: Values of HZ for Various Architectures


in include/asm-alpha/param.h:
# define HZ 1024

in include/asm-arm/param.h:
#define HZ 100

in include/asm-cris/param.h:
#define HZ 100

in include/asm-i386/param.h:
#define HZ 100

in include/asm-ia64/param.h:
# define HZ 1024

in include/asm-m68k/param.h:
#define HZ 100

in include/asm-mips/param.h:
# define HZ 100

in include/asm-mips64/param.h:
# define HZ 100

in include/asm-parisc/param.h:
#define HZ 100

in include/asm-ppc/param.h:
#define HZ 100

in include/asm-s390/param.h:
#define HZ 100

in include/asm-s390x/param.h:
#define HZ 100

in include/asm-sh/param.h:
#define HZ 100

in include/asm-sparc/param.h:
#define HZ 100

in include/asm-sparc64/param.h:
#define HZ 100

Right on Time

Now we know how Linux uses the hardware mechanisms to fulfill the needs of programmers. If you are interested in learning more about what actually happens each time Linux is notified by the PIT, or what data structures are employed to manage all of the timing functions for user applications, check out the book Understanding the Linux Kernel by Daniel Pierre Bovet and Marco Cesati. Much of the information in this article comes from that book’s chapter on timing measurements; it is a great reference for this subject.



Benjamin Chelf is an author and engineer at CodeSourcery, LLC. He can be reached at chelf@codesourcery.com.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62