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

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


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

   critical section

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)
     *** READING DATA BASE ***
  if (nreaders==0) UP(db)

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: Threads

Per process itemsPer thread items
Address spaceProgram counter
Global variablesMachine registers
Open filesStack
Child processes
Pending alarms
Signals and signal handlers
Accounting information

The idea is to have separate threads of control (hence the name) running in the same address space. Each thread is somewhat like a process (e.g., it is scheduled to run) but contains less state (the address space belongs to the process in which the thread runs.

2.2.1: The Thread Model

A process contains a number of resources such as address space, open files, accounting information, etc. In addition to these resources, a process has a thread of control, e.g., program counter, register contents, stack. The idea of threads is to permit multiple threads of control to execute within one process. This is often called multithreading and threads are often called lightweight processes. Because the threads in a process share so much state, switching between them is much less expensive than switching between separate processes.

Individual threads within the same process are not completely independent. For example there is no memory protection between them. This is typically not a security problem as the threads are cooperating and all are from the same user (indeed the same process). However, the shared resources do make debugging harder. For example one thread can easily overwrite data needed by another and if one thread closes a file other threads can't read from it.

2.2.2: Thread Usage

Often, when a process A is blocked (say for I/O) there is still computation that can be done. Another process can't B do this computation since it doesn't have access to the A's memory. But two threads in the same process do share the memory so there is no problem.

An important example is a multithreaded web server. Each thread is responding to a single WWW connection. While one thread is blocked on I/O, another thread can be processing another WWW connection. Why not use separate processes, i.e., what is the shared memory?
Ans: The cache of frequently referenced pages.

A common organization is to have a dispatcher thread that fields requests and then passes this request on to an idle thread.

Another example is a producer-consumer problem in which we have 3 threads in a pipeline. One reads data, the second processes the data read, and the third outputs the processed data. Again, while one thread is blocked the others can execute.

2.2.3: Implementing threads in user space

Write a (threads) library that acts as a mini-scheduler and implements thread_create, thread_exit, thread_wait, thread_yield, etc. The central data structure maintained and used by this library is the thread table the analogue of the process table in the operating system itself.



2..2.4: Implementing Threads in the Kernel

Move the thread operations into the operating system itself. This naturally requires that the operating system itself be (significantly) modified and is thus not a trivial undertaking.

2.2.5: Hybrid Implementations

One can write a (user-level) thread library even if the kernel also has threads. Then each kernel thread can switch between user level threads.

Making Single-threaded Code Multithreaded

Definitely NOT for the faint of heart.

Chapter 3: Deadlocks

A deadlock occurs when a every member of a set of processes is waiting for an event that can only be caused by a member of the set.

Often the event waited for is the release of a resource.

In the automotive world deadlocks are called gridlocks.

For a computer science example consider two processes A and B that each want to print a file currently on tape.

  1. A has obtained ownership of the printer and will release it after printing one file.
  2. B has obtained ownership of the tape drive and will release it after reading one file.
  3. A tries to get ownership of the tape drive, but is told to wait for B to release it.
  4. B tries to get ownership of the printer, but is told to wait for A to release the printer.

Bingo: deadlock!

3.1: Resources:

The resource is the object granted to a process.

3.1.1: Preemptable and Nonpreemptable Resourses

3.1.2: Resourse Acquisition

Simple example of the trouble you can get into.

3.2: Introduction to Deadlocks

To repeat: A deadlock occurs when a every member of a set of processes is waiting for an event that can only be caused by a member of the set.

Often the event waited for is the release of a resource.

3.2.1: (Necessary) Conditions for Deadlock

The following four conditions (Coffman; Havender) are necessary but not sufficient for deadlock. Repeat: They are not sufficient.

  1. Mutual exclusion: A resource can be assigned to at most one process at a time (no sharing).
  2. Hold and wait: A processing holding a resource is permitted to request another.
  3. No preemption: A process must release its resources; they cannot be taken away.
  4. Circular wait: There must be a chain of processes such that each member of the chain is waiting for a resource held by the next member of the chain.

3.2.2: Deadlock Modeling

On the right is the Resource Allocation Graph, also called the Reusable Resource Graph.