bug with setitimer on 4.2 BSD?

muller at sdccsu3.UUCP muller at sdccsu3.UUCP
Sun Apr 1 04:55:36 AEST 1984


There is not a problem with setitimer on 4.2, but in fact this is a classic
critical section problem. The important issue in this example is the period
that ITIMER_REAL is small: 30 millisecs. The call to setitimer causes a 
context switch to the kernel to start the counter. The counter is set and
another context switch is performed back to the program. Once back in the
program the sigpause is executed by performing another context switch to
the kernal to wait for the signal from the counter. This is fine for time
period values which are large in relation to the time required for the two
context switches (from the kernal to the program after the timer was set,
and from the program back to the kernel to execute the sigpause). However
if the signal is delivered BEFORE the sigpause is executed, the handler is
invoked, control flow returns to the main program, executes the sigpause,
and the program waits forever for a signal that will never arrive! (The 
current ITIMER_REAL has expired).

As with every critical section type problem you can never assume any
"rate" at which a program executes. The "real time" that it takes the
system to complete a context switch must be considered an unpredictable
value. So what must be done is to guard against (prevent) the SIGALRM
from being delivered after the ITIMER_REAL is set and before the sigpause
is executed. On way is to use sigblock to block off SIGALRM. Now sigblock
returns the old signal mask of blocked signals. Sigpause takes an arguement
of a mask of signals that are blocked. What sigpause does nicely is
to provide an ATOMIC operation (in one single context switch) that waits
for a signal using the signal mask provided as an arguement. When an
unmasked signal arrives the signal mask that exsisted before the call to
sigpause is restored. (Note: I would not even attempt to do this with 
System V unix).

So to make the program work you block off SIGALRM before the for loop
saving the signal mask that is returned by sigblock. Now when setitimer
returns no matter how long the context switches take the SIGALRM is blocked.
So even if SIGALRM is pending, the sigpause is guarenteed to execute. The
old mask returned by sigblock is used as the signal mask that sigpause uses
to wait for the SIGALRM (that mask does NOT have SIGALRM masked). This
provides protection of the "critical section" (the code between the setitimer
and the sigpause). 

The following code is one way to protect the sigpause.

/*******************************************************************/
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

struct	itimerval timer;
int	wakeup;

main(argc, argv)
int argc;
char **argv;
{
	int	alarmtrap();
	register int i;
	int oldmask;

	signal(SIGALRM, alarmtrap);
	timer.it_interval.tv_sec = 0;
	timer.it_interval.tv_usec = 0;
	timer.it_value.tv_sec = 0;
	wakeup = 0;
	/*
	 * block off that nasty SIGALRM
	 */
	oldmask = sigblock(1 << (SIGALRM-1));
	for (i=0; i<1000; i++) {
		timer.it_value.tv_usec = 30000;
		setitimer(ITIMER_REAL, &timer, 0);
		/*
		 * crical section now protected from SIGALRM
		 */
		sigpause(oldmask);
	}
}

alarmtrap() {
	wakeup++;
	fprintf(stderr,"alarm %d\n", wakeup);
}



			Keith Muller
			UCSD Computer Center
			ucbvax!sdcsvax!sdccsu3!muller



More information about the Comp.unix.wizards mailing list