Problem 2-A kernel timer

In this assignment, you will use the ITIMER_REAL, ITIMER_VIRTUAL, and ITIMER_PROF interval timers to measure the processor usage of a process. You will prepare a report which provides real-time (i.e. wall-clock time) of execution using the ITIMER_REAL timer, processor time (the time that a process is actually running in both user and kernel space), user space time, and kernel space time. You can check the accuracy of your wall clock time by calling gettimeofday( ). Design your solution to have millisecond precision, although not all clocks do have millisecond precision. Use signals to keep track of the number of seconds for the 3 categories of time by raising signals once per second. Measure the times of a parent and two child processes, each executing the Fibonacci program for N=20,30 and 36.

Background

UNIX keeps track of the time elapsed (seconds and microseconds) since the beginning of the UNIX "time epoch", i.e. since January 1, 1970, making use of two long integer variables.

The data structure which the UNIX kernel uses for holding time values is

struct timeval {

long tv_sec;
long tvusec;
}

UNIX updates the timeval structure by making use of programmable timer devices, which are used to produce interrupts at precise time intervals (e.g. 100 times per second in Linux). When the OS is initialized, it sets the time variable to zero and increments it each time an interrupt occurs. If the interrupt occurs every 10 ms. the timer device handler adds 10 ms. to the OS time variable tv_usec filed, and carries the overflow into the tv_sec field whenever there is an overflow.

The kernel accumulates time and is responsible for managing timers for each process. For example, in order to implement a scheduling strategy, each process has to keep a record of the amount of CPU time it has used since it last accessed the CPU. The kernel keeps track of three different time intervals for each process:
ITIMER_REAL: Passage of real time, implemented using the it_real_value and it_real_incr fields.
ITIMER_VIRTUAL: Passage of virtual time, i.e. this timer is incremented only when the process is executing in user mode.
ITIMER_PROF: Passage of time during which the process is active (virtual time) plus the time that the kernel is doing work on behalf of teh corresponding process (e.g. executing a system call).

Each of these timers is a "countdown timer"-it is first initialized with some value and then counts down to zero. Upon reaching zero, it raises a signal to another part of the system (either the OS or a user program). It then resets the period and starts counting down again.

Each timer is initialized via the setitmer( ) system call:

#include
.....
setitimer{
int timerType,
const struct timerval *value,
struct itimerval *oldValue
};

The struct itimerval includes the fields
stuct itimerval {
struct timeval it_interval;
struct timeval it_value:
};

More details can be found on the man page for setitimer( ). The timerType parameter is set to ITIMER_REAL, ITIMER_VIRTUAL, OR ITIMER_PROF\ (constants define in the sys/time.h include file) . The value parmeter is used to initialize the second and microsecond fields of the timer, the it_value difeines the current value for the timer, and the it_interval field defines the value used to rest the timer when it reaches zero.
A timer's value can be read with the getitimer( ) system call:

#include
...
getitimer{
int timerType,
const struct timerval *value,
};

The value parameter is used to return the value of the kernel clock.

The following code sets ITIMER_REAL and then reads it.

#include
...
sturct itimerval v;
\ ....
v.it_interval.tv_sec=9;
v.it_interval.tv_userc=99999;
v.it_value.tv_sec=9;
v.it_value.tv_usec=99999;
setitimer(ITIMER_REAL, &v, NULL);
...
getitimer(ITIMER_REAL, &v);
printf("...%1d seconds, %1d microseconds.....",...,
v.it_value.tvsec,
v.it_value.tv_usec,
...);
....

When ITIMER_REAL reaches zero, it will be reset to (9, 99999) once again.

SKELETON

All forms of UNIX make use of signals, which are mechanisms which are used by a process to notify another process that an event has occurred. Some examples are the user pressing the delete key, the run time system detecting an attempt to divide by zero, or one of the itimers reaching zero. Upon receiving a signal, the process executes code for that particular signal. Note that signals can be used between application processes.

There are 31 different types of signals built into contemporary UNIX systems. The system include file, signal.h defines symbolic names for the signal types. For example, SIGINT is raised by the terminal driver when the user presses the interrupt character (delete or cntl-C). Application programmers are not allowed to create their own signals; however SIGUSR 1 AND SIGUSR 2 are included in most versions of UNIX for application to application signaling.

A signal is raised by calling the kill ( ) function and identifying the process to receive the signal and the signal type. The receiving process can cause the signal to be handled in the default manner, to be ignored, or to be processed by user-defined code. For example, to ignore the SIGALARM signal, the process must execute the signal (SIGALRM, SIG_IGN) system call. Default handling is obtained via signal(SIGALRM, SIG_DFL). By supplying a function s the second argument in signal, the application can use its own code to process the signal. The function takes an integer argument and returns void.

In the following code we provide an outline of how how the mechanism operates. The code is not intended to do anything real; it is merely an illustration of how signals are raised and caught.

#include
static void sig_handler(int);
int main void\{
int i, parent_pid, child_pid, status;
/*Prepare the sig_handler routine to catch SIGUSR1_ AND SIGUSR2*/
if (signal(SIGUSR1, sig_handler)= =SIG_ERR)
printf("Parent: unable to create sig_handler for SIGUSR1\\n");
if (signal(SIGUSR2, sig_handler)= =SIG_ERR)
printf("Parent: unable to create sig_handler for SIGUSR2\\n");
parent_pid=getpid( );
if ((child_pid=fork( )= =0)) {
kill(parent_pid, SIGUSR1);/*raise SIGUSR1*/
for (;;) pause( ); /*child waits for signal*/
} else\{
kill(child_pid, SIGUSR2);/*parent raises SIGUSR2*/
printf("Parent: terminating child with prejudice");
kill(child_pid.SIGTERM); /*Parent raises SIGTERM*/
wait(&status); /*Parent waits for kid to terminate*/
printf("done\\n");
}
}

static void sig_handler(int signo){
switch (signo) {
case SIGUSR1; /*incoming SIGUSR1 signal*/
printf("Parent: received SIGUSR1\\n");
break;
case SIGUSR2; /*incoming SIGUSR1 signal*/
printf("Parent: received SIGUSR2\\n");
break;
}
return;
}

Solution Outline

This is one possible outline for a solution. Feel free to use your own approach.

#include
#include
#include
#include
long unsigned in fibonacci(unsigned int n);

//The following variables are used to record the accumulated times. They are set by //the signal handlers and are read by the processes when they report the results.
static long p_realt_secs = 0, c1_realt_secs=0, c2_realtsecs =0;
static long p_virtt_secs = 0, c1_virtt_secs=0, c2_virtt_secs =0;
static long p_proft_secs = 0, c1_proft_secs=0, c2_proft_secs =0;
static struct itimerval p_realt, c1_realt, c2realt;
static struct itimerval p_virtt, c1_virtt, c2_virtt;
static struct itimerval p_proft, c1_proft, c2_proft;

main( int argc, char **argv) {
long unsigned fib = 0;
int pid1, pid2;
unsigned int fibarg;
int status;

//get command line argument, fibarg (the value of N in the problem statement
...
//Initialize parent, child1 and child 2 timere values
p_realt.it_interval.tv_sec = ...;
p_realt.it_interval.tv_usec = ...;
p_realt.it_value.tv_sec = ...;
p_realt.it_value.tv_usec = ...;
...
//Enable parent's signal handlers
signal(SIGALARM,....);
signal(SIGVTALARM,...);
signal(SIGPROF,...);

//Set parent's itimers
if(setitimer(ITIMER_REAL,...)== -1
perror("parent real timer set error");
if(setitimer(ITIMER_VIRTUAL,...)== -1
perror("parent virtual timer set error");
if(setitimer(ITIMER_PROF,...)== -1
perror("parent profile timer set error");
pid1=fork( );
if(pid1 == 0)\{
//Enable child 1 signal handlers (disable parent handlers)
//Enable child 1 itimers
//Set child 1 itimers
//Start child 1 on the fibonacci program
fib=fibonacci(fibarg);
//Read child 1 itimer values and report them
getiimter(ITIMER_PROF,...);
getiimter(ITIMER_REAL,...);
getiimter(ITIMER_VIRTUAL,...);
printf("\\n");
printf("Child 1 fib = %1d, real time = %1 d sec, %1 d usec, %1d msec\\n",
fib, c1_realt_secs,
elapsed_usecs(c1_realt.it_value.tv_sec,
c1_realt.it_value.tv_usec / 1000);

printf("Child 1 fib = %1d, cpu time = %1 d sec, %1 d usec, %1d msec\\n",
fib, c1_realt_secs,
elapsed_usecs(c1_proft.it_value.tv_sec,
c1_proft.it_value.tv_usec / 1000);

printf("Child 1 fib = %1d, user time = %1 d sec, %1 d usec, %1d msec\\n",
fib, c1_virtt_secs,
elapsed_usecs(c1_virtt.it_value.tv_sec,
c1_virtt.it_value.tv_usec / 1000);

printf("Child 1 fib = %1d, kernel time = %1 d sec, %1 d usec, %1d msec\\n",
fib, c1_delta_time(c1_proft, c_virtt),
elapsed_usecs(c1_proft.it_value.tv_sec,
c1_proft.it_value.tv_usec / 1000)-
apsed_usecs(c1_virtt.it_value.tv_sec,
c1_virtt.it_value.tv_usec / 1000);

fflush(stdout);
exit(0);
}
else\{
\ pid2 = fork( );
if (pid2 = = 0) {
//Enable child 1 signal handlers
...
//Set child 2 itimers
...
Start child 2 on the fibonacci program
fib= fibonacci (fibarg);
// Read child 2 itimer values and report them
....
} else{/*this is the parent*/
// Start parent on the fibonacci program
fib =fibonacci(fibarg);
// Wait for children to terminate
waitpid(0, &status, 0);
waitpid(0, &status, 0);

//Read parent itimer values and report them
.....
}

long unsigned int fibonacci(unsigned int n) {
if(n == 0)
return 0;
else if (n==1||n==2)
return 1;
else
return(finbonacci(n-1) + fibonacci(n-2)
} }