Salut Olivier (and Nishanth),

Regarding man page documentation of the problem of short sleeps 
for setiteimer(2)...

> > -- pointers to those threads
> 
> http://bugzilla.kernel.org/show_bug.cgi?id=4569
> http://lkml.org/lkml/2005/4/29/163
> 
> > -- indications of which kernel versions show this bahaviour
> 
> AFAIK, all versions as far as x86 is concerned.
> Dunno if it is hardware specific.
> 
> > -- a (short) test program to demonstrate it, if you have one.
> 
> See the bugzilla bug's attachments

Sorry for the long delay in following this up, but I've got to 
it now.  I tweaked your suggestions slightly:

{{
Timers will never expire before the requested time,
-instead expiring some short, constant time afterwards, dependent
-on the system timer resolution (currently 10ms).  
+but may expire some (short) time afterwards, which depends
+on the system timer resolution and on the system load.
+Upon expiration, a signal will be generated and the timer reset.
+If the timer expires while the process is active (always true for

+On certain systems (including x86), the Linux kernel has a bug which will
+produce premature timer expirations of up to one jiffy under some
+circumstances.
}}

Thanks for this bug reporet,

Nishanth: if and when your changes are accepted, and the problem 
is thus fixed, could you please send me a notification of that
fact, and I can then further amend the manual pages.

Cheers,

Michael



/* itimer_short_interval_bug.c 

   June 2005

   In current Linux kernels, an interval timer set using setitimer() 
   can sometimes sleep *less* than the specified interval.
   This program demonstrates the behaviour by looping through all
   itimer values from 1 microsecond upwards, in one microsecond steps.
*/
/* Adapted from a program by Olivier Croquette, June 2005 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>



typedef unsigned long long int u_time_t; /* in microsecs */

static int handler_flag;

/* return time as a number of microsecs  */

static u_time_t 
gettime(void ) 
{
    struct timeval tv;

    if ( gettimeofday(&tv, NULL) == -1) {
        perror("gettimeofday()");
        return 0;
    }
    return (tv.tv_usec + tv.tv_sec * 1000000LL);
}


static void 
handler (int sig, siginfo_t *siginfo, void *context) 
{
    handler_flag++;
    return ;
}


/* Sleep for 'time' microsecs. */
static int 
isleep(u_time_t time) 
{
    struct itimerval  newtv;
    sigset_t sigset;
    struct sigaction  sigact;

    if (time == 0)
        return 0;

    /* block SIGALRM */
    sigemptyset (&sigset);
    sigaddset (&sigset, SIGALRM);
    sigprocmask (SIG_BLOCK, &sigset, NULL);

    /* set up our handler */
    sigact.sa_sigaction  = handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = SA_SIGINFO;
    sigaction (SIGALRM, &sigact, NULL);
 
    newtv.it_interval.tv_sec  = 0;
    newtv.it_interval.tv_usec = 0;
    newtv.it_value.tv_sec     = time / 1000000;
    newtv.it_value.tv_usec    = time % 1000000;
    if (setitimer(ITIMER_REAL,&newtv,NULL) == -1) {
        perror("setitimer(set)");
        return 1;
    }

    sigemptyset (&sigset);
    sigsuspend (&sigset);
    return 0;
}


int 
main(int argc, char *argv[]) {
    u_time_t wait;
    int loop, numLoops;
    u_time_t t1, t2;
    u_time_t actual;
    long long minDiff, maxDiff, totDiff, diff;
    int numFail = 0;

    if (argc != 2) {
	fprintf(stderr, "Usage: %s num-loops\n", argv[0]);
	exit(EXIT_FAILURE);
    } /* if */

    numLoops = atoi(argv[1]);
    setbuf(stdout, NULL);

    for (wait = 1; ; wait++) {
	maxDiff = 0;
	numFail = 0;
	totDiff = 0;
	minDiff = -wait;

        if (wait % 10000 == 0)
	    printf("%llu\n", wait);
        
	for (loop = 0; loop < numLoops; loop++) {
            t1 = gettime();

            handler_flag = 0;
            isleep(wait);
	    
	    if ( handler_flag != 1 ) 
                printf("Problem with the handler flag (%d)!\n", handler_flag);
    
            t2 = gettime();
            actual = t2 - t1;
            if ( actual < wait ) {
	        diff = actual - wait;
		if (diff < maxDiff)
		    maxDiff = diff;
		if (diff > minDiff)
		    minDiff = diff;
		totDiff += diff;
		numFail++;
	    } /* if */

        } /* for */
	if (numFail > 0) 
            printf("%llu: %3d fail (%4lld %4lld; avg=%6.1f)\n", 
		    wait, numFail, minDiff, maxDiff, 
		    (double) totDiff / numFail);
    } /* for */

    return 0;
} /* main */
This commit is contained in:
Michael Kerrisk 2005-06-13 09:01:49 +00:00
parent 561aa928db
commit c13fcab060
1 changed files with 16 additions and 8 deletions

View File

@ -53,6 +53,7 @@ Timer values are defined by the following structures:
.PD 0
.RS .5i
.nf
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
@ -107,10 +108,10 @@ and
are significant in determining the duration of a timer.
.LP
Timers will never expire before the requested time,
instead expiring some short, constant time afterwards, dependent
on the system timer resolution (currently 10ms). Upon expiration, a
signal will be generated and the timer reset. If the timer expires
while the process is active (always true for
but may expire some (short) time afterwards, which depends
on the system timer resolution and on the system load.
Upon expiration, a signal will be generated and the timer reset.
If the timer expires while the process is active (always true for
.BR ITIMER_VIRT )
the signal will be delivered immediately when generated. Otherwise the
delivery will be offset by a small time dependent on the system loading.
@ -141,11 +142,13 @@ POSIX.1-2001, SVr4, 4.4BSD (this call first appeared in 4.2BSD).
.BR sigaction (2),
.BR signal (2)
.SH BUGS
Under Linux, the generation and delivery of a signal are distinct, and
there each signal is permitted only one outstanding event. It's therefore
conceivable that under pathologically heavy loading,
The generation and delivery of a signal are distinct, and
only one instance of each of the signals listed above may be pending
for a process.
Under very heavy loading, an
.B ITIMER_REAL
will expire before the signal from a previous expiration has been delivered.
timer may expire before the signal from a previous expiration
has been delivered.
The second signal in such an event will be lost.
On Linux, timer values are represented in jiffies.
@ -158,6 +161,11 @@ On Linux 2.6 on x86 (where a jiffy is 0.001 seconds),
this means that the ceiling value for a timer is
approximately 24.86 days.
On certain systems (including x86), the Linux kernel has a bug which will
produce premature timer expirations of up to one jiffy under some
circumstances.
.\" As at June 2005, the above holds in 2.4.x and 2.6.c (e.g., 2.6.12.)
POSIX.1 says that
.B setitimer
should fail if a