NOTE: These notes are adapted from those of Allan Gottlieb, and are reproduced here with his permission.


================ Start Lecture #6 (Feb. 10)

2.2.2: Critical sections

We must prevent interleaving sections of code that need to be atomic with respect to each other. That is, the conflicting sections need mutual exclusion. If process A is executing its critical section, it excludes process B from executing its critical section. Conversely if process B is executing is critical section, it excludes process A from executing its critical section.

Requirements for a critical section implementation.

  1. No two processes may be simultaneously inside their critical section.
  2. No assumption may be made about the speeds or the number of CPUs.
  3. No process outside its critical section may block other processes.
  4. No process should have to wait forever to enter its critical section.

Semaphores

A semaphore is an integer-valued variable that is manipulated through the two operations DOWN(s) (also called P(s)) and UP(s) (also called V(s)) These are executed atomically (i.e. not interrupted in mid execution by other user processes) as follows:
DOWN(s) {
  if (s > 0) 
    then s--
    else block the calling process and put it on a queue waiting for s
  }


UP(s) {
  if there are any processes waiting for s 
   then choose one such process and move it to the ready queue
   else s++
 }

Mutual Exclusion

semaphore s = 1

loop 
   non-critical-section
   DOWN(s)
   critical section
   UP(s)

Readers and writers

Quite useful in multiprocessor operating systems. The ``easy way out'' is to treat all processes as writers in which case the problem reduces to mutual exclusion (P and V). The disadvantage of the easy way out is that you give up reader concurrency.

Semaphores: mutex = 1. db=1. 
Shared variable: nreaders = 0.

Reader:                                        Writer
loop                                           loop
  non-critical-section                           non-critical-section
  DOWN(mutex)                                    DOWN(db)
  nreaders++                                       *** WRITING DATA BASE ***
  if (nreaders==1) DOWN(db)                      UP(db)
  UP(mutex)
     *** READING DATA BASE ***
  DOWN(mutex)
  nreaders--
  if (nreaders==0) UP(db)
  UP(mutex)

Producer-consumer problem

Initially e=k, f=0 (counting semaphore); b=open (binary semaphore)

Producer                              Consumer

loop forever                          loop forever
    produce-item                        DOWN(f)
    DOWN(e)                             DOWN(b); take item from buf; UP(b)
    DOWN(b); add item to buf; UP(b)     UP(e)
    UP(f)                             consume-item

Dining Philosophers

A classical problem from Dijkstra

What algorithm do you use for access to the shared resource (the forks)?

The purpose of mentioning the Dining Philosophers problem without giving the solution is to give a feel of what coordination problems are like. The book gives others as well. We are skipping these (again this material would be covered in a sequel course). If you are interested look, for example, here.

2.2.3 Mutual exclusion with busy waiting

The operating system can choose not to preempt itself. That is, no preemption for system processes (if the OS is client server) or for processes running in system mode (if the OS is self service). Forbidding preemption for system processes would prevent the problem above where x<--x+1 not being atomic crashed the printer spooler if the spooler is part of the OS.

But simply forbidding preemption while in system mode is not sufficient.

Software solutions for two processes

Initially P1wants=P2wants=false

Code for P1                             Code for P2

Loop forever {                          Loop forever {
    P1wants <-- true         ENTRY          P2wants <-- true
    while (P2wants) {}       ENTRY          while (P1wants) {}
    critical-section                        critical-section
    P1wants <-- false        EXIT           P2wants <-- false
    non-critical-section }                  non-critical-section }

Explain why this works.

But it is wrong! Why?

Let's try again. The trouble was that setting want before the loop permitted us to get stuck. We had them in the wrong order!

Initially P1wants=P2wants=false

Code for P1                             Code for P2

Loop forever {                          Loop forever {
    while (P2wants) {}       ENTRY          while (P1wants) {}
    P1wants <-- true         ENTRY          P2wants <-- true
    critical-section                        critical-section
    P1wants <-- false        EXIT           P2wants <-- false
    non-critical-section }                  non-critical-section }

Explain why this works.

But it is wrong again! Why?

So let's be polite and really take turns. None of this wanting stuff.

Initially turn=1

Code for P1                      Code for P2

Loop forever {                   Loop forever {
    while (turn = 2) {}              while (turn = 1) {}
    critical-section                 critical-section
    turn <-- 2                       turn <-- 1
    non-critical-section }           non-critical-section }

This one forces alternation, so is not general enough. Specifically, it does not satisfy condition three, which requires that no process in its non-critical section can stop another process from entering its critical section. With alternation, if one process is in its non-critical section (NCS) then the other can enter the CS once but not again.

In fact, it took years (way back when) to find a correct solution. Many earlier ``solutions'' were found and several were published, but all were wrong. The first true solution was found by Dekker. What follows is Peterson's solution. When it was published, it was a surprise to see such a simple soluntion. In fact Peterson gave a solution for any number of processes. A proof that the algorithm for any number of processes satisfies our properties (including a strong fairness condition) can be found in Operating Systems Review Jan 1990, pp. 18-22.

Initially P1wants=P2wants=false  and  turn=1

Code for P1                        Code for P2

Loop forever {                     Loop forever {
    P1wants <-- true                   P2wants <-- true
    turn <-- 2                         turn <-- 1
    while (P2wants and turn=2) {}      while (P1wants and turn=1) {}
    critical-section                   critical-section
    P1wants <-- false                  P2wants <-- false
    non-critical-section               non-critical-section