POSIX Semaphores

POSIX Semaphores

     The potential learning curve of System V semaphores is much higher when compared to POSIX semaphores. This will be more understandable after you go through this section and compare it to what you learned in the previous section.

To start with, POSIX comes with simple semantics for creating, initializing, and performing operations on semaphores. They provide an efficient way to handle interprocess communication. POSIX comes with two kinds of semaphores: named and unnamed semaphores.

 

Named Semaphores

     If you look in the man pages, you’ll see that a named semaphore is identified by a name, like a System V semaphore, and, similarly, the semaphores have kernel persistence. This implies that these semaphores, like System V, are system-wide and limited to the number that can be active at any one time. The advantage of named semaphores is that they provide synchronization between unrelated process and related process as well as between threads.

A named semaphore is created by calling following function:

sem_t *sem_open(const char *name,  int oflag, mode_t mode , int value);

name

Name of the semaphore to be identified.

oflag

Is set to O_CREAT for creating a semaphore (or with O_EXCL if you want the call to fail if it already exists).

mode_t

Controls the permission setting for new semaphores.

value

Specifies the initial value of the semaphore.

A single call creates the semaphore, initializes it, and sets permissions on it, which is quite different from the way System V semaphores act. It is much cleaner and more atomic in nature. Another difference is that the System V semaphore identifies itself by means of type int (similar to a fd returned from open()), whereas the sem_open function returns type sem_t, which acts as an identifier for the POSIX semaphores.

 

From here on, operations will only be performed on semaphores. The semantics for locking semaphores is:

int  sem_wait(sem_t *sem);

This call locks the semaphore if the semaphore count is greater than zero. After locking the semaphore, the count is reduced by 1. If the semaphore count is zero, the call blocks.

 

The semantics for unlocking a semaphore is:

int  sem_post(sem_t *sem);

This call increases the semaphore count by 1 and then returns.

 

Once you’re done using a semaphore, it is important to destroy it. To do this, make sure that all the references to the named semaphore are closed by calling the sem_close() function, then just before the exit or within the exit handler call sem_unlink() to remove the semaphore from the system. Note that sem_unlink() would not have any effect if any of the processes or threads reference the semaphore.

 

Unnamed Semaphores

     Again, according to the man pages, an unnamed semaphore is placed in a region of memory that is shared between multiple threads (a thread-shared semaphore) or processes (a process-shared semaphore). A thread-shared semaphore is placed in a region where only threads of an process share them, for example a global variable. A process-shared semaphore is placed in a region where different processes can share them, for example something like a shared memory region. An unnamed semaphore provides synchronization between threads and between related processes and are process-based semaphores.

The unnamed semaphore does not need to use the sem_open call. Instead this one call is replaced by the following two instructions:

{

  sem_t semid;

  int sem_init(sem_t *sem, int pshared, unsigned  value);

}

pshared

This argument indicates whether this semaphore is to be shared between the threads of a process or between processes. If pshared has value 0, then the semaphore is shared between the threads of a process. If pshared is non-zero, then the semaphore is shared between processes.

value

The value with which the semaphore is to be initialized.

Once the semaphore is initialized, the programmer is ready to operate on the semaphore, which is of type sem_t. The operations to lock and unlock the semaphore remains as shown previously: sem_wait(sem_t *sem) and sem_post(sem_t *sem). To delete a unnamed semaphore, just call the sem_destroy function.

The last section of this article has a simple worker-consumer demo that has been developed by using a POSIX semaphore.

 

Related Threads

/* Includes */

#include <unistd.h>     /* Symbolic Constants */

#include <sys/types.h>  /* Primitive System Data Types */ 

#include <errno.h>      /* Errors */

#include <stdio.h>      /* Input/Output */

#include <stdlib.h>     /* General Utilities */

#include <pthread.h>    /* POSIX Threads */

#include <string.h>     /* String handling */

#include <semaphore.h>  /* Semaphore */

/* prototype for thread routine */

void handler ( void *ptr );

/* global vars */

/* semaphores are declared global so they can be accessed 

   in main() and in thread routine,

   here, the semaphore is used as a mutex */

sem_t mutex;

int counter; /* shared variable */

int main()

{

    int i[2];

    pthread_t thread_a;

    pthread_t thread_b;

    i[0] = 0; /* argument to threads */

    i[1] = 1;

    sem_init(&mutex, 0, 1);      /* initialize mutex to 1 - binary semaphore */

                                 /* second param = 0 - semaphore is local */

    /* Note: you can check if thread has been successfully created by checking return value of

       pthread_create */                         

    pthread_create (&thread_a, NULL, (void *) &handler, (void *) &i[0]);

    pthread_create (&thread_b, NULL, (void *) &handler, (void *) &i[1]);

    pthread_join(thread_a, NULL);

    pthread_join(thread_b, NULL);

   sem_destroy(&mutex); /* destroy semaphore */

    /* exit */  

    exit(0);

} /* main() */

void handler ( void *ptr )

{

  int x; 

    x = *((int *) ptr);

    printf("Thread %d: Waiting to enter critical region...n", x);

    sem_wait(&mutex);       /* down semaphore */

    /* START CRITICAL REGION */

    printf("Thread %d: Now in critical region...n", x);

    printf("Thread %d: Counter Value: %dn", x, counter);

    printf("Thread %d: Incrementing Counter...n", x);

    counter++;

    printf("Thread %d: New Counter Value: %dn", x, counter);

    printf("Thread %d: Exiting critical region...n", x);

    /* END CRITICAL REGION */    

    sem_post(&mutex);       /* up semaphore */

   pthread_exit(0); /* exit thread */

}

 

Related Process

The processes are said to be related if the new process is created from within an existing process, which ends up in duplicating the resources of the creating process. Such processes are called related processes. The following example shows how the related processes are synchronized.

#include <semaphore.h>

#include <stdio.h>

#include <errno.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/mman.h>

 

int main(int argc, char **argv)

{

  int fd, i,count=0,nloop=10,zero=0,*ptr;

  sem_t mutex;

 

  //open a file and map it into memory

 

  fd = open(“log.txt”,O_RDWR|O_CREAT,S_IRWXU);

  write(fd,&zero,sizeof(int));

  ptr = mmap(NULL,sizeof(int),PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);

  close(fd);

 

  /* create, initialize semaphore */

  if( sem_init(&mutex,1,1) < 0)

    {

      perror(“semaphore initilization”);

      exit(0);

    }

  if (fork() == 0) { /* child process*/

    for (i = 0; i < nloop; i++) {

      sem_wait(&mutex);

      printf(“child: %dn”, (*ptr)++);

      sem_post(&mutex);

    }

    exit(0);

  }

  /* back to parent process */

  for (i = 0; i < nloop; i++) {

    sem_wait(&mutex);

    printf(“parent: %dn”, (*ptr)++);

    sem_post(&mutex);

  }

  exit(0);

}

In this example, the related process access a common piece of memory, which is synchronized.

 

Unrelated Process

Processes are said to be unrelated if the two processes are unknown to each other and no relationship exists between them. For example, instances of two different programs are unrelated process. If such programs try to access a shared resource, a semaphore could be used to synchronize their access. The following source code demonstrates this:

<u>File1: server.c </u>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>

#include <semaphore.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

#define SHMSZ 27

char SEM_NAME[]= “vik”;

 

int main()

{

  char ch;

  int shmid;

  key_t key;

  char *shm,*s;

  sem_t *mutex;

 

  //name the shared memory segment

  key = 1000;

 

  //create & initialize semaphore

  mutex = sem_open(SEM_NAME,O_CREAT,0644,1);

  if(mutex == SEM_FAILED)

    {

      perror(“unable to create semaphore”);

      sem_unlink(SEM_NAME);

      exit(-1);

    }

 

  //create the shared memory segment with this key

  shmid = shmget(key,SHMSZ,IPC_CREAT|0666);

  if(shmid<0)

    {

      perror(“failure in shmget”);

      exit(-1);

    }

 

  //attach this segment to virtual memory

  shm = shmat(shmid,NULL,0);

 

  //start writing into memory

  s = shm;

  for(ch=’A';ch<=’Z';ch++)

    {

      sem_wait(mutex);

      *s++ = ch;

      sem_post(mutex);

    }

 

  //the below loop could be replaced by binary semaphore

  while(*shm != ‘*’)

    {

      sleep(1);

    }

  sem_close(mutex);

  sem_unlink(SEM_NAME);

  shmctl(shmid, IPC_RMID, 0);

  _exit(0);

}

<u>File 2: client.c</u>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>

#include <semaphore.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

#define SHMSZ 27

char SEM_NAME[]= “vik”;

 

int main()

{

  char ch;

  int shmid;

  key_t key;

  char *shm,*s;

  sem_t *mutex;

 

  //name the shared memory segment

  key = 1000;

 

  //create & initialize existing semaphore

  mutex = sem_open(SEM_NAME,0,0644,0);

  if(mutex == SEM_FAILED)

    {

      perror(“reader:unable to execute semaphore”);

      sem_close(mutex);

      exit(-1);

    }

 

  //create the shared memory segment with this key

  shmid = shmget(key,SHMSZ,0666);

  if(shmid<0)

    {

      perror(“reader:failure in shmget”);

      exit(-1);

    }

 

  //attach this segment to virtual memory

  shm = shmat(shmid,NULL,0);

 

  //start reading

  s = shm;

  for(s=shm;*s!=NULL;s++)

    {

      sem_wait(mutex);

      putchar(*s);

      sem_post(mutex);

    }

 

  //once done signal exiting of reader:This can be replaced by another semaphore

  *shm = ‘*';

  sem_close(mutex);

  shmctl(shmid, IPC_RMID, 0);

  exit(0);

}

The above executables (client and server) demonstrate how semaphore could be used between completely different processes.

Comments