A Simple Way to Interrupt System Calls Under 4.2 BSD

donn at sdchema.UUCP donn at sdchema.UUCP
Wed Dec 14 21:28:54 AEST 1983


Apparently some people are having trouble with the new 4.2 BSD signal
mechanism because it doesn't interrupt certain system calls.  Suppose
you have arranged to catch interrupt signals, and your program does a
read on a terminal and waits for someone to type in some data.  Now
suppose a person at the terminal hits the interrupt key: your signal
handling routine is called and returns.  What happens?  Instead of
aborting the read, it is restarted.  Under the old signal mechanism, we
expect the read to immediately return -1 with an 'interrupted system
call' error.

It would be nice to have the ability to cause system calls to be
aborted the old way.  Some people on the net have made some radical
suggestions about how this might be done:

	A nice clean way to handle this would be to have the
	signal-handling routine's return code indicate whether the
	system call which was interrupted, if any, should be
	restarted.  This would still require changes to old code which
	doesn't return anything meaningful from the signal handler.
	Maybe yet another flag, specified in the call to signal that
	gives the handler's address?

	 -- dmmartindale at watcgl.UUCP

	What we need here is an ioctl (fnctl) to set/clear the 'system
	call restart' feature, just like the 'close on exec' feature.

	 -- Clyde W. Hoover @ Univ. of Texas Computation Center

But I think it should be possible to get the proper effect without
making even more kernel changes.  One change that works is to use
select() instead of read(), since select() may be interrupted. (This
was suggested by steveh at hammer.)

Another possibility is to read the manual page on the new signals and
figure out how restarted system calls are implemented.  I tried this
and came up with a loathsome but interesting hack that appears to make
interrupted reads a snap.  The trick is to realize that one of the
parameters supplied to a signal handler is a pointer to a record of the
context in which the signal occurred.  This record contains the PC
where the process is to be restarted after the signal catcher returns.
For a restartable system call, this points at the system call itself:
when the signal handler exits, the PC is set to the same CHMK
instruction that initiated the system call previously, and the system
call is repeated.  To return elsewhere, one merely diddles this stored PC.

The following is a working C routine that diddles the stored PC so that
the system call appears to return immediately with a value of -1 and
error number set to 'interrupted system call':

---------------------------------------------------------------------------
/*
 * causeintr.c
 *
 * Function:	Allow signals to interrupt system calls.
 * Usage:	causeintr( scp )
 *		struct sigcontext *scp;
 * Date:	Wed Dec 14 03:35:06 PST 1983
 * Author:	Donn Seeley, UC San Diego
 * Remarks:
 *	This routine is strictly for use with the new 4.2 BSD signal
 *	mechanism, and has gross VAX dependencies.  It is completely
 *	and utterly non-portable and thus represents a gorgeous hack.
 *
 *	This routine must be compiled without using the '-O' optimizer,
 *	which will strip out the 'unreachable' error code given a chance.
 *
 *	The situation in which this routine is used is the following:
 *	You desire to use the new, modern, etc. signal mechanism in 4.2
 *	but you require some of your signal handlers to have the ability
 *	to interrupt a system call.  A handler which needs to interrupt
 *	a system call should be declared
 *
 *		handler( sig, code, scp )
 *			int			sig;
 *			int			code;
 *			struct sigcontext	*scp;
 *
 *	and should call 'causeintr( scp )' before returning.  If a
 *	slow system call was being processed when the signal was
 *	received, the system call will appear to return immediately
 *	with a return value of -1 and EINTR (interrupted system call)
 *	will be the error number in 'errno'.
 */

# include	<signal.h>
# include	<errno.h>

/*
 * CHMK is the change-mode-to-kernel opcode which all system calls use.
 */
# define	CHMK		0xbc

extern int	errno;

int
causeintr( scp )
	struct sigcontext	*scp;
{
	/*
	 * Hack, hack.
	 */
	union {
		int	(*fi_func)();
		int	fi_int;
	} fi;

	/*
	 * This goto is used to make 'error' appear at a fixed (known)
	 * offset from the beginning of the routine.  (Gag, splutter)
	 */
	goto normal;

error:
	/*
	 * Fake a return from an interrupted system call.
	 */
	errno		= EINTR;
	return ( -1 );

normal:
	/*
	 * Make sure that we are supposed to return to a system call.
	 * If so, then patch sc_pc so that we return to 'error' instead!
	 */
	if ( ((* (char *) scp->sc_pc) & 0xff) == CHMK ) {
		fi.fi_func	= causeintr;
		scp->sc_pc	= fi.fi_int + 0x6;	/* Magic number */
	}
	return ( 0 );
}
---------------------------------------------------------------------------

I even have a working program which contains an example of the use
of this routine:

---------------------------------------------------------------------------
/*
 * sigtest.c
 *
 * Trivial program to test 4.2 BSD signal handling.
 */

# include	<stdio.h>
# include	<signal.h>
# include	<errno.h>

# define	STDIN		0
# define	STDOUT		1
# define	STDERR		2

# define	mask(x)		(1 << (x))

extern int	dosig();
extern int	errno;

struct sigvec	sv, osv;
char		buf[256];

main()
{
	register int	i;
	int		m;

	sv.sv_handler	= dosig;
	sv.sv_mask	= mask( SIGINT ) | mask( SIGQUIT ) | mask( SIGTSTP );
	sv.sv_onstack	= 0;
	sigvec( SIGINT, &sv, &osv );
	sigvec( SIGQUIT, &sv, &osv );
	sigvec( SIGTSTP, &sv, &osv );

	do {
		errno		= 0;
		i		= read( STDIN, buf, sizeof buf );
		if ( i > 0 )
			fprintf( stderr, "Data: %.*s", i, buf );
		else if ( i == 0 )
			fprintf( stderr, "End of file\n" );
		else
			perror( "Error" );
	} while ( i != 0 );

	exit( 0 );
}


dosig( sig, code, scp )
	unsigned int		sig;
	int			code;
	struct sigcontext	*scp;
{
	psignal( sig, "Signal" );
	causeintr( scp );
}
---------------------------------------------------------------------------

This sample routine prints 'Data: input' for normal input, 'Signal:
signal-action' when an interrupt, quit or terminal stop signal is
generated at the keyboard, 'Error: error-message' when the read()
terminates abnormally, and 'End of file' when ^D is typed (it then
exits).  When the call to causeintr() is absent and an interrupt is
sent, the read does not return with an error; when it is present, the
error 'Interrupted system call' is printed.

Causeintr() is certainly simple to use -- can anyone point out any
severe drawbacks to it that would prevent it from being useful?
My personal feeling is that it is a large improvement over setjmp/
longjmp for this application...  (But of course I wrote the routine!)

Donn Seeley    UCSD Chemistry Dept. RRCF    ucbvax!sdcsvax!sdchema!donn
32 52' 30"N 117 14' 25"W  (619) 452-4016    sdcsvax!sdchema!donn at noscvax



More information about the Comp.unix.wizards mailing list