CS361: Operating System

Jian Huang — Spring 2012

EECS | University of Tennessee - Knoxville


A semaphore S is an integer variable that, apart from initialization, is accessed only through two standard atomic operations: wait() and signal().

wait(S) {
  while S <= 0;
  S --;

signal(S) {
  S ++;

There are two kinds of semaphores:

Using Semaphore

The use of semaphore is to sandwich a critical section using the pair of "wait" and "signal".

do {
     critical section
     remainder section
} while(TRUE);

When a process executes wait() and finds that the semaphore value is not positive, the process blocks and is placed into a waiting queue associated with the semaphore, and the state of the process is switched to waiting state. The behavior of "wait" and "signal" are the following. Semaphore functions must execute atomically.

wait(semaphore * S)
  S->value --;
  if (S->value < 0) {
     add this process to S->list;

signal(semaphore *S)
  if (S->value <= 0) {
     remove a process P from S->list;

The conceptual struct of semaphore is the following.

typedef struct (
  int value;
  struct process * list;
} semaphore;

Within linux kernel, the actual code defining semaphore type is:

struct semaphore {
  raw_spinlock_t lock;
  unsigned int count;
  struct list_head wait_list;

POSIX semaphores have two kinds: named (sem_open) vs. unnamed (sem_init). Before Linux 2.6, linux only had unnamed, thread-shared semaphores. On 2.6+ and glibc with NPTL, Linux has complete implementation of POSIX semaphores. Need to link with cc -lrt. Named sempahores have system wide presence, while unnamed semaphores do not and have to be placed in a memory section shared by all threads and processes that need to be synchronized. Please consider:

  man sem_open/close/init/wait/post/destroy (POSIX API)
  man semget/semop (an older set of semaphore API)

Anecdotally, inter-process shared memory can be set up using shmget (acquiring shared memory) and shmat (attach to shared memory).

Problems for Synchronization

Deadlock example.

P0         P1
wait(S);   wait(Q);
wait(Q);   wait(S);

signal(S); signal(Q);
signal(Q); signal(S);

Priority inversion example - assuming there are three processes: high, middle and low; and high needs some results produced by low, low uses a resource that's also needed by middle, and middle preempts low before low can finish - this means high would have to wait after middle is done. Priority-inheritance protocol is a fix.

The overall goal of synchronization is to achieve: deadlock-free and starvation-free allocation of multiple shared resources among several processes. The most classic exercise is the "dining philosopher problem". The secret source of solving synchronization problem is to break the symmetry, or remove cycles in the dependency graph.

Bounded Buffer (consumer/producer problem)

semaphore mutex; // binary, initialized to 1
semaphore empty, full // counting

do {
  // produce an item in nextp
  // add next to buffer
} while (TRUE)

do {
  // remove an item from buffer
  // consume the item
} while (TRUE)

Reader-writer Problem

In this case we assume the design goal being - no reader be kept waiting unless a writer has already obtained permission to use a shared object.

semaphore mutex; // binary, initialized to 1
semaphore wrt; //  counting, initialized to 1 
int readcount = 0;

do {
  // write
} while (TRUE);

do {
  readcount ++;
  if (readcount == 1)
  // reading
  readcount --;
  if (readcount == 0)
} while(TRUE)


Monitor is a high-level language construct (in languages like Java, C#). It is an abstract data type implemented using semaphore. It operates one procedure at a time (thus handling mutual exclusion). Cannot be used directly by multiple processes.

Jian Huang / EECS /UTK / revised 01/2012