[comp.unix.wizards] setitimer vs select

torek@elf.ee.lbl.gov (Chris Torek) (05/11/91)

[nb: include files removed in the quotes below]

>>In article <1489@cvbnetPrime.COM> mkaminsk@cvbnet.prime.com (mailhost) writes:
>>:    struct timeval      timeout;
>>:    timeout.tv_sec = 0;
>>:    timeout.tv_usec = number_of_microseconds;
>>:    select(1, NULL, NULL, NULL, &timeout);

>natha@hal.com (Nathaniel Ingersol) wrote:
>>Am I missing something here, or why can't you just make a usleep out
>>of setitimer?  You could even use ITIMER_REAL.

setitimer is considerably less efficient:

In article <69033@rtfm.Princeton.EDU> pfalstad@phoenix.princeton.edu
(Paul Falstad) writes:
>You could just do this, yes.
>
>int handler(){;} /* needed to make pause() work */
>
>usleep(unsigned usec) {
>   struct itimerval ix;
>   ix.it_value.tv_sec = 0;
>   ix.it_value.tv_usec = usec;
>   ix.it_interval.tv_sec = 0;
>   ix.it_interval.tv_usec = 0;
>   signal(SIGALRM,handler);
>   setitimer(ITIMER_REAL,&ix,NULL);
>   pause();
>   signal(SIGALRM,SIG_DFL);
>}
>
>Looks harder to me.  :-)

Actually, the usleep() above is unreliable, and you need something
more like this:

	static struct sigvec prev;
	static int caught;	/* must be volatile in newer systems */

	static int handler()	/* void in newer systems */
	{

		(void) sigvec(SIGALRM, &prev, (struct sigvec *)NULL);
		caught = 1;
	}

	int usleep(unsigned usec) {
		struct itimerval ix, oix;
		int saverr, again;
		long omask;
		struct sigvec saveprev, new;
		/* many of the previous must be volatile in newer systems */

		ix.it_value.tv_sec = usec / 1000;
		ix.it_value.tv_usec = usec % 1000;
		ix.it_interval.tv_sec = 0;
		ix.it_interval.tv_usec = 0;
		/* block interrupts so we can change state safely */
		omask = sigblock(sigmask(SIGALRM));
		/* try to set new and get old state */
		if (setitimer(ITIMER_REAL, &ix, &oix)) {
			saverr = errno;
			(void) sigsetmask(omask);
			errno = saverr;
			return (-1);
		}
		/* see whether we have to correct for old state */
		again = 0;
		correct = 0;
		if (oix.it_interval.tv_sec || oix.it_interval.tv_usec) {
			/* old itimer had a reload value, which we must
			   restore to keep the timer from drifting back.
			   if old timer had a longer interval, we should
			   use the difference between our value and
			   its interval instead, but it then becomes
			   excessively difficult to reinstate the previous
			   interval. */
			again = 1;
			ix.it_interval = oix.it_interval;
		}
		if (oix.it_value.tv_sec < ix.it_value.tv_sec ||
		    oix.it_value.tv_sec == ix.it_value.tv_sec &&
		    oix.it_value.tv_usec < ix.it_value.tv_usec) {
			/* previous alarm would fire before ours.
			   unclear how to handle this; we set ours
			   to the (smaller) old value. */
			again = 1;
			ix.it_value = oix.it_value;
		} else if (oix.it_value.tv_sec || oix.it_value.tv_usec) {
			/* there was a previous alarm, but it occurs
			   after ours.  Remember the difference between
			   ours and its.  This cause drift, but the
			   drift will vanish on the reload.  If the
			   reload is shorter than the next scheduled
			   alarm, however, we should use the next
			   scheduled alarm instead. */
			if (oix.it_value.tv_sec > oix.it_interval.tv_sec ||
			    oix.it_value.tv_sec == oix.it_interval.tv_sec &&
			    oix.it_value.tv_usec > oix.it_interval.tv_usec) {
				/* I am too tired to keep this up.
				   Someone Out There will have to finish
				   this.  Sorry. */
			}
		}
				
		(void) setitimer(ITIMER_REAL, &ix, (struct itimerval *)NULL);
		saveprev = prev;
		new.sv_handler = handler;
		new.sv_mask = 0;
		new.sv_flags = 0;
		/* cross fingers -- we are too far to bother backing out */
		(void) sigvec(SIGALRM, &new, &prev);
		/* okay, everything is set */
		do {
			/* atomically enable and wait for signal */
			sigpause(omask);
			/* keep doing it until we got the right one */
		} while (!caught);
		/* now that we are done sleeping, we must restore old state
		   (the alarm has fired exactly once, although the reload
		   interval may have loaded a new shorter time) */
		prev = saveprev;
		caught = 0;
		if (correct) {
			/* if we need to change the interval back, here is
			   where to do it. (see `tired' above) */
		}

		/* Finally, all state is back to where it was before we
		   started.  Release the block on SIGALRM, and return 0. */
		sigsetmask(omask);
		return (0);
	}

select() is MUCH simpler; the signal method requires, at a minimum
(you can drop much of the state saving for the usual case, where
signal handlers are not multiply nested, running on several stacks,
making heavy use of blocking masks, etc.), five separate system calls:

	block signal
	set handler
	set timer
	sigpause
	unblock signal

The select() technique makes only one system call.
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov