Section 6: Preemption and Virtual Memory

Sections Wed Mar 01 to Sat Mar 04

Handout written by Nick Troccoli. Edits by Pratyush Agarwal.

Learning Goals

During this section, you will:

  1. review interrupts and how we enable/disable them as needed to implement preemptive scheduling
  2. get practice with the idea of virtual memory and virtual vs. physical addresses

Get Started

Clone the section starter code by using the command below. This command creates a section6 directory containing the project files.

git clone /afs/ir/class/cs111/repos/lab6/shared section6

Next, pull up the online section checkoff and have it open in a browser so you can jot things down as you go.

1. Preemption

thread-cycle-preemption.cc is the same thread cycling program as last week's section, but using preemptive scheduling - meaning that every 0.5 seconds, it automatically yields the current thread to run the next one every time the timer fires, instead of the threads manually calling yield. The code has been updated so that all threads print forever, and are switched between whenever the timer fires. The main thread now does the following:

// Fire the timer every 500,000 microseconds to context switch
timer_init(500000, timer_interrupt_handler);

while (true) {
    cout << "Hello, I am the main thread" << endl;
}

The other threads now do the following:

void thread_run() {
    intr_enable(true);
    while (true) {
        cout << "Hello, I am thread " << current->name << endl;
    }
}

And the interrupt handler just calls yield:

void timer_interrupt_handler() {
    yield();
}

All other code is the same as from the prior section. Compile and run the program, and trace through the code to answer the following questions:

Q1: When the program switches back to executing the main thread the first time, where does it resume?

The timer implementation we provide disables interrupts before calling your handler, and re-enables them after. You can imagine that the code within the timer that calls your handler looks something like the following:

IntrGuard guard;
timer_handler();
...

Q2: Why is it important that interrupts be disabled when the handler is run?

Q3: The program calls intr_enable(true) at the start of the other thread functions to re-enable interrupts. Why is that needed? What happens if that line is removed? Why?

2) Virtual Memory Review

In lecture, we saw the first approach we will discuss for implementing virtual memory: base and bound. It relies on the idea of distinct virtual and physical address spaces, and the OS is the middle-person that intercepts any memory references and translates them.

Review how base and bound works, and answer the following questions:

Q4: Why is it useful to have separate virtual and physical addresses, with the OS intercepting them to translate, as opposed to just having physical addresses?

Q5: In a base and bound implementation, let's say a process has base = 4000 and bound = 200. For each of the following memory accesses, are they valid? And if so, what physical address would they really access?

  • accessing virtual address 0
  • accessing virtual address 50
  • accessing virtual address 300

Q6: Let's say the OS decides to move that process's reserved physical memory somewhere else; specifically, the OS copies it from base 4000 to base 2000. The OS also gives the process more memory, updating its bound to bound 500. How would the outcome of the memory accesses above change? (Cool note: the process itself has no idea its physical memory was moved, and all it needs to do to access the additional memory we've given it is to now refer to those larger virtual addresses. Pretty neat!).