[comp.unix.questions] msleep

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (11/09/87)

/**************************************************************************
 *                                                                        *
 *                        msleep                                          *
 *                                                                        *
 **************************************************************************

   Purpose :   What you all been waiting for -- A sleep that works in
	     microseconds!!!.  Actually it depends on the granularity
	     of the interrupt timer, but at least its better than
	     the second wait.

   Programmer : Eddie Wyatt (orginally written by Paul Allen)
 
   Date : November 1987

   Input :  
	sec - number of seconds to sleep
	msec - number of micro seconds to sleep 

   Output : None

   Locals : 
	off - itimer value to turn off the timer interrupt.
	time - wait time
	oldtime - old itime value
	bob - the old interrupt handler

   Globals : 

 ************************************************************************/

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

/* ARGSUSED */
static int signal_handler(sig,code,scp)
    int sig, code;
    struct sigcontext *scp;
    { 
    return(1);
    }

static struct itimerval off = {{0,0},{0,0}};


void msleep(sec,msec)
    int sec, msec;
    {
    struct itimerval time;
    struct itimerval oldtime;
    int (*bob)();

    if ((sec == 0) && (msec == 0)) return;

	/* note that we have the timer interrupting continously as
	   oppose to just once.  This is because if we were to
	   install the itimer to interrupt just once, there would be a
	   critial section (the space between setitimer and sigpause)
	   where if the interrupt sound then, it would be lost and
	   we would wait forever in sigpause */

    time.it_interval.tv_sec = sec;
    time.it_interval.tv_usec = msec;

    time.it_value.tv_sec = sec;
    time.it_value.tv_usec = msec;

	/* insert my interrupt handler and itimer val (order matters) while
	   saving old values to be re-instated */

    bob = signal(SIGALRM, signal_handler);
    if (setitimer(ITIMER_REAL, &time, &oldtime) < 0)
	perror("setitimer");

	/* wait until an interrupt happens, hopefully a timer interrupt -
	  but it need not be. */
    
    sigpause(0);

	/* turn timer off while returning timer and interrupt handler
	   back to the old state (order matters) */

    if (setitimer(ITIMER_REAL, &off, (struct itimerval *) 0) < 0)
	perror("setitimer");

    if ((int) signal(SIGALRM, bob) == -1)
	perror("signal");

    if (setitimer(ITIMER_REAL, &oldtime, (struct itimerval *) 0))
	perror("setitimer");
    }

-- 
That one looks Jewish -- And that one's a coon -- Who let all this riff raff
into the room -- There's one smoking a joint -- and Anoter with spots -- If
I had my way -- I'd have all of you shot ... Pink Floyd, "In the Flesh"
Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

chris@mimsy.UUCP (Chris Torek) (11/12/87)

In article <332@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:
>/**************************************************************************
> *                                                                        *
> *                        msleep                                          *
> *                                                                        *
> **************************************************************************
>
>   Purpose :   What you all been waiting for -- A sleep that works in
>	     microseconds!!!.  Actually it depends on the granularity
>	     of the interrupt timer, but at least its better than
>	     the second wait.

... followed by code that uses setitimer.

0) Only certain 4BSD-based operating systems *have* setitimer.
   (What this *really* means is that if you need a sleep that works
   in milliseconds or microseconds or whatever, using a routine
   like msleep is imperative.  Then the victim, er, user, of your
   code need rewrite only msleep.)

1) you can (and, I think, should) avoid the critical section by using
   sigblock and sigsetmask (see man 2 sigblock).

2) using setitimer is more expensive than calling select with three
   `(fd_set *)0' arguments.  Select does have one problem: like
   sigpause, it is interrupted by signals; unlike sigpause, it does
   not take a signal mask, so it sometimes has unavoidable critical
   sections.  (Yes, I want a sixth argument to select!)

3) The actual granularity of interval timers (including select timers):

	system				granularity
	4.[23]BSD Vax			10 ms
	Sun[23] running SunOS 3.x	20 ms
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (11/17/87)

In article <9310@mimsy.UUCP>, chris@mimsy.UUCP (Chris Torek) writes:
> 
> 0) Only certain 4BSD-based operating systems *have* setitimer.
>    (What this *really* means is that if you need a sleep that works
>    in milliseconds or microseconds or whatever, using a routine
>    like msleep is imperative.  Then the victim, er, user, of your
>    code need rewrite only msleep.)
> 
> 1) you can (and, I think, should) avoid the critical section by using
>    sigblock and sigsetmask (see man 2 sigblock).
> 
> 2) using setitimer is more expensive than calling select with three
>    `(fd_set *)0' arguments.  Select does have one problem: like
>    sigpause, it is interrupted by signals; unlike sigpause, it does
>    not take a signal mask, so it sometimes has unavoidable critical
>    sections.  (Yes, I want a sixth argument to select!)
> 
> 3) The actual granularity of interval timers (including select timers):
> 
> 	system				granularity
> 	4.[23]BSD Vax			10 ms
> 	Sun[23] running SunOS 3.x	20 ms
> -- 
> In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
> Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

   Sorry about #0, I didn't know SYSV doesn't provide itimers.


   #1 Sorry, I think critical section was a misnomer for what I meant.  The
problem that I was describing had to do with setting the itimer up
such that it interrupts only once (the second half of the structure set
to zeros).    If the ONLY timer interrupt was to come during the period
between the call to setitimer and  sigpause, my interrupt handle
would be called and then NOTHING would happen in sigpause to
make sigpause exit.  The only timer interrupt sounded would not have
caused sigpause to exit, since it sounded BEFORE entering into sigpause.
This is the rational behind using continuous interrupts (the second half
of the structure not set to zeros) when at first glance, someone might
think you could get away with just one.

BTW, yes I'm very familiar with the sigblock and sigsetmask functions,
they are not needed to implement this facility, setitimer suffices.


   Point two about setitimer being slower.  Does that really make
sense to complain about how long setitimer takes as compared to 
select!!!  If the interrupt timer's granularity is 10 millisecs, 
both select and msleep will usually time out at the same time (setitimer
has to be in the order of 100 micros per call).  Statistically though,
msleep should have a mean wait time that is a little longer than select.

  Also, don't you consider the use of select as a timer to be an
abuse of the function?  Then again, are you one that argues,
vfork is avid way to get shared mem in BSD. ;-)
-- 
That one looks Jewish -- And that one's a coon -- Who let all this riff raff
into the room -- There's one smoking a joint -- and Anoter with spots -- If
I had my way -- I'd have all of you shot ... Pink Floyd, "In the Flesh"
Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

davel@hpcupt1.HP.COM (Dave Lennert) (11/17/87)

> 2) using setitimer is more expensive than calling select with three
>    `(fd_set *)0' arguments.  Select does have one problem: like
>    sigpause, it is interrupted by signals; unlike sigpause, it does
>    not take a signal mask, so it sometimes has unavoidable critical
>    sections.  (Yes, I want a sixth argument to select!)
> -- 
> In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
> Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris
> ----------

In reality one would like every system call to accept a signal mask
(like what sigpause() does for pause()).  However, it is not impossible
to code reliably; setjmp()...longjmp() work acceptably.

You can't code reliably in the presense of signals (especially SIGALRM)
without using setjmp()...longjmp() (with the one exception of sigpause()).
If you timeout a system call other than sigpause() by arming SIGALRM and
then immediately invoking the system call (without setjmp()) you have a
race condition.

-Dave Lennert   HP   ihnp4!hplabs!hpda!davel

chris@mimsy.UUCP (Chris Torek) (11/18/87)

In article <6060001@hpcupt1.HP.COM> davel@hpcupt1.HP.COM (Dave Lennert) writes:
>In reality one would like every system call to accept a signal mask
>(like what sigpause() does for pause()).  However, it is not impossible
>to code reliably; setjmp()...longjmp() work acceptably.

Not so.  There are two problems here; I shall describe the harder
one first.

In the presence of longjmp, C needs an unwind-protect mechanism.
Example:  The 4.3BSD fprintf routine temporarily buffers unbuffered
streams.  This is often a major performance win, since stderr is
normally unbuffered.  The problem is illustrated by the code (names
changed to protect the innocent):

	fprintf(FILE *f, <other details left out>)
	{
		unsigned char buf[BUFSIZ];
		int ret;
		int we_buffered_it = 0;

		if ((f->flags & BUFFERED) == 0) {
			f->buf = buf;
			f->bufsize = BUFSIZ;
			f->flags |= BUFFERED;
			we_buffered_it = 1;
		}
		ret = _doprnt(...);
		if (we_buffered_it) {
			f->buf = NULL;
			f->flags &= ~BUFFERED;
		}
		return (ret);
	}

Aside from deferring all signals for the duration of _doprnt() (a
bad idea!), there is no way to prevent a longjmp() that leaps out of
_doprnt() and fprintf() from trashing FILE *f.  With unwind-protect
you might do this:

		unsigned char buf[BUFSIZ];
		int ret, omask, we_buffered_it = 0;

		if ((f->flags & BUFFERED) == 0) {
			omask = sigblock(ALLSIGS);
			if (unwind_protect()) {
				f->buf = NULL;
				f->flags &= ~BUFFERED;
				return;	/* continue longjmp-ing */
			}
			f->buf = buf;
			f->bufsize = BUFSIZ;
			f->flags |= BUFFERED;
			we_buffered_it = 1;
			(void) sigsetmask(omask);
		}
		ret = _doprnt(...);
		if (we_buffered_it) {
			omask = sigblock(ALLSIGS);
			f->buf = NULL;
			f->flags &= ~BUFFERED;
			(void) sigsetmask(omask);
		}
		return (ret);

The idea is that longjmp() would, as it unwinds stack frames,
notice markers left by unwind-protect calls and `return' om
there to let the function clean up after itself.  Returning
from the protection code would resume the longjmp.

Back to the easier problem:

>In reality one would like every system call to accept a signal mask
>(like what sigpause() does for pause()).

This also is not true.  Any system call that is restarted after a
signal does not need a signal mask, simply because having one would
make no difference.  In this case you *must* use longjmp to abort
the system call (which leaves you with the unwind-protect problem
above).

Adding unwind-protect would fix both problems, actually.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

davel@hpcupt1.UUCP (12/05/87)

> / chris@mimsy.UUCP (Chris Torek) / 12:38 pm  Nov 18, 1987 /
> 
> In the presence of longjmp, C needs an unwind-protect mechanism.

This is an intriguing idea.  I like it.  But it does require that all functions
used (e.g., from a library) are correctly coded to use the unwind-protect
mechanism as needed.  I guess that goes without saying (so I said it ;-) ).


My posting was meant to assume that standard unix signal handling
precautions were being followed.  It is known (and even documented in
Posix !)  that it is unsafe in the general case to do anything in a
signal handler but the most simple things.  (For example, set a variable
or longjmp to a state which itself does trivial cleanup and exit.)
(Worrying about the general case is very important for a library routine
which establishes signal handlers, e.g., sleep().)

However, if the application limits what is going on during the interval
of time when the signal handler can be entered (e.g., make sure you
aren't in stdio) then you have more freedom in the signal handler.

Specifically, I was discussing the case where SIGALRM was used to
timeout a system call.  The longjmp() in this case merely returns to
the same routine that invoked the system call; no stack frames are
unwound at all.

Thank you for pointing out the pitfalls in the general case and for
describing the unwind-protect mechanism.

-Dave Lennert   HP   ihnp4!hplabs!hpda!davel