home *** CD-ROM | disk | FTP | other *** search
- Xref: sparky comp.lang.c:11605 comp.unix.programmer:3916
- Path: sparky!uunet!cis.ohio-state.edu!pacific.mps.ohio-state.edu!linac!att!ucbvax!dog.ee.lbl.gov!horse.ee.lbl.gov!torek
- From: torek@horse.ee.lbl.gov (Chris Torek)
- Newsgroups: comp.lang.c,comp.unix.programmer
- Subject: Re: trapping "control C"
- Keywords: setjmp longjmp signal
- Message-ID: <24897@dog.ee.lbl.gov>
- Date: 26 Jul 92 01:10:23 GMT
- References: <1992Jul17.082746.13375@ibmpcug.co.uk> <1992Jul17.141248.4837@mdd.comm.mot.com> <1992Jul17.232515.6365@athena.mit.edu> <1992Jul20.235307.23706@seas.smu.edu>
- Reply-To: torek@horse.ee.lbl.gov (Chris Torek)
- Followup-To: comp.unix.programmer
- Organization: Lawrence Berkeley Laboratory, Berkeley
- Lines: 174
- NNTP-Posting-Host: 128.3.112.15
-
- (I am moving this to comp.unix.programmer, since most of this has
- nothing to do with the C language itself, but rather with details of
- its use under Unix systems.)
-
- In article <1992Jul17.232515.6365@athena.mit.edu> scs@adam.mit.edu
- (Steve Summit) notes that
- >>This bug (catching SIGINT unilaterally, without first testing if
- >>it is being ignored) is rampant, because it is immaterial if you
- >>don't have multitasking and background processes, and because
- >>even on Unix it (the bug) is not noticed under the C shell.
-
- This is correct: Unix programs should first verify that SIGINT is
- *supposed* to be caught or defaulted before `taking it over'. This
- applies also to SIGHUP and SIGQUIT (these three are the only `old
- shell' compatible `tty generated' signals).
-
- In article <1992Jul20.235307.23706@seas.smu.edu> mustafa@seas.smu.edu
- (Mustafa Kocaturk) writes:
- >I have tried to write the following `usleep' function by using
- >the SIGALRM signal and some library functions. It works even when it is
- >run in the background ...
-
- Since SIGALRM is not a `tty signal' it can be caught or defaulted
- without first testing its disposition, provided you control the rest
- of the source. (A library function should be more careful.)
-
- (Steve Summit again:)
- >>Signal handling is really one of the cases where portability and
- >>pragmatism collide head-on. ...
-
- Indeed.
-
- >In my humble opinion, this lack of guarantee should not prevent some
- >questioning programmers to use longjmp inside a signal handler function.
-
- It should at the least discourage casual usage of signal functions.
- Writing correct signal functions is difficult (as the example below
- will illustrate).
-
- >After all, most of the uses for a `longjmp', in my opinion, are for
- >returning from signal handlers and low-level interrupt routines.
-
- On the contrary, longjmp is most likely to be used portably to `escape'
- from several levels of function call. For instance, a program that
- writes to files (using stdio) may use longjmp to `escape out' of an
- inner output-routine all the way back to setup code that can erase a
- temporary file and abort, thus recovering from, or at least failing
- gracefully under, an overfull disk.
-
- >The object `sigcontext' contains largely system-dependent data that
- >the signal handler can, if necessary, examine and modify before it
- >returns or terminates.
-
- In fact, *having* a `sigcontext' structure is in violation of ANSI C.
- A signal handler must be called with exactly one `int' argument, that
- being the signal number. (Unix systems can handle this safely simply
- by not delivering sigcontexts to *signal* functions, but sending them
- on to *sigaction* functions. A small `wrapper' function in the C
- library will handle this, at some cost in efficiency. Currently,
- though, most Unix systems simply live dangerously and pass `extra'
- parameters to signal functions, hoping that this causes no trouble.)
-
- >#include <stdio.h>
- >#include <signal.h>
- >#include <sys/time.h>
- >#include <setjmp.h>
- >
- >jmp_buf env;
- >
- >int inthand(int sig, int code, struct sigcontext *scp)
- > {
- > longjmp(env,1);
- > }
- >
- >int usleep(int usecs)
- > {
- > struct itimerval value, ovalue;
- > void (*prevh)();
- > value.it_interval.tv_sec=value.it_interval.tv_usec=0;
- > value.it_value.tv_sec=usecs/1000000;
- > value.it_value.tv_usec=usecs%1000000;
- > setitimer(ITIMER_REAL,&value,&ovalue);
- > if(setjmp(env)!=0)
- > {
- > signal(SIGALRM,prevh);
- > return 0;
- > }
- > prevh=signal(SIGALRM,inthand);
- > sigpause(0);
- > }
-
- There are at least two bugs in this code. First (and somewhat
- irrelevant due to Unix systems `living dangerously' as noted above),
- inthand() should take only its `int sig' argument, or the code
- should use sigaction() to set up the catching function.
-
- The most important bug here is a race condition. This may cause the
- program to fail `mysteriously'. In particular, if the system is
- heavily loaded, and/or the time passed to usleep() is sufficiently
- short, the setitimer call may succeed and then the alarm may occur
- before the setjmp() or the textually-second signal() call finishes. In
- this case, the SIGALRM will be handled according to the previous
- disposition. If the signal was previously ignored, the program will
- then hang at the sigpause() until some other signal occurs. If the
- signal was previously caught, the old catcher will run (what effect
- this has obviously depends on that function). If the signal was
- previously defaulted, the program will terminate.
-
- Even if we do get far enough to catch the signal, the sigpause() may
- finish due to some *other* caught signal. The sigpause(), or in POSIX,
- sigsuspend(), waits until a signal occurs *and then returns*. (It may
- `grab' more than one signal if several pile up `all at once', but as
- soon as it gets one it can proceed.) In this case, the alarm may
- eventually occur *after usleep has returned*, and the longjmp() will
- then jump to a nonexistent or overwritten function activation.
-
- (All of the above is lumped under one `race condition' bug.)
-
- As side bugs, the routine restores the previous SIGALRM handler but
- does not restore any previous interval timer, and it ignores possible
- error returns from setitimer and signal. Restoring previous intervals
- is difficult and probably not worthwhile, as is attempting to handle
- the case where an existing value would cause the alarm to fire *before*
- the time to usleep is up.
-
- The usleep() function *can* be implemented using something like the
- code above, provided that:
-
- A. You set and arrange to catch SIGALRM `atomically';
- B. You loop calling sigpause().
-
- Provision A can be handled with sigblock and sigsetmask or their POSIX
- equivalents (sigprocmask with SIG_BLOCK and SIG_UNBLOCK or
- SIG_SETMASK). Once you combine this with B, however, you can drop the
- setjmp and longjmp and simply test a variable, looping until it is set
- or cleared by your alarm handler. Thus:
-
- static volatile sig_atomic_t caught;
-
- static void
- catch(int sig)
- {
-
- caught = 1;
- }
-
- void
- usleep(unsigned usecs)
- {
- struct itimerval value;
- sigset_t mask, omask, pausemask;
- void (*prevh)(int);
-
- value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
- value.it_value.tv_sec = usecs / 1000000;
- value.it_value.tv_usec = usecs % 1000000;
- (void) sigemptyset(&mask);
- (void) sigaddset(&mask, SIGALRM);
- (void) sigprocmask(SIG_BLOCK, &mask, &omask);
- pausemask = omask; /* in case SIGALRM was already blocked... */
- (void) sigdelset(&pausemask, SIGALRM);
- (void) setitimer(ITIMER_REAL, &value, (struct itimerval *)NULL);
- prevh = signal(SIGALRM, catch);
- while (!caught)
- sigsuspend(&pausemask);
- (void) signal(SIGALRM, prevh);
- (void) sigprocmask(SIG_SETMASK, &omask, (sigmask_t *)NULL);
- }
-
- This still has plenty of room for improvement, but at least the races
- are gone and the types all match up with various standards.
- --
- In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
- Berkeley, CA Domain: torek@ee.lbl.gov
-