/* A readers/writers program using a shared buffer and semaphores  */
#include <sys/types.h>
#include <unistd.h>          
#include <stdio.h>
#include <sys/mman.h>
#include <sys/sem.h>

#define BUF_SIZE 5            /* logical size of buffer */
#define SHARED_MEM_SIZE (BUF_SIZE+2)*sizeof(int) /* size of shared memory */
#define run_length 10  /* number of iterations in test run */

#define buf_used 0     /* semaphore array index to check buffer elts used */
#define buf_space 1    /* semaphore array index to check buffer elts empty */

int sem_init(void)
{  /* procedure to create and initialize semaphores and return semaphore id,
      assuming two semaphores defined in the given array of semaphores     */
   int semid;

   /* create new semaphore set of 2 semaphores */
   if ((semid = semget (IPC_PRIVATE, 2, IPC_CREAT | 0600)) < 0)
     {  perror ("error in creating semaphore");/* 0600 = read/alter by user */
        exit (1);
     }

   /* initialization of semaphores */
   /* BUF_SIZE free spaces in empty buffer */
   if (semctl (semid, buf_space, SETVAL, BUF_SIZE) < 0)
     {  perror ("error in initializing first semaphore");
        exit (1);
     }

   /* 0 items in empty buffer */
   if (semctl (semid, buf_used, SETVAL, 0) < 0) 
     {  perror ("error in initializing second semaphore");
        exit (1);
     }
   return semid;
}

void P(int semid, int index)
{/* procedure to perform a P or wait operation on a semaphore of given index */
  struct sembuf sops[1];  /* only one semaphore operation to be executed */
   
   sops[0].sem_num = index;/* define operation on semaphore with given index */
   sops[0].sem_op  = -1;   /* subtract 1 to value for P operation */
   sops[0].sem_flg = 0;    /* type "man semop" in shell window for details */

   if (semop (semid, sops, 1) == -1)
     {  perror ("error in semaphore operation");
        exit (1);
     }
}

void V(int semid, int index)
{/* procedure to perform a V or signal operation on semaphore of given index */
   struct sembuf sops[1];  /* define operation on semaphore with given index */

   sops[0].sem_num = index;/* define operation on semaphore with given index */
   sops[0].sem_op  = 1;    /* add 1 to value for V operation */
   sops[0].sem_flg = 0;    /* type "man semop" in shell window for details */

   if (semop (semid, sops, 1) == -1)
     {  perror ("error in semaphore operation");
        exit (1);
     }
}

int main (void)
{  pid_t pid;          /* variable to record process id of child */

   /* shared memory elements */
   caddr_t shared_memory;   /* shared memory base address */
   int *in;         /* pointer to logical 'in' address for writer */
   int *out;        /* pointer to logical 'out' address for reader */
   int *buffer;     /* logical base address for buffer */

   /* semaphore elements */
   int semid;       /* identifier for a semaphore set */

   /* local variables */
   int i_child, j_child;   /* index variables */
   int value;              /* value read by child */

   /* set up shared memory segment */
   shared_memory=mmap(0, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, 
                              MAP_ANONYMOUS | MAP_SHARED, -1, 0);
   if (shared_memory == (caddr_t) -1)
     { perror ("error in mmap while allocating shared memory\n");
       exit (1);
     }

   /* set up pointers to appropriate places in shared memory segment */
   buffer = (int*) shared_memory; /* logical buffer starts at shared segment */
   in  = (int*) shared_memory + BUF_SIZE*sizeof(int);
   out = (int*) shared_memory + (BUF_SIZE+1)*sizeof(int);

   *in = *out = 0;          /* initial starting points */

   /* create and initialize semaphore */
   semid = sem_init();

   if (-1 == (pid = fork())) /* check for error in spawning child process */
     { perror ("error in fork");  
       exit (1);
     }

   if (0 == pid)             
     { /* processing for child == reader */
       printf ("The reader process begins.\n");

       for (i_child = 0; i_child < run_length; i_child++)
         {  P(semid, buf_used);  /* wait semaphore for something used */
            value = buffer[*out];
            *out = (*out + 1) % BUF_SIZE;
            printf ("Reader's report: item %2d == %2d\n", i_child, value);
            V(semid, buf_space); /* signal semaphore for space available */
            if ((i_child % 3) == 1)
              sleep(1);  /* take time to process every third element */
         }
       printf ("Reader done.\n");
     } 
  else 
     { /* processing for parent == writer */
       printf ("The writer process begins.\n");
       
       for (j_child = 0; j_child < run_length; j_child++)
         {  P(semid, buf_space);/* wait semaphore for space available */
            buffer[*in] = j_child*j_child;    /* put data in buffer */
            *in = (*in + 1) % BUF_SIZE;
            printf ("Writer's report: item %2d put in buffer\n", j_child);
            V(semid, buf_used); /* signal semaphore for something used */
            if ((j_child % 4) == 0)
              sleep(1); /* take time to generate every fourth element */
          }
       wait (pid);
       printf ("Writer done.\n");  

       /* Remove the semaphore from the system and destroy the set of
          semaphores and data structure associated with it. */
       if (semctl (semid, 0, IPC_RMID) < 0)
         {  perror ("error in removing semaphore from the system");
            exit (1);
         }
       printf ("Semaphore cleanup complete.\n");
     }
   exit (0);
}
