Team-FLY |
9.6 Timer Drift , Overruns and Absolute TimeOne of the problems associated with POSIX:TMR timers and POSIX:XSI timers, as described so far, is the way they are set according to relative time. Suppose you set a periodic interrupt with an interval of 2 seconds, as in Program 9.7 or Program 9.11. When the timer expires , the system automatically restarts the timer for another 2-second interval. Let's say the latency between when the timer was due to expire and when the timer was reset is 5 m sec. The actual period of the timer is 2.000005 seconds. After 1000 interrupts the timer will be off by 5 ms. This inaccuracy is called timer drift . The problem can be even more severe when the timer is restarted from the timer signal handler rather than from the it_interval field of struct itimerval or struct itimerspec . In this case, the latency depends on the scheduling of the processes and the timer resolution. A typical timer resolution is 10 ms. With a latency of 10 ms, the timer drift will be 10 seconds after 1000 iterations. Exercise 9.20Consider an extreme case of a repeating timer with period of 22 ms when the timer has a resolution of 10 ms. Estimate the timer drift for 10 expirations of the timer. Answer: If you set the time until expiration to be 22 ms, this value will be rounded up to the clock resolution to give 30 ms, giving a drift of 8 ms every 30 ms. These results are summarized in the following table. The drift grows by 8 ms on each expiration.
One way to handle the drift problem is keep track of when the timer should actually expire and adjust the value for setting the timer each time. This method uses absolute time for setting the timer rather than relative time . Exercise 9.21For the specific case described by Exercise 9.20, devise a procedure for setting the timers according to absolute time. What is the timer drift for 10 iterations? Work out a chart similar to the one of Exercise 9.20. Answer:
If the timer resolution is 30 ms, then the time at the beginning of step 3 is approximately t = T + 30 ms, and the timer is set to expire in 12 ms. No matter how long the program runs, the total timer drift will be less than 10 ms.
The procedure of Exercise 9.21 assumes that the value (T - t + 22 ms) is never negative. You cannot set a timer to expire in the past. A negative value means that a timer expiration has been missed completely. This is called a timer overrun . A timer overrun also occurs when the timer is set to automatically restart and a new signal is generated before the previous one has been handled by the process. The POSIX:TMR timers can make it easier to use absolute time, and they can keep track of timer overruns. POSIX:TMR does not queue signals generated by the same timer. The timer_getoverrun function can be called from within the timer signal handler to obtain the number of missed signals. The flags parameter of timer_settime can be set to TIMER_ABSOLUTE to signify that the time given in the it_value member of the *value parameter represents the real time rather than a time interval. The time is related to the clock from which the timer was generated. Exercise 9.22Outline the procedure for using POSIX:TMR timers with absolute time to solve the problem of Exercise 9.21. Answer: The procedure for using absolute time with POSIX:TMR timers is as follows .
The abstime program of Program 9.13 demonstrates various scenarios for using the POSIX:TMR timer facility. Program 9.13 has three modes of operation: absolute time, relative time and automatic periodic reset. Use the abstime program as follows. abstime -a -r -p [inctime [numtimes [spintime]]] The first command-line argument must be -a , -r or -p specifying absolute time, relative time or automatic periodic reset. The optional additional arguments ( inctime , numtimes and spintime ) control the sequence in which timer expirations occur. The program generates numtimes SIGALARM signals that are inctime seconds apart. The signal handler wastes spintime seconds before handling the timer expiration. The abstime program uses a POSIX:TMR timer that is created with timer_create and started with timer_settime . For absolute times, the abstime program sets the TIMER_ABSTIME flag in timer_settime and sets the it_value member of value field to the current absolute time (time since January 1, 1970) plus the inctime value. When the timer expires, abstime calculates a new absolute expiration time by adding inctime to the previous expiration time. If relative time is set, the program sets it_value to the value specified by inctime . When the timer expires, the handler uses inctime to restart the timer. For periodic time, abstime sets relative time and automatically restarts the timer so that the handler does not have to restart it. The program calculates the time it should take to finish numtimes timer expirations and compares the calculated value with the actual time taken. Program 9.14 is a header file that defines a data type and the prototypes of the functions in Program 9.15 that are used in the main program of Program 9.13. You must link these files with Program 9.13 to run the abstime program. Example 9.23The following command uses abstime with absolute time. It simulates a signal handler that takes 5 milliseconds to execute and does 1000 iterations with a time interval of 22 milliseconds. If the timing were exact, the 5 milliseconds of spin time would not affect the total running time, which should be 22 seconds. abstime -a 0.022 1000 0.005 Exercise 9.24The command of Example 9.23 uses absolute time. Are there differences in output when it is run with relative time instead? Answer: For an execution of abstime -a 0.022 1000 0.005 the output might be the following. pid = 12374 Clock resolution is 10000.000 microseconds or 0.010000 sec. Using absolute time Interrupts: 1000 at 0.022000 seconds, spinning 0.005000 Total time: 22.0090370, calculated: 22.0000000, error = 0.0090370 For an execution of abstime -r 0.022 1000 0.005 the output might be the following. pid = 12376 Clock resolution is 10000.000 microseconds or 0.010000 sec. Using relative time Interrupts: 1000 at 0.022000 seconds, spinning 0.005000 Total time: 30.6357934, calculated: 22.0000000, error = 8.6357934 When absolute timers are used, the error is much less than 1 percent, while relative timers show the expected drift corresponding to the amount of processing time and timer resolution. The resolution of the clock is displayed by means of a call to clock_getres . A typical value for this might be anywhere from 1000 nanoseconds to 20 milliseconds. The 20 milliseconds (20,000,000 nanoseconds or 50 Hertz) is the lowest resolution allowed by the POSIX:TMR Extension. One microsecond (1000 nanoseconds) is the time it takes to execute a few hundred instructions on most fast machines. Just because a system has a clock resolution of 1 microsecond does not imply that a program can use timers with anything near this resolution. A context switch is often needed before the signal handler can be entered and, as Table 1.1 on page 5 points out, a context switch can take considerably longer than this. Example 9.25The following command uses Program 9.13 to estimate the effective resolution of the hardware timer on a machine by calling abstime with an inctime of 0, default numtimes of 1 and default spintime of 0. The abstime program displays the clock resolution and starts one absolute time clock interrupt to expire at the current time. The timer expires immediately. abstime -a 0 Example 9.26The following command uses Program 9.13 to determine the maximum number of timer signals that can be handled per second by starting 1000 timer interrupts with an inctime of 0. These should all expire immediately. The abstime program then displays the minimum time for 1000 interrupts. abstime -a 0.0 1000 0.0 Program 9.13 illustrates some other useful tips in using POSIX:TMR timers. Information about the timer that generated the signal is available in the signal handler. When a timer is created, an integer or a pointer can be stored in the sigev_value member of the struct sigevent structure. If the signal handler is to restart that timer or if multiple timers are to share a signal handler, the signal handler must have access to the timer ID of the timer that generated the signal. If the signal handler was set up with the SA_SIGINFO flag, it can access the value that timer_create stored in sigev_value through its second parameter. The timer_create cannot directly store the timer ID in its sigev_value because the ID is not known until after the timer has been created. It therefore stores a pointer to the timer ID in the sival_ptr member of union sigval . Program 9.13 abstime.cThe abstime program illustrates POSIX:TMR timers with absolute time. Program 9.14 and Program 9.15 are called . #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include "abstime.h" #define INCTIME 0.01 #define NUMTIMES 1 #define SPINTIME 0.0 int main(int argc, char *argv[]) { struct sigaction act; struct timespec clockres, currenttime; timer_data data; struct sigevent evp; sigset_t sigset; double tcalc, tend, tstart, ttotal; data.exitflag = 0; data.inctime = INCTIME; data.numtimes = NUMTIMES; data.spintime = SPINTIME; data.type = -1; if (argc > 1) { if (!strcmp(argv[1], "-r")) data.type = TYPE_RELATIVE; else if (!strcmp(argv[1], "-a")) data.type = TYPE_ABSOLUTE; else if (!strcmp(argv[1], "-p")) data.type = TYPE_PERIODIC; } if ( (argc < 2) (argc > 5) (data.type < 0) ){ fprintf(stderr, "Usage: %s -r -a -p [inctime [numtimes [spintime]]]\n", argv[0]); return 1; } if (argc > 2) data.inctime = atof(argv[2]); if (argc > 3) data.numtimes = atoi(argv[3]); if (argc > 4) data.spintime = atof(argv[4]); fprintf(stderr, "pid = %ld\n", (long)getpid()); act.sa_flags = SA_SIGINFO; act.sa_sigaction = timehandler; if ((sigemptyset(&act.sa_mask) == -1) (sigaction(SIGALRM, &act, NULL)) == -1) { perror("Failed to set handler for SIGALRM"); return 1; } evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGALRM; evp.sigev_value.sival_ptr = &data; if (timer_create(CLOCK_REALTIME, &evp, &data.timid) < 0) { perror("Failed to create a timer"); return 1; } if (clock_getres(CLOCK_REALTIME, &clockres) == -1) perror("Failed to get clock resolution"); else fprintf(stderr, "Clock resolution is %0.3f microseconds or %0.6f sec.\n", D_MILLION*time_to_double(clockres), time_to_double(clockres)); data.tvalue.it_interval.tv_sec = 0; data.tvalue.it_interval.tv_nsec = 0; data.tvalue.it_value = double_to_time(data.inctime); data.flags = 0; if (clock_gettime(CLOCK_REALTIME, ¤ttime) == -1) { perror("Failed to get current time"); return 1; } tstart = time_to_double(currenttime); if (data.type == TYPE_ABSOLUTE) { data.tvalue.it_value.tv_nsec += currenttime.tv_nsec; data.tvalue.it_value.tv_sec += currenttime.tv_sec; if (data.tvalue.it_value.tv_nsec >= BILLION) { data.tvalue.it_value.tv_nsec -= BILLION; data.tvalue.it_value.tv_sec++; } data.flags = TIMER_ABSTIME; fprintf(stderr,"Using absolute time\n"); } else if (data.type == TYPE_RELATIVE) fprintf(stderr,"Using relative time\n"); else if (data.type == TYPE_PERIODIC) { data.tvalue.it_interval = data.tvalue.it_value; fprintf(stderr,"Using periodic time\n"); } fprintf(stderr, "Interrupts: %d at %.6f seconds, spinning %.6f\n", data.numtimes, data.inctime, data.spintime); if (timer_settime(data.timid, data.flags, &data.tvalue, NULL) == -1){ perror("Failed to start timer"); return 1; } if (sigemptyset(&sigset) == -1) { perror("Failed to set up suspend mask"); return 1; } while (!data.exitflag) sigsuspend(&sigset); if (clock_gettime(CLOCK_REALTIME, ¤ttime) == -1) { perror("Failed to get expiration time"); return 1; } tend = time_to_double(currenttime); ttotal=tend - tstart; tcalc = data.numtimes*data.inctime; fprintf(stderr, "Total time: %1.7f, calculated: %1.7f, error = %1.7f\n", ttotal, tcalc, ttotal - tcalc); return 0; } Program 9.14 abstime.hThe abstime.h include file contains constants, type definitions, and prototypes used by abstime and abstimelib . #define BILLION 1000000000L #define D_BILLION 1000000000.0 #define D_MILLION 1000000.0 #define TYPE_ABSOLUTE 0 #define TYPE_RELATIVE 1 #define TYPE_PERIODIC 2 typedef struct { timer_t timid; int type; int flags; int numtimes; int exitflag; double inctime; double spintime; struct itimerspec tvalue; } timer_data; struct timespec double_to_time(double tm); double time_to_double(struct timespec t); void timehandler(int signo, siginfo_t* info, void *context); Program 9.15 abstimelib.cThe abstimelib module contains the signal handler and utility routines used by abstime . #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include "abstime.h" static struct timespec add_to_time(struct timespec t, double tm) { struct timespec t1; t1 = double_to_time(tm); t1.tv_sec = t1.tv_sec + t.tv_sec; t1.tv_nsec = t1.tv_nsec + t.tv_nsec; while (t1.tv_nsec >= BILLION) { t1.tv_nsec = t1.tv_nsec - BILLION; t1.tv_sec++; } return t1; } static int spinit (double stime) { /* loops for stime seconds and returns */ struct timespec tcurrent; double tend, tnow; if (stime == 0.0) return 0; if (clock_gettime(CLOCK_REALTIME, &tcurrent) == -1) return -1; tnow = time_to_double(tcurrent); tend = tnow + stime; while (tnow < tend) { if (clock_gettime(CLOCK_REALTIME, &tcurrent) == -1) return -1; tnow = time_to_double(tcurrent); } return 0; } /* ------------------------- Public functions -------------------------- */ double time_to_double(struct timespec t) { return t.tv_sec + t.tv_nsec/D_BILLION; } struct timespec double_to_time(double tm) { struct timespec t; t.tv_sec = (long)tm; t.tv_nsec = (tm - t.tv_sec)*BILLION; if (t.tv_nsec == BILLION) { t.tv_sec++; t.tv_nsec = 0; } return t; } void timehandler(int signo, siginfo_t* info, void *context) { timer_data *datap; static int timesentered = 0; timesentered++; datap = (timer_data *)(info->si_value.sival_ptr); if (timesentered >= datap->numtimes) { datap->exitflag = 1; return; } if (spinit(datap->spintime) == -1) { write(STDERR_FILENO, "Spin failed in handler\n", 23); datap->exitflag = 1; } if (datap->type == TYPE_PERIODIC) return; if (datap->type == TYPE_ABSOLUTE) datap->tvalue.it_value = add_to_time(datap->tvalue.it_value, datap->inctime); if (timer_settime(datap->timid, datap->flags, &datap->tvalue, NULL) == -1) { write(STDERR_FILENO, "Could not start timer in handler\n",33); datap->exitflag = 1; } } Program 9.16 timesignals.cA program that calculates the time to receive 1000 SIGALRM signals . #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/time.h> #define COUNT 1000 #define MILLION 1000000L static int count = 0; /* ARGSUSED */ static void handler(int signo, siginfo_t *info, void *context) { count++; } int main(void) { struct sigaction act; sigset_t sigblocked, sigunblocked; long tdif; struct timeval tend, tstart; act.sa_flags = SA_SIGINFO; act.sa_sigaction = handler; if ((sigemptyset(&act.sa_mask) == -1) (sigaction(SIGALRM, &act, NULL) == -1)) { perror("Failed to set up handler for SIGALRM"); return 1; } if ((sigemptyset(&sigblocked) == -1) (sigemptyset(&sigunblocked) == -1) (sigaddset(&sigblocked, SIGALRM) == -1) (sigprocmask(SIG_BLOCK, &sigblocked, NULL) == -1)) { perror("Failed to block signal"); return 1; } printf("Process %ld waiting for first SIGALRM (%d) signal\n", (long)getpid(), SIGALRM); sigsuspend(&sigunblocked); if (gettimeofday(&tstart, NULL) == -1) { perror("Failed to get start time"); return 1; } while (count <= COUNT) sigsuspend(&sigunblocked); if (gettimeofday(&tend, NULL) == -1) { perror("Failed to get end time"); return 1; } tdif = MILLION*(tend.tv_sec - tstart.tv_sec) + tend.tv_usec - tstart.tv_usec; printf("Got %d signals in %ld microseconds\n", count-1, tdif); return 0; } Although the timer resolution might be as large as 10 ms, signals may be processed at a much higher rate than timer signals can be generated. Program 9.16 waits for SIGALRM signals and calculates the time to receive 1000 signals after the first one arrives. You can use Program 9.17 to send signals to a process. It takes two command-line arguments: a process ID and a signal number. It sends the signals as fast as it can until the process dies. A reasonably fast machine should be able to handle several thousand signals per second. Program 9.17 multikill.cThe multikill program continually sends signals to another process until the process dies . #include <signal.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int pid; int sig; if (argc != 3) { fprintf(stderr, "Usage: %s pid signal\n", argv[0]); return 1; } pid = atoi(argv[1]); sig = atoi(argv[2]); while (kill(pid, sig) == 0) ; return 0; } |
Team-FLY |