SOURCE FILE: sem.cpp



/*
     (Latest Revision: October 23, 2011)
 
*/

#include <iostream>
#include <pthread.h>
#include "sem.h"

using namespace std ;

/* 
 These directives make the two variables "checking" and
 "stdoutLock" visible to the functions in this file.  The
 variables "checking" and "stdoutLock" are supposed to be
 declared in the main program file.
 */   

extern int checking ;  
extern pthread_mutex_t stdoutLock ;

/* ################################################## */
/*                    create_sim_sem                  */
/* ################################################## */

/* Function to allocate a semaphore and
 initialize it. */

sim_semaphore create_sim_sem(int  init_val) 
{
    sim_semaphore ptr ;
    ptr = new sim_sem_data ;
    if   (ptr == NULL)
                cout << "ERROR: Out of space for semaphore." << endl ;
    else
    {    if ( 0!=pthread_mutex_init(&(ptr->lock), NULL) )
        {  cout << "MUTEX INITIALIZATION FAILURE!" << endl;
                exit(-1) ;}
                
                if ( 0!=pthread_cond_init(&(ptr->cond), NULL) )
                {  cout << "CONDITION INITIALIZATION FAILURE!" << endl ;
            exit(-1) ;}
                
                (ptr->value)     =  init_val ;
                (ptr->Qfront)    =  NULL ;
                (ptr->Qrear)     =  NULL ;
    }
        
    return(ptr) ;
}

/* ################################################## */
/*                    empty_sem_Q                     */
/* ################################################## */

/* Test to check if the semaphore queue is
 empty. */

int empty_sem_Q ( sim_sem_data sem_d)
{
        return (sem_d.Qfront == NULL) ;
}

/* ################################################## */
/*                    print_sem_Q                     */
/* ################################################## */


/* For printing the id_num's of the processes
 waiting in the semaphore queue -- for debugging
 & checking.
 
 -- WARNING! Don't add code in this procedure to
 lock the stdoutLock.  Callers of print_sem_Q
 will lock printing before making the call.
 Thus, it is an error for this procedure to
 try to gain the lock.
 */

/* Software maintenance note: 2011/10/23 - had to make a
 change so that id_num becomes a pointer to int type, due to
 compiler now not allowing return type of pthread_self to be
 cast to an int.  Below we cast temp->id_num to long for
 printing, so that it doesn't come out as an octal value.  I
 hope the long data type has enough bits so the numbers are
 unique. */

void print_sem_Q (sim_sem_data sem_d)
{
        sim_PCB  *temp ;
        temp = sem_d.Qfront ;
        cout << "Printing Q .... " << endl ;
        if (temp == NULL) cout << "Q is empty." << endl ;
        while (temp != NULL)
        {
                cout << "Thread #" << (long) temp->id_num << endl ;
                temp = temp->next ;
        }
}

/* ################################################## */
/*                      enq_sem_Q                     */
/* ################################################## */

/* For placing a process on the semaphore
 queue. */

void enq_sem_Q (sim_semaphore sem, sim_PCB * proc)
{    /* Place the unique id of the thread into
          a field of the sim_PCB. */
        
        /* Software maintenance note: 2011/10/23 This was a cast to
         int in previous versions of the code, but that stopped
         working with version 10.6 of the Mac OS.  So I changed
         the data type of id_num to int*, which seems to give a
         workable alternative. */
        
        (proc->id_num)   = (int*) pthread_self() ;
        (proc->next)     = NULL ;
        (proc->in_Q)     = 1 ;
        if   (empty_sem_Q(*sem)) sem->Qfront = proc ;
        else sem->Qrear->next = proc ;
        sem->Qrear = proc ;
}

/* ################################################## */
/*                    serve_sem_Q                     */
/* ################################################## */

/* For removing a process from the
 semaphore queue. */

void serve_sem_Q(sim_semaphore sem)
{
        sim_PCB  *temp ;
        
        if (checking)
        { pthread_mutex_lock (&stdoutLock) ;
        
                /* Software maintenance note: 2011/10/23.  I was casting the
                 'sem' to 'int' before, but that stopped working with
                 version 10.6 of the Mac OS.  So I cast to 'int*' and then
                 cast to 'long' for printing.  The compiler allows this
                 and the numbers come out in decimal.  Without the cast to
                 'long' I was getting octal output. */
                
                
                cout << "State of Q in semaphore #"<< (long) (int*) sem 
                << " prior to Q serve: " << endl ;
                print_sem_Q (*sem) ;
                pthread_mutex_unlock(&stdoutLock) ; }
        
        temp              =    sem->Qfront ;
        sem->Qfront        =   sem->Qfront->next ;
        
        /* Provide a means by which an enqueued
         thread can tell if it has been dequeued. */
        
        (temp->in_Q)   =   0 ; 
        if (sem->Qfront == NULL)  sem->Qrear = NULL ;
        
        if (checking) 
        { pthread_mutex_lock(&stdoutLock) ;
                cout << "State of Q in semaphore #"<< (long) (int*) sem 
                << " after Q serve: " << endl ;
                print_sem_Q (*sem) ;
                pthread_mutex_unlock(&stdoutLock) ; }
}

/* ################################################## */
/*                    wait_sem                        */
/* ################################################## */

/* The wait operation on the simulated
 semaphore. */

void wait_sem (sim_semaphore sem ) 
{ 
        pthread_mutex_lock( &(sem->lock) ) ;
        sem->value = sem->value - 1 ;
        
        if (checking)
        { pthread_mutex_lock(&stdoutLock) ;
                cout << "Thread #" << (long) pthread_self() 
                << " begins a wait and"
                << " decrements the value of semaphore #" 
                << (long) (int*) sem << " to: " << sem->value << endl ;
                pthread_mutex_unlock(&stdoutLock) ; }
        
        if (sem->value < 0)
        /* START (sem->value < 0) */
        {
                sim_PCB * proc = new sim_PCB ;
                if    (proc == NULL) 
                        cout << "ERROR: Out of space for sim_PCB." << endl ;
                else               
                /* START (proc != NULL) */
                {
                        if (checking)
                        { pthread_mutex_lock(&stdoutLock) ;
                                cout << "Thread #" << (long) pthread_self() 
                                << " queues up on semaphore #" << (long) (int*) sem
                                << "." << endl ;
                                pthread_mutex_unlock(&stdoutLock) ;  }
                        
                        /* Put a sim_PCB for this thread on the
                         queue.*/
                        
                        enq_sem_Q(sem, proc) ;  
                        
                        /* The thread "dozes" until it awakens
                         and finds that it has been taken off
                         the queue. */
                        
                        do
                        {
                                if (checking)
                                {  pthread_mutex_lock(&stdoutLock) ;
                                        cout << "Thread #" << (long) pthread_self()
                                        << " now BLOCKS on semaphore #" << (long) (int*) sem
                                        << " by doing a pthread_cond_wait." << endl ;
                                        pthread_mutex_unlock(&stdoutLock) ; }
                                
                                if ( 0!= pthread_cond_wait (&(sem->cond),&(sem->lock) ) )
                                {  cout << "COND BROADCAST FAILURE!" << endl ;
                                        exit(-1) ;}
                                
                                if (checking)
                                {  pthread_mutex_lock(&stdoutLock) ;
                                        cout << "Thread #" << (long) pthread_self()
                                        << " on semaphore #" << (long) (int*) sem
                                        << " wakes up, and checks to" << endl
                                        << "\t" << "see if a signal_sem OP has "
                                        << "taken it off the queue." << endl 
                                        << "\t" << "It IS" ;
                                        if (!proc->in_Q)  cout << " NOT" ;
                                        cout << " in the queue." << endl ;
                                        pthread_mutex_unlock(&stdoutLock) ; }
                        } /* END OF DO-PART */
                        while (proc->in_Q) ;
                        delete proc ;
                }  /* END  else (proc != NULL) */
        } /* END   if (sem->value < 0) */
        
        if (checking)
        {  pthread_mutex_lock(&stdoutLock) ;
        cout << "Thread #" << (long) pthread_self()
        << " completes a wait on semaphore #" 
        << (long) (int*) sem << "." << endl ;
        pthread_mutex_unlock(&stdoutLock) ; }
        
        pthread_mutex_unlock( &(sem->lock) ) ;
}


/* ################################################## */
/*                    signal_sem                      */
/* ################################################## */

/* The signal operation on the simulated
 semaphore. */

void signal_sem (sim_semaphore sem)  
{
        pthread_mutex_lock(&(sem->lock)) ;
        sem->value = sem->value + 1 ;
        
        if (checking)
        { pthread_mutex_lock(&stdoutLock) ;
                cout << "Thread #" << (long) pthread_self() 
                << " begins a signal and"
                << " increments the value of semaphore #" 
                << (long) (int*) sem << " to: " << sem->value << endl ;
                pthread_mutex_unlock(&stdoutLock) ; }
        
        if (sem->value <= 0)
        {
                if (checking)
                {  pthread_mutex_lock(&stdoutLock) ;
                        cout << "Thread #" << (long) pthread_self() 
                        << " removes a PCB from the queue of"
                        << " semaphore #" << (long) (int*) sem << "." << endl ;
                        pthread_mutex_unlock(&stdoutLock) ; }
                
                serve_sem_Q(sem) ;  /* Remove next sim_PCB from queue. */
                
                /* Tell each dozing thread to check
                 to see if it is the one removed. */
                
                if (checking)
                {  pthread_mutex_lock(&stdoutLock) ;
                        cout << "Thread #" << (long) pthread_self()
                        << " broadcasts to the thread(s) waiting on semaphore #"
                        << (long) (int*) sem << "." << endl ;
                        pthread_mutex_unlock(&stdoutLock) ;  }
                
                if ( 0!=pthread_cond_broadcast(&(sem->cond)) )
                {  cout << "COND BROADCAST FAILURE!" << endl ;
                        exit(-1) ;}
        }
        
        if (checking)
        {  pthread_mutex_lock(&stdoutLock) ;
                cout << "Thread #" << (long) pthread_self()
                << " completes a signal on semaphore #" 
                << (long) (int*) sem << "." << endl ;
                pthread_mutex_unlock(&stdoutLock) ; }
        
        
        pthread_mutex_unlock( &(sem->lock) ) ;
}