9.6 Timer Drift, Overruns and Absolute Time

Team-FLY

9.6 Timer Drift , Overruns and Absolute Time

One 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.20

Consider 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.

expiration number

 

1

2

3

4

5

6

7

8

9

10

time

30

60

90

120

150

180

210

240

270

300

drift

8

16

24

32

40

48

56

64

72

80

desired expiration

22

44

66

88

110

132

154

176

198

220

242

timer set for

22

22

22

22

22

22

22

22

22

22

22

rounded to resolution

30

30

30

30

30

30

30

30

30

30

30

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.21

For 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:

  1. Before starting the timer for the first time, determine the current time, add 22 ms to this and save the value as T. This is the desired expiration time.

  2. Set the timer to expire in 22 ms.

  3. In the signal handler, determine the current time, t. Set the timer to expire in time (T - t + 22 ms). Add 22 ms to T so that T represents the next desired expiration time.

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.

expiration number

 

1

2

3

4

5

6

7

8

9

10

time

30

50

70

90

110

140

160

180

200

220

drift

8

6

4

2

8

6

4

2

desired expiration

22

44

66

88

10

132

154

176

198

220

242

timer set for

22

14

16

18

20

22

14

16

18

20

22

rounded to resolution

30

20

20

20

20

30

20

20

20

20

30

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.22

Outline 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 .

  1. Before starting the first timer for the first time, determine the current time by using clock_gettime and add 22 ms to this. Save this value as T.

  2. Set the timer to expire at time T. Use the TIMER_ABSOLUTE flag.

  3. In the timer signal handler, add 22 ms to T and set the timer to expire at time T.

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.23

The 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.24

The 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.25

The 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.26

The 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.c

The 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, &currenttime) == -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, &currenttime) == -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.h

The 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.c

The 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.c

A 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.c

The 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


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net