problems with alarm(0)

johan johan at philmds.UUCP
Tue May 22 20:26:13 AEST 1984


Due to a bug in the kernel it may happen that an alarm
signal/interrupt is processed after the execution of alarm(0).
This may cause problems as I will demonstrate.

Several system calls may last long/forever, like read/write to
a character device.
Bounding this period can be done by using alarm()/signal(), like in

	onalarm()
	{
		signal(SIGALRM, onalarm);
	A:
	}

	{
		...
		signal(SIGALRM, onalarm);
		alarm(2);
	B:
		i = read(fd, buf, sizeof(buf));
		alarm(0);
		if (i == -1 && errno == EINTR) {
			...  /* handle timeout */
		} else {
			...  /* handle data read */
		}
		...
	}

This scheme may fail on busy systems if your process is suspended for
several seconds at label B. This can be cured by restarting the timer
in the routine onalarm() by adding the statement

		alarm(2)

at label A.

All this is not new and used in many situations in existing code.
The problem is that even this last version is not behaving properly.
In all UNIX kernels I have seen it is possible that a SIGALRM signal
is processed whenever the alarm(0) returns from system to user mode.
This causes the timer to be restarted and may lead to failures of any
future interruptable system calls in that process.

A short description of the sequence of events that makes this happen:
 - the kernel is processing the alarm(0) system call
 - a clock interrupt occurs
 - the statement 'if (++lbolt >= HZ)' in clock() happens to be true
 - BASEPRI(ps) is 0, so p_clktim processing is done in clock()
 - for our process p_clktim happens to reach 0, so psignal() is called
 - the appropriate bit for SIGALRM in p_sig is set
 - alarm(0) processing is continued
 - just before returning to user mode p_sig is checked and will cause
   the SIGALRM signal to be processed.
BINGO.

Two possible solutions I can think of:

1 - Change the kernel to avoid this strange behaviour.
    The p_sig bit corresponding to SIGALRM must be reset for alarm(0).
    The statement:
	p->p_clktim = uap->deltat;
    must be replaced by
	if ((p->p_clktim = uap->deltat) == 0)
		p->p_sig &= ~(1<<(SIGALRM-1));
    Notice that this code is not surrounded by any spl()'s !!!!
    Adding a spl1()/spl0() pair may result in a more consistent return
    value from alarm().
    Always resetting p_sig is possible if spl()'s added.

2 - For the time being the above piece of program can be modified
    slightly. The trick is to maintain a variable indicating when the
    timer must be restarted in onalarm(). Clear this variable
    just before the alarm(0). The modified example looks like:

	int	keepticking;
	
	onalarm()
	{
		signal(SIGALRM, onalarm);
		if (keepticking)
			alarm(2);
	}

	{
		...
		signal(SIGALRM, onalarm);
		keepticking = 1;
		alarm(2);
		i = read(fd, buf, sizeof(buf));
		keepticking = 0;
		alarm(0);
		if (i == -1 && errno == EINTR) {
			...  /* handle timeout */
		} else {
			...  /* handle data read */
		}
		...
	}

    A suggested alternative:
	while (alarm(0) == 0)
		;
    fails because the return value can not be trusted unless the
    spl()'s are added.



NOTE: We haven't seen this problem in real life, but came across the
possibility during some discussions. It would be appreciated if anyone
struck by this phenomenon could confirm.

				Johan Stevenson,
				Philips S&I, T&M, PMDS,
				Building TQV-5,
				Eindhoven, The Netherlands.
				phone: +31 40 784736
				uucp: mcvax!philmds!johan



More information about the Comp.unix.wizards mailing list