Remark

As pointed out in the recitation session, the example question don't reflect the exam structure. (We didn't just change some numbers). The example exam and the actual exam are covering material from Ayal's class. However, the tests do not necessarily cover the same subset of topics.

Ayal is going to have some more examples and solutions in a couple of minutes.

Problem 1

Consider the following algorithm to compute a product in a distributed system:
int product = 1;
...
pchild = fork();
if (pchild == 0) {
  /* childprocess code */
  product *= computeFirstNumber();
} else {
  /* parent code */
  product *= computeSecondNumber();
}
...
Where the two functions
  int computeFirstNumber(),
  int computeSecondNumber()
do some longrunning computation (e.g. compute primes with 100 digits).

Where's the obvious problem in this code? What OS techniques from the code do you need to fix it?

Solution

The code above attempts to pass information between child and parent process using a variable. There are two principal reasons why this is not working: Fork creates a child process which is an exact copy of the parent process. Variables aren't shared between the processes. Modifying the child's variable doens't change the value in the parent.

Even if variables wouldn't be shared, this piece of code isn't working since there is no synchronization at all. In this situation both processes could modify product at the same time...

I've implemented a solution with synchronization in the programming language Ada. Ada is quite convenient because there is built in support for all these things.

Problem 2

Assume that in our shell assignment, the programmer wanted to print the shell prompt after a background process is finished. A solution would be

pchild = fork();
if (pchild == 0) {
  /* child process */
  ...
  /* execute program */
  execv(path,A);

  /* print prompt */
  printf("myShell:>>");
}

This solution doesn't work. What's the reason for this? Give a brief explanation of the semantics of the exec command. Discribe a possible solution on a high level - WITHOUT coding it. (You don't know the necessary C commands!).

Solution

Each of the functions in the exec family overlays a new process image on an old process. This means that the child process is replaced by the process for the program A. The printf instruction will never be executed.

You could fix this by sending a signal to the parent process once the child is terminated... We didn't do this in class.

Problem 3

Problem 3a

Define the following terms

Problem 3b

Consider n processes p1, ... pn with burst times t1, t2, ..., tn. What is the schedule sequence with maximum waiting time? Assume that you only know the sum of the burst times
T = t1 + t2 + ... + tn. 
Can you give an upper bound on the waiting time of a process for any process? What about the average waiting time?

Problem 3c

Consider the following processes p1, ... pn all of which take the same burst time t. (They arrive in order p1, ... pn). What's the FCFS average waiting time? (need some computation) What's the FCFS average turnaround time? (almost obvious)

Problem 3d

True or false (give a one sentence explanation):

Solution 3a

Compare Silberschatz, Galvin, p. 128

Solution 3b

Schedule the jobs in decreasing burst time order. (Longest jobs first,...) This is maximizing the average waiting time.

Given on the sum of the burst times T, we only know that a process won't wait longer than T. Clearly, the average wait time can't be more. I will describe a situation with a high average waiting time. Consider the following silly scheduling algorithm. In a first phase we'll dispatch each process and preempt it just before the very last instruction. In a second phase, we'll finish each process.

Under this scheduling policy, all processes are finishing about the same time. Therefore, the waiting time for each process i is about T - burst_time_i. So the average of the waiting time is T - average(burst_times). (Which is close to T if you've short jobs.)

Solution 3c

Average Waiting time:

T = 0*t + 1*t + 2*t + ... + (n-1) * t / n =
  = (0 + ... + n-1) * t / n = 
  = n * (n-1)/2 * t / n
  = (n-1)/2 * t

Average Turnaround time:

T = 1*t + 2*t + 3*t + ... + n * t / n =
  = (1 + ... + n) * t / n = 
  = (n+1) * n/2 * t / n
  = (n+1)/2 * t
Solution 3d
Solution 5.5

If you've two pointers p1 and p2 to process P in your ready queue, this means that this process gets twice as many ``time quantums'' as the other processes.

One possible advantage is that you can express priorities for processes. If a process has high priority, you want it to get a lot of time. In order to archieve this goal, you could put many pointers to this process in the ready queue.

But usually, in order to implement priorities, one should work on the data structure, rather than duplicating pointers. This is a non-clean design..

A potential disadvantage of this priority scheme is that you've unnecessary switches between processes. Assume process P is very important and you put three pointers to it in the ready queue:

C notation: p1 = p2 = p3 = &P.
Queue Contents: (p1,p2,p3,...)

Round Robin would dispatch p1, p2 and p3 for a time quantum. This means there will be process switches between p1 and p2 - and between p2 and p3. These switches are unneccessary since p1, p2 and p3 point all to the same process. However, the standard Round Robin algorithm can't know about that!

However, in a real operating system, os checks which parts are common between processes. Therefore, it will not be very expensive. In real operating systems, it may happen that processes share code, and other memory components. When context switching,the os will not invalidate many of the translation information and other datastructires knowing that they are shared. So... efficiency is a matter of implementation, and won't necessary suffer in this case, but it's a bad design. (Thanks to Ayal for this footnote!)