NOTE: These notes are by Allan Gottlieb, and are reproduced here, with superficial modifications, with his permission. "I" in this text generally refers to Prof. Gottlieb, except in regards to administrative matters.

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

#### P and V and Semaphores

Note:

Tanenbaum does both busy waiting (like above) and blocking (process switching) solutions. We will only do busy waiting, which is easier. Some authors use the term semaphore only for blocking solutions and would call our solutions spin locks.

End of Note.

The entry code is often called P and the exit code V (Tanenbaum only uses P and V for blocking, but we use it for busy waiting). So the critical section problem is to write P and V so that

```loop forever
P
critical-section
V
non-critical-section
```
satisfies
1. Mutual exclusion.
2. No speed assumptions.
3. No blocking by processes in NCS.
4. Forward progress (my weakened version of Tanenbaum's last condition).

Note that I use indenting carefully and hence do not need (and sometimes omit) the braces {}

A binary semaphore abstracts the TAS solution we gave for the critical section problem.

• A binary semaphore S takes on two possible values ``open'' and ``closed''
• Two operations are supported
• P(S) is
```    while (S=closed) {}
S<--closed     <== This is NOT the body of the while
```
where finding S=open and setting S<--closed is atomic
• That is, wait until the gate is open, then run through and atomically close the gate
• Said another way, it is not possible for two processes doing P(S) simultaneously to both see S=open (unless a V(S) is also simultaneous with both of them).
• V(S) is simply S<--open

The above code is not real, i.e., it is not an implementation of P. It is, instead, a definition of the effect P is to have.

To repeat: for any number of processes, the critical section problem can be solved by

```loop forever
P(S)
CS
V(S)
NCS
```

The only specific solution we have seen for an arbitrary number of processes is the one just above with P(S) implemented via test and set.

Remark: Peterson's solution requires each process to know its processor number. The TAS soluton does not. Moreover the definition of P and V does not permit use of the processor number. Thus, strictly speaking Peterson did not provide an implementation of P and V. He did solve the critical section problem.

To solve other coordination problems we want to extend binary semaphores.

• With binary semaphores, two consecutive Vs do not permit two subsequent Ps to succeed (the gate cannot be doubly opened).
• We might want to limit the number of processes in the section to 3 or 4, not always just 1.

The solution to both of these shortcomings is to remove the restriction to a binary variable and define a generalized or counting semaphore.

• A counting semaphore S takes on non-negative integer values
• Two operations are supported
• P(S) is
```    while (S=0) {}
S--
```
where finding S>0 and decrementing S is atomic
• That is, wait until the gate is open (positive), then run through and atomically close the gate one unit
• Said another way, it is not possible for two processes doing P(S) simultaneously to both see the same positive value of S unless a V(S) is also simultaneous.
• V(S) is simply S++

These counting semaphores can solve what I call the semi-critical-section problem, where you premit up to k processes in the section. When k=1 we have the original critical-section problem.

```initially S=k

loop forever
P(S)
SCS   <== semi-critical-section
V(S)
NCS
```

#### Producer-consumer problem

• Two classes of processes
• Producers, which produce times and insert them into a buffer.
• Consumers, which remove items and consume them.
• What if the producer encounters a full buffer?
Answer: It waits for the buffer to become non-full.
• What if the consumer encounters an empty buffer?
Answer: It waits for the buffer to become non-empty.
• Also called the bounded buffer problem.
• Another example of active entities being replaced by a data structure when viewed at a lower level (Finkel's level principle).
```Initially e=k, f=0 (counting semaphore); b=open (binary semaphore)

Producer                         Consumer

loop forever                     loop forever
produce-item                     P(f)
P(e)                             P(b); take item from buf; V(b)
P(b); add item to buf; V(b)      V(e)
V(f)                             consume-item
```
• k is the size of the buffer
• e represents the number of empty buffer slots
• f represents the number of full buffer slots
• We assume the buffer itself is only serially accessible. That is, only one operation at a time.
• This explains the P(b) V(b) around buffer operations
• I use ; and put three statements on one line to suggest that a buffer insertion or removal is viewed as one atomic operation.
• Of course this writing style is only a convention, the enforcement of atomicity is done by the P/V.
• The P(e), V(f) motif is used to force ``bounded alternation''. If k=1 it gives strict alternation.

#### Dining Philosophers

A classical problem from Dijkstra

• 5 philosophers sitting at a round table
• Each has a plate of spaghetti
• There is a fork between each two
• Need two forks to eat
What algorithm do you use for access to the shared resource (the forks)?
• The obvious solution (pick up right; pick up left) deadlocks.
• Big lock around everything serializes.
• Good code in the book.

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.

• Two classes of processes.
• Readers, which can work concurrently.
• Writers, which need exclusive access.
• Must prevent 2 writers from being concurrent.
• Must prevent a reader and a writer from being concurrent.
• Must permit readers to be concurrent when no writer is active.
• Perhaps want fairness (i.e., freedom from starvation).
• Variants

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. Again for more information see the web page referenced above.

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.

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.