From: jrb@rigel.cs.pdx.edu (James Binkley)
Newsgroups: comp.unix.bsd
Subject: bug in setitimer?
Date: 3 Jan 93 21:23:26 GMT
Article-I.D.: pdxgate.6648

I'm trying to use setitimer with the REAL TIMER (ITIMER_REAL) to 
get so many interrupts per second on 386BSD; .e.g,
	setitimer(ITIMER_REAL, &ival, NULL);
So far I can't make it work correctly in terms of time.  You get
ALARM signals but you seemingly can't get better
than 10 intr/sec. Anybody else have any ideas? 
I'll start digging into the kernel but wondered
if anybody else might have encountered this or might
have a logical explanation?

There is sample code at the end and the output
results from 3 runs below. The first on a Sun,
the 2nd two on a 386BSD system. The total elapsed
time should roughly be 5 seconds as it is on the
Sun. The input to clk is in "bpm" or beats per minute 
and is translated into hertz and usecs for the setitimer
call.  (I'm porting some pc music code to 386bsd and at 
least I have a mpu driver working.)


				Jim Binkley
				jrb@cs.pdx.edu

----------------------------------------------------------
sun run:

bash$ /bin/time clk 48
hertz 19 bpm 48
setclock: START: hertz bpm usecs 19 48 52631
closeClock: interrupts == 95
count 95
        5.3 real         4.7 user         0.1 sys  

----------------------------------------------------------
386bsd run:

bash% /usr/bin/time clk 48 
hertz 19 bpm 48 
setclock: START: hertz bpm usecs 19 48 52631   
closeClock: interrupts == 97 
count 95 
        9.27 real         8.80 user         0.31 sys 

interrupts/time == 10hz roughly
----------------------------------------------------------
bash% /usr/bin/time clk 100 
hertz 40 bpm 100 
setclock: START: hertz bpm usecs 40 100 25000 
closeClock: interrupts == 201 
count 200 
       17.92 real        17.66 user         0.14 sys

interrupts/time == still 10hz roughly

-----------------------------------slice here-----------------
/*
 * clk.c
 *
 * To compile:
 *	cc -o clk clk.c
 * To run:
 *	/usr/bin/time (or /bin/time) clk 48
 *	/usr/bin/time (or /bin/time) clk 255
 *
 * Should take roughly 5 seconds in both cases approximately.
 *
 * Uses setitimer(2), ITIMER_REAL for interrupt clock.
 */
#ifdef unix
#include <stdio.h>
#include <sys/signal.h>
#include <sys/time.h>
#endif

#define DEBUG
#define TESTMAIN

#define QEVENTS		24		/* no of events per beat */
#define LOWBPM		48              /* lowest acceptable metronome */
#define HIBPM	       255              /* highest acceptable metronome */
#define TRUE		 1
#define FALSE		 0

/* bpm is a module input that is translated into hertz
*/
static unsigned int hertz;              /* current hertz */
static int bpm;				/* current beats per minute */

/* scheduling flags. Flags shared between interrupt routine
 * and scheduler.
 */
int inevents; 	      /* true if sched executing event code */
int timewow;          /* true if clock interrupt during event code */
int pendclk;          /* True if no clk interrupt yet */

/* error string printed if attempt occurs to set hbeat clk out of range
*/
static char *bpmsyntax = "error: bpm range 48..255";

static int clockon=0;
unsigned long interrupts;

openClock()
{
	/* set new clock rate according to bpm. setclock
	 * fiddles with hw.
	 */
	setclock(FALSE);

	clockon = 1;
	interrupts = 0L;
}

closeClock()
{
	if (clockon == 0)
		return;
	setclock(TRUE);         /* restore pc time */
	clockon = 0;
#ifdef DEBUG
	printf("closeClock: interrupts == %ld\n", interrupts);
#endif
}


/* check bpm semantics. must be between LOWBPM..HIBPM
 * inclusive.
 */
chkbpm(newbpm)
{
	if ( newbpm < LOWBPM ) {
		setError(bpmsyntax);
		return(0);
	}
	if ( newbpm > HIBPM ) {
		setError(bpmsyntax);
		return(0);
	}
	return(1);
}

setbpm(newbpm)
{
	unsigned int timebase;
	if (chkbpm(newbpm) == 0)
		return;
	bpm = newbpm;
	hertz = (unsigned int) ((bpm * QEVENTS) / 60);
}

/* outside code can interrogate module to find out what bpm is
*/
getbpm()
{
	return(bpm);
}

/* setclock
 *
 * modifies runtime clock. Only routine in this module that
 * actually touches hardware.
 */

#ifdef unix
struct itimerval ival;
struct itimerval oval;
#endif

static
setclock(stopflag)
{
	extern unsigned int hertz;
	unsigned int count;
	unsigned int low;
	unsigned int hi;
#ifdef unix
	unsigned long t;

	if (stopflag == TRUE) {
		unixStopClock();
	}
	else {
		unixStopClock();
		if ( hertz > 100 )
			hertz = 100;
		if ( hertz == 0 )
			hertz = 1;
		
		t = 1000000L / hertz;	
		ival.it_interval.tv_sec = 0;
		ival.it_interval.tv_usec = t;
		ival.it_value.tv_sec = 0;
		ival.it_value.tv_usec = t;
#ifdef DEBUG
		printf("setclock: START: hertz bpm usecs %d %d %ld\n", hertz, bpm, t);
#endif
		unixStartClock();
	}
	return;
#endif

}

#ifdef unix

extern void tintr();

static
unixStartClock()
{
	ival.it_interval.tv_sec = 0;
	ival.it_value.tv_sec = 0;
	signal(SIGALRM, tintr);
	setitimer(ITIMER_REAL, &ival, NULL);
}

unixStopClock()
{
	ival.it_value.tv_sec = 0;
	ival.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL, &ival, NULL);
}

void 
tintr()
{
	pendclk = FALSE;         /* interrupt has occured */
	/* clock occured during event execution; i.e., timewow
	 * error has occurred
	 */
	if ( inevents == TRUE )
		timewow = TRUE;

#ifdef DEBUG
	interrupts++;
#endif
}
#endif

#ifdef TESTMAIN

/* module clocktimer test routine
 *
 * syntax:
 *	clk [bpm]
 *	bpm can vary from 48.255 (beats per minute)
 *	Results:
 * 		should take approximately 5 seconds for bpm
 *		== 48 to 255, just more interrupts per second.
 * e.g.,  
 *	/bin/time clk 100
 */
main(argc, argv)
int argc; 
char **argv;
{
	int count=0;
	int lbpm = 120;
	int delaycount;

	if (argc == 2)
		lbpm = atoi(argv[1]);

	/* 120 bpm means 2 beats per second.
  	 * 24 "ticks" per quarter note (ppq).
	 */
	setbpm(lbpm);  /* compute hertz */
	printf("hertz %d bpm %d\n",hertz, bpm);
	delaycount = 5 * hertz;	/* 5 seconds * intrs/sec */

	openClock();
	do {
		pendclk = TRUE;
		while(pendclk)
			;
		count++;
	}
	while ( count < delaycount );
	closeClock();

	printf("count %d\n",count);
}

setError(s)
char *s;
{
	printf("%s\n",s);
}
#endif