Lab Assignment 2 - Communicating Processes

Objectives:

The objective of this assignment is to understand the workings of POSIX threads, semaphores, and mutex locks.

Assignment:

This laboratory assignment consist of multiple programming assignments defined by tasks. Each program must be submitted for evaluation and grading. There is no requirement to write a report for this laboratory assignment.

Project team:

You are allowed to work on this assignment in a team of at most two students.

Submission of source code and report:

Submit the source codes of the programming assignments to the TA. Include your name and last four digits of your SSN in all source codes.

Requirements:

You need to be able to write, compile, and debug C or C++ programs, and have a basic undestanding of threads, semaphores, and monitors.

Use the man command to access the manual pages. If you cannot access the man pages, add this line to your .tcshrc file and then logoff/logon:

setenv MANPATH /usr/man:/usr/share/man:/usr/local/man:/usr/local/share/man

The examples shown in this document are all in C. But it is not required to use C for your assignments and you may use C++ constructs if you wish. Note that the C examples use the following functions that are rarely used in C++:

You should consult the manual pages to understand these library functions. The stdin, stdout, and stderr streams are frequently used with these functions.

Task 1 - Using Semaphores to Synchronize Threads

Linux supports POSIX 1003.1b semaphores defined in <semaphores.h>. There are many non-portable semaphore implementations. Here we will focus solely on the POSIX semaphores on Linux. We will use these to synchronize a set of concurrent POSIX threads.

A semaphore is an object of of type sem_t. Consult the manual pages on a Linux machine to understand the following library functions:

The POSIX thread library define in <pthread.h> provides the following functions for pthread_t thread objects:

Consider the following incomplete program:

/** @file text.c
* Demonstrates semaphores to contol the synchronization of threads.
*
* This program starts a number of processes that are synchronized with
* semaphores to control the text output.
*
* @author Robert van Engelen
**/

#include <stdlib.h>
#include <stdio.h>

#include <unistd.h> /* defines _POSIX_THREADS if pthreads are available */
#ifdef _POSIX_THREADS
# include <pthread.h>
#endif

#include <semaphore.h>

void *text(void *arg);

int code[] = { 4, 6, 3, 1, 5, 0, 2 };

int main()
{
  int i;
  pthread_t tid[7];

  for (i = 0; i < 7; i++)
    pthread_create(&tid[i], NULL, text, (void*)&code[i]);

  return 0;
}

void *text(void *arg)
{
  int n = *(int*)arg;

  switch (n)
  {
    case 0:
      printf("A semaphore S is an integer-valued variable which can take only non-negative\n");
      printf("values. Exactly two operations are defined on a semaphore:\n\n");
      break;

    case 1:
      printf("Signal(S): If there are processes that have been suspended on this semaphore,\n");
      printf(" wake one of them, else S := S+1.\n\n");
      break;

    case 2:
      printf("Wait(S): If S>0 then S:=S-1, else suspend the execution of this process.\n");
      printf(" The process is said to be suspended on the semaphore S.\n\n");
      break;

    case 3:
      printf("The semaphore has the following properties:\n\n");
      break;

    case 4:
      printf("1. Signal(S) and Wait(S) are atomic instructions. In particular, no\n");
      printf(" instructions can be interleaved between the test that S>0 and the\n");
      printf(" decrement of S or the suspension of the calling process.\n\n");
      break;

    case 5:
      printf("2. A semaphore must be given an non-negative initial value.\n\n");
      break;

    case 6:
      printf("3. The Signal(S) operation must waken one of the suspended processes. The\n");
      printf(" definition does not specify which process will be awakened.\n\n");
      break;
  }

  pthread_exit(0);
}

There are a couple of problems with this program. First of all, the threads do not have a chance to complete, because the main program terminates without waiting for them. Second, the threads are not synchronized and therefore the text output is garbled.

Your task is to add semaphores to this program to synchronize the threads. You may declare the semaphores as global objects.

Note: to link the Pthread library, use gcc option -lpthread as in gcc -Wall -o text text.c -lpthread

Task 2 - The BATMAN

In a heroic effort to meet increasingly tighter shipping deadlines, a company has employed a Bidirectional Autonomous Trolley (BAT) system to move products from its warehouses to the delivery trucks. Each BAT is a mobile platform that travels on separate tracks back and forth between a warehouse and a truck. Because the goods are fragile, the tracks are perfectly leveled, which requires the placement of level crossings between warehouses. At most one BAT can cross at a time. Traffic at the crossing arriving from the right has the right of way. But this presents a problem, as the company soon found out when a simultaneous shipment of plastic penguins and black umbrellas caused the system to come to a grinding halt. Two BATs with the shipments and two other BATs returning to the warehouses were deadlocked at a level crossing:

In our system each BAT is controlled by a separate thread. It is your task to create BATMAN: a BAT Manager that prevents deadlock at a crossing. Part of the solution is to use one mutex lock for the crossing, so that at most one BAT at a time can pass. The mutex lock will act as a monitor for the BAT operations at the crossing. Besides the mutex lock, we will also need a set of condition variables to control the synchronization of the BATs.

We need a condition variable per BAT to queue BATs arriving from one direction (NorthQueue, EastQueue, SouthQueue, WestQueue). For example, when a BAT from North is already at the crossing, a second BAT from North will have to wait.

Another type of condition variable is needed to let BATs from the right have precedence to cross (NorthFirst, EastFirst, SouthFirst, WestFirst). However, using this rule can cause starvation. To prevent starvation, when a BAT is waiting to cross but BATs continuously arriving from the right have the right of way, we will let a BAT that just passed the crossing signal a waiting BAT on his left.

When deadlock occurs the BAT Manager must signal one of the BATs to proceed, e.g. the BAT from North. You will need a counter for each direction to keep track of the number of BATs waiting in line.

The program must take a string of 'n', 's', 'e', 'w' from the command line indicating a sequence of arrivals of BATs from the four directions. For example:

$ ./batman nsewwewn
BAT 4 from West arrives at crossing
BAT 2 from South arrives at crossing
BAT 1 from North arrives at crossing
BAT 3 from East arrives at crossing
DEADLOCK: BAT jam detected, signalling North to go
BAT 1 from North leaving crossing
BAT 3 from East leaving crossing
BAT 2 from South leaving crossing
BAT 4 from West leaving crossing
BAT 6 from East arrives at crossing
BAT 5 from West arrives at crossing
BAT 8 from North arrives at crossing
BAT 5 from West leaving crossing
BAT 6 from East leaving crossing
BAT 8 from North leaving crossing
BAT 7 from West arrives at crossing
BAT 7 from West leaving crossing

Note: the ordering of the above arrivals and departures may vary between runs and implementations. You don't need to produce exactly the same output.

To summarize:

To implement the solution, analyze the actions of a BAT arriving from a particular direction under different circumstances, i.e. another BAT is already at the crossing, there is a BAT at the right, and/or there is a BAT at the left. Determine what to do when it arrives at the crossing and it must wait in line or wait for the BAT at his right, what to do when it passed the crossing, and what to do when it leaves the crossing.

Consult the manual pages on a Linux machine to understand the following library functions on mutex locks (pthread_mutex_t) and condition variables (pthread_cond_t):

Note that we will use a mutex lock to implement monitor-like functionality, i.e. the BAT operations are part of the monitor.

Addendum

The BATMAN monitor has the following high-level structure:

monitor BATMAN
{
  ... // shared data, e.g. counters

  condition variable NorthQueue, EastQueue, SouthQueue, WestQueue,
                     NorthFirst, EastFirst, SouthFirst, WestFirst;

  arrive(BAT b)
  {
    printf("BAT %d from %s arrives at crossing\n", b.num, b.dir);
    ... // code to check traffic in line, use counters, condition variables etc.
  }

  cross(BAT b)
  {
    ... // code to check traffic from the right, use counters, condition variables etc
    sleep(1); // it takes one second for a BAT to cross
  }

  leave(BAT b)
  {
    printf("BAT %d from %s leaving crossing\n", b.num, b.dir);
    ... // code to check traffic, use counters, condition variables etc.
  }

  check()
  {
    ... // the manager checks for deadlock and resolves it
  }
};

A BATs execute the following operations:

arrive(b);
cross(b);
leave(b);

It takes a BAT one second to cross. Use sleep(1) to simulate the BAT's progress.

The BAT manager executes the following operations:

while BATs are active do
  check();

Note that you need a mutex lock to implement the monitor.

- End -