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