Linux Application Development (paperback) (2nd Edition)

   

18.2. Using Timers

A timer is simply a way of scheduling an event to happen at some point in the future. Instead of looping around, looking at the current time, and wasting CPU cycles, a program can ask the kernel to notify it when at least a certain amount of time has passed.

There are two ways to use timers: synchronously and asynchronously. The only way to use a timer synchronously is to wait for the timer to expire sleeping. Using a timer asynchronously, like every other asynchronous facility, involves signals. What may be surprising is that using a timer synchronously may also involve signals.

18.2.1. Sleeping

A process requesting that it not be scheduled for at least a certain amount of time is called sleeping. Four functions are available for sleeping; each measures time in different units. They also have slightly different behavior and interact with other parts of the system differently.

unsigned int sleep(unsigned int seconds);

sleep() causes the current process to sleep at least seconds seconds or until a signal that the process does not ignore is received by the process. On most platforms, sleep() is implemented in terms of the SIGALRM signal, and therefore it does not mix well with using the alarm() system call, creating a SIGALRM handler, ignoring the SIGALRM signal, or using ITIMER_REAL interval timers (described later), which share the same timer and signal.

If sleep() does not sleep for the full time allotted, it returns the number of seconds left to sleep. If it has slept at least as long as requested, it returns zero.

void usleep(unsigned long usec);

usleep() causes the current process to sleep at least usec microseconds. No signals are used. On most platforms, usleep() is implemented via select().

int select(0, NULL, NULL, NULL, struct timeval tv);

select(), documented in Chapter 13, provides a portable way to sleep for a precise amount of time. Simply fill in the struct timeval with the minimum amount of time you wish to wait, and do not wait for any events to occur.

int nanosleep(struct timespec *req, struct timespec *rem);

nanosleep() causes the current process to sleep at least the amount of time specified in req (see page 484 for timespec), unless a signal is received by the process. If nanosleep() terminates early due to a received signal, it returns -1 and sets errno to EINTR and, if rem is not NULL, sets rem to represent the amount of time remaining in the sleep period.

nanosleep() is currently the least portable of these functions, because it was specified as part of the POSIX.1b (previously called POSIX.4) real-time specification, which is not implemented on all versions of Unix. However, all new Unix implementations implement it, because the POSIX.1b functions are now a standard part of the Single Unix Specification.

Not all platforms that provide the nanosleep() function provide high accuracy, but Linux, like other real-time operating systems, attempts to honor short waits with extreme accuracy for real-time processes. See Programming for the Real World [Gallmeister, 1995] for more information on real-time programming.

18.2.2. Interval Timers

Interval timers, once enabled, continually deliver signals to a process on a regular basis. Exactly what regular means depends on which interval timer you use. Each process has three interval timers associated with it.

ITIMER_REAL

Tracks time in terms of the clock on the wall real time, regardless of whether the process is executing and delivers a SIGALRM signal. It conflicts with the alarm() system call, which is used by the sleep() function. Use neither alarm() nor sleep() if you have a real itimer active.

ITIMER_VIRTUAL

Counts time only when the process is executing excluding any system calls the process makes and delivers a SIGVTALRM signal.

ITIMER_PROF

Counts time when the process is executing including the time the kernel spends executing system calls on the behalf of the process, but not including any time spent processing interrupts on behalf of the process and delivers a SIGPROF signal. Accounting for time spent processing interrupts would be so expensive that it would change the timings.

The combination of ITIMER_VIRTUAL and ITIMER_PROF is often used for profiling code.

Each of these timers generates its associated signal within one system clock tick (normally 1-10 milliseconds) of the timer expiring. If the process is currently running when the signal is generated, it is delivered immediately; otherwise, it is delivered soon afterward, depending on system load. Since ITIMER_VIRTUAL is tracked only when the process is running, it is always delivered immediately.

Use a struct itimerval to query and set itimers:

struct itimerval { struct timeval it_interval; struct timeval it_value; };

The it_value member is the amount of time left until the next signal is sent. The it_interval member is the amount of time between signals; it_value is set to this value each time the timer goes off.

There are two system calls for dealing with interval timers. Both take a which argument that specifies which timer to manipulate.

int getitimer(int which, struct itimerval *val);

Fills in val with the current state of the which timer.

int setitimer(int which, struct itimerval *new, struct itimerval *old);

Sets the which timer to new and fills in old with the previous setting if it is non-NULL.

Setting a timer's it_value to zero immediately disables it; the timer will no longer be called. Setting a timer's it_interval to zero disables it after the next time the timer triggers.

In the following example, a parent process starts a child process, runs a one-second ITIMER_REAL timer, sleeps for 10 seconds, and then kills the child process:

1: /* itimer.c */ 2: 3: #include <stdio.h> 4: #include <stdlib.h> 5: #include <sys/wait.h> 6: #include <unistd.h> 7: #include <string.h> 8: #include <signal.h> 9: #include <sys/time.h> 10: 11: 12: void catch_signal (int ignored) { 13: static int iteration=0; 14: 15: printf("caught interval timer signal, iteration %d\n", 16: iteration++); 17: } 18: 19: pid_t start_timer (int interval) { 20: pid_t child; 21: struct itimerval it; 22: struct sigaction sa; 23: 24: if (!(child = fork())) { 25: memset(&sa, 0, sizeof(sa)); 26: sa.sa_handler = catch_signal; 27: sigemptyset(&sa.sa_mask); 28: sa.sa_flags = SA_RESTART; 29: 30: sigaction(SIGALRM, &sa, NULL); 31: 32: memset(&it, 0, sizeof(it)); 33: it.it_interval.tv_sec = interval; 34: it.it_value.tv_sec = interval; 35: setitimer(ITIMER_REAL, &it, NULL); 36: 37: while (1) pause(); 38: } 39: 40: return child; 41: } 42: 43: void stop_timer (pid_t child) { 44: kill (child, SIGTERM); 45: } 46: 47: int main (int argc, const char **argv) { 48: pid_t timer = 0; 49: 50: printf("Demonstrating itimers for 10 seconds, " 51: "please wait...\n"); 52: timer = start_timer(1); 53: sleep(10); 54: stop_timer(timer); 55: printf("Done.\n"); 56: 57: return 0; 58: }


       
     

    Категории