* Compiler
* Linker
* Loader
* Boot
module or boot ROM
1. What is the effect
of
having such a large number of registers on the operating system?
2. What additional
hardware
features you would recommend added to the design above.
3. What happens if
the hardware
designer also wants to add a 16-station pipeline into the CPU. How
would
that affect the context
switching overhead?
if(child == 0)
{
The piece of code shown creates two processes. Therefore, we have a total of three processes, the parent, the first and second child. Each of these has its own private copy of the variable c. For the parent, the variable c be 20 before the end of the program. For the first child (the one created in the first program statement), the variable c will contain the value 10 before the end of the program. For the second child (the one created in the else clause), the variable c will contain the value 15 before the end of the program.
n > 0: T(n) = 2 T(n-1) + 1
n = 0: T(0) = 0
where T(n) is the number of processes created by the function. To see why this is the case, consider what happens when the function is called. The first statement calls the system call fork() which creates a child in addition to the caller. Both the caller and the child then execute a recursive call to forkthem() with an argument set to n-1. Therefore, a call to forkthem() creates one process of its own, and then is responsible for all the children that will get created by the function with n-1. The solution to the recurrence equation is 2^n - 1.
Program 1:
main()
{
val = 5;
if(fork())
wait(&val);
val++;
printf("%d\n", val);
return val;
}
Program 2:
main()
{
val = 5;
if(fork())
wait(&val);
else
exit(val);
val++;
printf("%d\n", val);
return val;
}
In the first program, the parent process creates a child and then waits for the child to exit (through the system call "wait"). The child executes and prints out the value of val, which is "6" after the v++ statement. The child then returns the value of val to the parent, which receives it in the argument to "wait" (& val). The parent then prints out the value of val, which is now 7. Note that the parent and child have seperate copies of the variable "val".
Using similar reasoning, you can see that the parent in program 2 waits for the child to return, and the child exits immediately. In this case only one value gets printed out which is the number 6 (from the parent process).
A typical hardware architecture provides an instruction called return from interrupt, and abbreviated by something like iret. This instruction switches the mode of operation from supervisor mode to user mode. This instruction is usually only available while the machine is running in supervisor mode.
1. Explain where in the operating system this
instruction
would be used.
2. What happens if an application program
executes
this instruction?
Hint: You should use system calls such as gethrtime() or gettimeofday() for time measurements. Design your code such that the measurement overhead is negligible. Also, be aware that timer values in some systems have limited resolution (e.g., millisecond resolution).
A system call is expected to be significantly more expensive than a procedure call (provided that both perform very little actual computation). A system call involves the following actions, which do not occur during a simple procedure call, and thus entails a high overhead:
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
int foo(){
return(10);
}
long nanosec(struct timeval t){ /* Calculate nanoseconds in a
timeval
structure */
return((t.tv_sec*1000000+t.tv_usec)*1000);
}
main(){
int i,j,res;
long N_iterations=1000000; /* A million iterations
*/
float avgTimeSysCall, avgTimeFuncCall;
struct timeval t1, t2;
/* Find average time for System call */
res=gettimeofday(&t1,NULL); assert(res==0);
for (i=0;i<N_iterations; i++){
j=getpid();
}
res=gettimeofday(&t2,NULL);
assert(res==0);
avgTimeSysCall = (nanosec(t2) -
nanosec(t1))/(N_iterations*1.0);
/* Find average time for Function call */
res=gettimeofday(&t1,NULL);
assert(res==0);
for (i=0;i<N_iterations; i++){
j=foo();
}
res=gettimeofday(&t2,NULL);
assert(res==0);
avgTimeFuncCall = (nanosec(t2) -
nanosec(t1))/(N_iterations*1.0);
printf("Average time for System call getpid :
%f\n",avgTimeSysCall);
printf("Average time for Function call :
%f\n",avgTimeFuncCall);
}
Sample output on a linux machine :
> gcc -O0 testtime.c -o testtime
> ./testtime
Average time for System call getpid : 394.778015
Average time for Function call : 15.080000
Most compilers set the stack pointer to be at the beginning of the stack
area when a function is called. Thus, automatic
variables are found underneath the location to which the stack
pointer
is pointing to (except for the HP PA-RISC, in which
the stack grows upward). Thus,