Saturday, February 2, 2013

Linux Timers



Linux Timers

This article discusses using Timers in user space in Linux. Various methods to start and stop dynamic timers, calculate time spent between instructions etc, are discussed below. The first section deals with the varius sources for timer interrupts. The next section provides details on the various APIs that “user space” applications can use for their timer needs. Finally, some code snippets are provided that will help the reader on how exactly to use some of the timer methods explained earlier.

Timer sources

RTC

The notes for this section are taken from [1]. Linux has two largely-compatible user space RTC API families
·         /dev/rtc ... is the RTC provided by PC compatible systems (Old "RTC Class" Drivers)
·         /dev/rtc0, /dev/rtc1 ... supported by a wide variety of RTC chips on all systems (New “RTC Class" Drivers)
Both the above RTC methods use the same API to make requests, but the hardware may not offer the same functionality.

Old "RTC Class" Drivers:

All PCs have a Real Time Clock (RTC) built into them, which tracks wall clock time and is battery backed. It tracks the Coordinated Universal Time (UTC, formerly "Greenwich Mean Time"). It can also be used to generate signals from a slow 2Hz to a relatively fast 8192Hz, in increments of powers of two. These signals are reported by interrupt number 8.
The interrupts are reported via /dev/rtc (major 10, minor 135, read only character device) in the form of an unsigned long. The low byte contains the type of interrupt (update-done, alarm-rang, or periodic) that was raised, and the remaining bytes contain the number of interrupts since the last read.  Status information is reported through the pseudo-file /proc/driver/rtc if the /proc filesystem was enabled.  The driver has built in locking so that only one process is allowed to have the /dev/rtc interface open at a time.
A user process can monitor these interrupts by doing a read or a select on /dev/rtc -- either will block/stop the user process until the next interrupt is received.
For managing clock time, if the kernel time is synchronized with an external source, the kernel will write the time back to the CMOS clock every 11 minutes. In the process of doing this, the kernel briefly turns off RTC periodic interrupts, so be aware of this if you are doing serious work.
The alarm and/or interrupt frequency are programmed into the RTC via various ioctl calls as listed in ./include/linux/rtc.h

New "RTC Class" Drivers

Because Linux supports many non-ACPI and non-PC platforms, some of which have more than one RTC style clock, it needed a more portable solution than expecting a single battery-backed MC146818 clone on every system. Accordingly, a new "RTC Class" framework has been defined.  It offers three different userspace interfaces:
·         /dev/rtcN ... much the same as the older /dev/rtc interface
·         /sys/class/rtc/rtcN ... sysfs attributes support readonly access to some RTC attributes.
·         /proc/driver/rtc ... the first RTC (rtc0) may expose itself using a procfs interface.  More information is (currently) shown here than through sysfs.
This new framework removes the "one RTC per system" restriction.

TSC

All 80x86 microprocessors include a CLK input pin, which receives the clock signal of an external oscillator. Starting with the Pentium, 80x86 microprocessors sport a counter that is increased at each clock signal.  The Time Stamp Counter is a 64-bit register present on all x86 processors since the Pentium [2]. It counts the number of clock ticks since reset. Instruction RDTSC returns the TSC in EDX:EAX. Its opcode is 0F 31.
For Pentiums, you can get the number of clock cycles elapsed since the last reboot with the following C code [5]
#include
static uint64_t get_tsc(void) {
                unsigned long long tsc;
                __asm__ __volatile__ ("rdtsc" : "=A" (tsc));
                return tsc; }
It is preferred that developers use read the value of CLOCK_MONOTONIC  clock using POSIX  clock_gettime function [3]. Note that gettimeofday() uses the clock_gettime(CLOCK_REALTIME) back end [4]. Similarly settimeofday() and clock_settime(CLOCK_REALTIME) use the same backend code, the only difference being that clock_settime allows for nanosecond resolution in setting the time. The result of clock_gettime() is guaranteed to be monotonic, and the resolution of clock_gettime() is nanoseconds.

PIT

It is also called the System Clock and accurately generates interrupts at regular time intervals. The chip itself has 3 channels: Channel 0 is tied to is tied to IRQ0, to interrupt the CPU at predictable and regular times, Channel 1 is system specific, and Channel 2 is connected to the system speaker. In this section, we are only concerned with Channel 0 - mapped to IRQ0. (The PIT is also used to drive an audio amplifier connected to the computer's internal speaker.)

HPET (hrtimers)

The High Precision Event Timer (HPET) is a new timer chip developed jointly by Intel and Microsoft. The hrtimers set of APIs uses these high precision timers. If an HPET is present in the system, it is preferred for use as a sytem clock verses using a PIT.

ACPI Power Management Timer (ACPI PMT)

Timer APIs

Interval Timers: setitimer

Interval timers are activated by means of the POSIX setitimer( ) system call. The first parameter specifies which of the following policies should be adopted:
·         ITIMER_REAL: The actual elapsed time; the process receives SIGALRM signals.
·         ITIMER_VIRTUAL: The time spent by the process in User Mode; the process receives SIGVTALRM signals.
·         ITIMER_PROF: The time spent by the process both in User and in Kernel Mode; the process receives SIGPROF signals.
The interval timers can be either single-shot or periodic. The second parameter of setitimer( ) points to a structure of type itimerval that specifies the initial duration of the timer (in seconds and nanoseconds) and the duration to be used when the timer is automatically reactivated (or zero for single-shot timers).The third parameter of setitimer( ) is an optional pointer to an itimerval structure that is filled by the system call with the previous timer parameters.
To implement an interval timer for each of the preceding policies, the process descriptor includes three pairs of fields:
·         it_real_incr and it_real_value
·         it_virt_incr and it_virt_value
·         it_prof_incr and it_prof_value
The first field of each pair stores the interval in ticks between two signals; the other field stores the current value of the timer.
The ITIMER_REAL interval timer is implemented by using dynamic timers because the kernel must send signals to the process even when it is not running on the CPU. Therefore, each process descriptor includes a dynamic timer object called real_timer. The setitimer( ) system call initializes the real_timer fields and then invokes add_timer( ) to insert the dynamic timer in the proper list. When the timer expires, the kernel executes the it_real_fn( ) timer function. In turn, the it_real_fn( ) function sends a SIGALRM signal to the process; then, if it_real_incr is not null, it sets the expires field again, reactivating the timer.
The ITIMER_VIRTUAL and ITIMER_PROF interval timers do not require dynamic timers, because they can be updated while the process is running. The account_it_virt( ) and account_it_prof( ) functions are invoked by update_ process_times( ), which is called either by the PIT's timer interrupt handler (UP) or by the local timer interrupt handlers (SMP). Therefore, the two interval timers are updated once every tick, and if they are expired, the proper signal is sent to the current process.

Interval Timers: alarm

The alarm system call is the same as settimer system call with REAL signal (explained above). Expiry times are given in seconds.

 

Posix Timers: timer_create

The POSIX 1003.1b standard introduced a new type of software timers for User Mode programs in particular, for multithreaded and real-time applications. These timers are often referred to as POSIX timers.
These set of calls allow per thread creation of timers, with a user defined signal to be sent on timer expiry. The Linux 2.6 kernel offers two types of POSIX clocks:
·         CLOCK_REALTIME:  This virtual clock represents the real-time clock of the system which is essentially the value of the xtime variable.
·         CLOCK_MONOTONIC:  This virtual clock represents the real-time clock of the system purged of every time warp due to the synchronization with an external time source.
The Linux kernel implements the POSIX timers by means of dynamic timers. POSIX timers, however, are much more flexible and reliable than traditional interval timers. A couple of significant differences between them are:
·         When a traditional interval timer decays, the kernel always sends a SIGALRM signal to the process that activated the timer. Instead, when a POSIX timer decays, the kernel can send every kind of signal, either to the whole multithreaded application or to a single specified thread. The kernel can also force the execution of a notifier function in a thread of the application, or it can even do nothing (it is up to a User Mode library to handle the event).
·         If a traditional interval timer decays many times but the User Mode process cannot receive the SIGALRM signal (for instance because the signal is blocked or the process is not running), only the first signal is received: all other occurrences of SIGALRM are lost. The same holds for POSIX timers, but the process can invoke the timer_getoverrun( ) system call to get the number of times the timer decayed since the generation of the first signal.
In a process that has multiple threads, it is important to understand that as per POSIX API, there are certain properties that are not shared among threads. Some of these are signal mask, errno variable, thread id (via gettid call), etc. Then, there are certain properties that are shared among the threads in the same thread group. These are PID, Parent PID, Interval timers and Posix timers.
Use the following call to check if your Linux system is NPTL compliant. If the following shell command
getconf GNU_LIBPTHREAD_VERSION
 returns a string containing NPTL, then your system uses NPTL libraries, and the above POSIX compliance holds true.

 

Select Call: timerfd

The system calls timerfd_create/gettime/settime operate on a timer that delivers timer expiration notifications via a file descriptor.  They provide an alternative to the use of setitimer or timer_create, with the advantage that the file descriptor may be monitored by select, poll, and epoll [7].

Library: libevent

The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts [8,9].

Standard usage

Every program that uses libevent must include the header, and pass the -levent flag to the linker. Before using any of the functions in the library, you must call event_init() or event_base_new() to perform one-time initialization of the libevent library.

Event notification

For each file descriptor that you wish to monitor, you must declare an event structure and call event_set() to initialize the members of the structure. To enable notification, you add the structure to the list of monitored events by calling event_add(). The event structure must remain allocated as long as it is active, so it should be allocated on the heap. Finally, you call event_dispatch() to loop and dispatch events.

Timers

libevent can also be used to create timers that invoke a callback after a certain amount of time has expired. The evtimer_set() function prepares an event struct to be used as a timer. To activate the timer, call evtimer_add(). Timers can be deactivated by calling evtimer_del().

Timeouts

In addition to simple timers, libevent can assign timeout events to file descriptors that are triggered whenever a certain amount of time has passed with no activity on a file descriptor. The timeout_set() function initializes an event struct for use as a timeout. Once initialized, the event must be activated by using timeout_add(). To cancel the timeout, call timeout_del().

References

[9] http://www.monkey.org/~provos/libevent/doxygen-1.4.3/

No comments:

Post a Comment