UNIVERSITY OF WISCONSIN-MADISON
Computer Sciences Department
CS 537-2
Spring 2003
A. Arpaci-Dusseau
Midterm Exam



Name: Solutions Student ID #:








Problem Points Total
1   14
2   15
3   14
4   14
5   14
6   14
7   15
Total   100


This exam contains seven in-depth questions, each with multiple parts. Please be sure to pace yourself. Remember you have two hours, so you should spend about 15 minutes on each question. You do not need to write your answers in complete sentences, but please write clearly and as neatly as possible!

Good luck!

Problem 1: Critical Section Implementation

Protecting a critical section correctly means that one must guarantee mutual exclusion, progress, and bounded waiting. In this question, we will investigate the implications of protecting a critical section by disabling interrupts.

A) What are the advantages of disabling interrupts to implement a critical section (as opposed to other approaches)?

Worth 3 points:

- Very fast, low overhead
- Works for any number of processes
- Does not require any special hardware instructions (e.g., TestAndSet)
- Does not induce any busy-waiting
- Relatively simple to implement
- Does not require any shared memory between cooperative processes

B) What are the disadvantages of disabling interrupts to implement a critical section?

Worth 6 points:

- Prevents all other processes from running, even those not involved
  with critical section
- Malicious or buggy processes can hold on to CPU for arbitrary amount
  of time
- Cannot guarantee correctness if process performs a system call, any
  I/O, or a page fault
- Does not work on a multiprocessor
- Must track nested critical sections carefully

C) Under what circumstances is it most appropriate to disable interrupts? Give a concrete example.

Worth 5 points: (Note: Very few people got what I was looking for
here)

The critical sections that are most appropriate for guarding with
interrupts are those that are:

- short pieces of code 
- running within the OS
- for performance improvements and not correctness (so okay if run on a
  multiprocessor) 

The example we discussed in lecture was for the critical section code
within the OS to atomically add/remove one process to/from a list of
processes waiting for a lock or semaphore.

Problem 2: Dining Philosophers with Condition Variables

Below is code that solves the Dining Philosopher problem. This code is similar to what was presented in class, but uses monitors and condition variables instead of semaphores.

monitor {
    Condition mayEat[5]; 
    int state[5];        // Initialized to THINKING

    void take_chopsticks(int i) {
        state[i] = HUNGRY;
        test(i);
        if (state[i] != EATING) 
            mayEat[i].wait();
    }
    void put_chopsticks(int i) {
        state[i] = THINKING;
        test(i+1%5);
        test(i+4%5);
    }
    void test(int i) {
        if (state[i]==HUNGRY && state[i+1%5]!=EATING && state[i+4%5]!=EATING) {
            state[i] = EATING;
            mayEat[i].signal();
        }	
    }
}

A) Throughout this example, we will consider the case where this code is run on a system with Mesa semantics. What is the difference between Mesa and Hoare semantics?


Worth 3 points: 

- Hoare: The signaling process hands off the CPU and the monitor lock
  immediately and directly to the waiting process

- Mesa:  The waiting process is awoken from the CV, but must compete
  with all other processes for the monitor lock

B) Often when Mesa semantics are used, after being woken by a corresponding signal, the process must recheck the condition it is waiting on. In this example, this corresponds to changing:
if (state[i] != EATING) mayEat[i].wait();
to
while (state[i] != EATING) mayEat[i].wait();
Why isn't a while() loop needed in this code?


Worth 2 points: The main issue that must be addressed is "why is it
guaranteed that state[i] can't have changed, even with Mesa semantics,
given that another process could have been scheduled after this
process was signaled?"

The key is that each process is waiting on its own mayEat[i] CV; no
other process will be woken and no other process will change state[i]
from EATING.

C) Would this code work if we changed if (state[i] != EATING) to while (state[i] != EATING)? Why or why not?

Worth 2 points: 

Yes, this will still work.  The waiting process will simply recheck
the condition when it wakes and see that state[i] == EATING, and
therefore fall through the loop.

D) Where are all of the places in the code where the monitor lock is acquired? Where is the monitor lock released? (You may show this on the code listing, as long as you do this clearly).

Worth 3 points:

A monitor lock is acquired whenever the process calls an
externally-visible procedure within the monitor class; the monitor
lock is released when it exits the procedure.

The monitor lock is also released when a process calls wait(); it is
reacquired when the process is woken.

E) How should the elements of mayEat be initialized?

Worth 2 points:

Condition variables have no state associated with them; therefore,
they cannot be initialized.

F) Does the code work correctly if the statement mayEat[i].signal() is moved before state[i]=EATING in test()? Briefly describe why or why not.

Worth 3 points:

Yes, the code will still work.  These two statements occur when the
monitor lock is held.  Therefore, their ordering appears atomic to all
other processes; another processes will see the effects of both
statements when it later acquires the monitor lock.

Problem 3: You are the compiler and the OS

Assume the following C code is compiled to create the executable program, pointless, which is then run on a UNIX system.

#include 

int main(int argc, char *argv[]) {
  int a = 0;
  int b = 0;

  a++;
  if (fork() == 0) {
    b +=2;
    printf("If!\n");
    printf("a: %d\n", a);
    printf("b: %d\n", b);

  } else {
    b++;
    printf("Else!\n");
    printf("a: %d\n", a);
    printf("b: %d\n", b);
  }

  a++;
  if (fork() == 0) {
    b++;
    printf("If #2!\n");
    printf("a: %d\n", a);
    printf("b: %d\n", b);

  } else {
    b++;
    printf("Else #2!\n");
    printf("a: %d\n", a);
    printf("b: %d\n", b);
  }

}

A)How many processes will be part of the pointless program?

Worth 4 points:

4 processes (the original parent; one child, A,  from the first fork; one
child, B,  from the second fork; a "grandchild" from child A
performing the second fork).

B)Assume a preemptive scheduling policy in which a newly created process is always given a higher, static priority than any of the existing processes; also assume that processes never voluntarily relinquish the CPU until they exit. What output will the user see on the terminal when they run pointless? You may show this output above, next to the code listing.


Worth 10 points: Approximately 5 points for getting the scheduling
correct and 5 points for getting the data correct.

If! (child A -- highest priority of child and parent)
a 1
b 2
If #2 (child of child A)
a 2
b 3
Else #2 (back to child A)
a 2
b 3
Else (parent -- only active process)
a 1
b 1
If #2 (child B)
a 2
b 2
Else #2 (parent)
a 2
b 2

Problem 4: Scheduling Comparison

In this problem, you will compare the basic CPU scheduling algorithms. Consider the case where you know that the following jobs will arrive at the specified time and use the specified amount of CPU:

Job Arrival Time CPU Time
A 0 6
B 2 5
C 3 2

For each of the following scheduling algorithms, draw the Gantt chart and show the waiting time for each job (A, B, and C).

Grading: Most people did well on this question.  In general, half
credit for the Gantt chart and half for the correct waiting times.
The most common error was forgetting to subtract off the arrival times
for jobs B and C.

A) FCFS:

Grading: 3 points

AAAAAABBBBBCC

A: 0
B: 4
C: 8


B) SJF:

Grading: 3 points

Note that this is non-preemptive!

AAAAAACCBBBBB

A: 0
B: 6
C: 3

C) STCF (or SCTF):

Grading: 4 points

Note that this one is preemptive.  

AAACCAAABBBBB

A: 2
B: 6
C: 0

D) RR (assume time-slice of 1):

Grading: 4 points

AABCABCABABAB

A: 6
B: 6
C: 2

Problem 5: Scheduling Design

In this question, you will discuss the important aspects when designing a multi-level feedback queue scheduler.

A)What are the goals of a multi-level feedback queue scheduler?

Grading: 3 points (many different answers accepted, though one must
address the goals of the scheduler, and not just how it
is implemented)

- To approximate the behavior of a STCF scheduler, by observing the
  past behavior of a process and using that to predict its future
  behavior

- To handle a wide mix of job types and classify their behavior
  dynamically

- Improve response time of interactive jobs and throughput of
  compute-bound jobs

- To increase the utilization of the CPU and other devices

(Note that real-time and system jobs are not usually
handled by a multi-level feedback queue scheduler)

B) Which types of jobs are given high priority in such a scheduler? Why? Which jobs are given low priority? Why?

Grading: 4 points 

- High: Interactive jobs; need to be scheduled promptly to get good
response time for interactive users.  I/O bound jobs: need to be
scheduled promptly so that they can get their next request out to the
disk.  Both job classes usually relinquish the CPU promptly; that is,
each CPU burst is relatively short and this helps approximate STCF.

- Low: Compute-bound jobs; just need to be scheduled eventually and it
doesn't matter exactly when.

C) Are high or low priority jobs generally given longer time-slices? Why?

Grading: 2 points

Low priority jobs get a longer time-slice.  Since these jobs are
compute-bound, they often have significant cache state; therefore,
scheduling them for longer intervals gives them better throughput and
minimizes the overhead of context switches.

D) What concrete rules can be used to raise or lower the priority of a running process? What properties should the scheduler guarantee?

Grading: 5 points

- (1 pt) A process that consumes its entire time-slice can be identified as
compute-bound; therefore, its priority is decreased.

- (2 pts) A process that blocks frequently or does not consume its time-slice
is identified as interactive or I/O bound; therefore its priority is
increased.  However, the scheduler must contain rules to ensure that a
process can not game it, but faking interactive behavior by blocking
for a very brief period.  Therefore, the scheduler must ensure that a
process has been blocked for a "significant" amount of time.

- (2 pts) A process that has not been scheduled for a significant amount of
time is identified as starving (one of the key properties a scheduler
must guarantee); therefore, its priority is increased.

Problem 6: Memory Allocation

A number of simple allocation policies exist for managing memory in the heap. In this question, we will examine Best Fit, First Fit, and Next Fit (also called Rotating First Fit).

A) Describe the data structures that are needed to implement a basic memory allocator. Specifically, what information does the allocator need about the state of memory and how should this information be organized?


Grading: Worth 4 points 

The allocator must be able to find all of the free, or available,
space.  This freelist is traditionally maintained as a linked list
(doubly-linked is good) where each element contains its size, starting
address, and a pointer to the next element.  This list is sorted by
the address of each element.


B) What operations take place within the memory allocator when a process frees memory; specifically, how are your data structures modified?

Grading: Worth 4 points 

The newly freed block must be inserted into its proper location in the
sorted free list.  To see if this block should be coalesced, or
merged, with the block before or after, the starting address of this block is compared to the ending
address of the previous block and the ending address of this block is
compared to the starting address of the next block. 

C) For different workloads, it has been shown that First Fit and Best Fit tend to succeed in performing an allocation about equally well. Given this similarity, what is the primary advantage of the First Fit algorithm over Best Fit?


First fit only needs to search until it finds a block of sufficient
size, whereas Best fit must search through the entire list (or until
it finds a perfect match).  Thus, first fit is usually faster.

D) Most systems end up using Next Fit over First Fit or Best Fit; what is the primary advantage of the Next Fit algorithm over First Fit?


First fit starts at the beginning of the free list each time; the
problem with this is that many small holes may be left here, which
won't be sufficient for the current allocation (especially if requests
are often for the same size memory).

Next fit looks where it left off the previous time; it will be
faster since it doesn't need to recheck those small holes.

Problem 7: Segmentation

In this question, you will consider a memory system that contains support for segmentation (without paging).

A) Systems that support segmentation usually cause less internal fragmentation than systems that support dynamic relocation with only a simple base-and-bounds implementation. Why?

Grading: Worth 3 points 

With a simple base-and-bounds implementation, the entire address space
of the process must be contiguous, even if it is sparse.  This implies that the heap and
stack must be in the same contiguous range; since there is likely to
be a lot of extra, reserved space between the current stack and heap,
this space is wasted, internal to the process.

With segmentation, the stack and heap can be placed in separate
segments and no reserved space between them needs to be allocated.

B) What are the locations in the system that the segment table for a process may reside? Be precise.


Grading: Worth 3 points 

In the PCB (process control block) when the process is not running
(the PCB is kept in the address space of the OS, which is likely in
main memory, but could hypothetically be paged out to disk)

In the MMU (memory management unit) when the process is running.

C) Consider a system where logical and physical addresses are 15 bits, with 3 bits for the segment identifier and 12 bits for the segment offset. Assume that a process has the following segment table:

Segment Base Bounds R/W
0 0x254 0x200 10
1 0x5e0 0x98 11
2 0x108 0xff 11
3 0x000 0x0 00
4 0x000 0x0 00
5 0x4c4 0x80 11
6 0xa08 0x3a0 11
7 0x000 0x0 00

What physical address corresponds to each of the following logical addresses?

Grading: Worth 9 points 

0x7318: invalid
0x1044: 0x624 (The very common answers of 0x634 and 0x614 were also accepted :)
0x0112: 0x366
0x30a0: invalid
0x1100: invalid
0x2000: 0x108

This concludes your midterm exam. I hope you have a great Spring Break! <