9    Semaphores

POSIX 1003.1b semaphores provide an efficient form of interprocess communication. Cooperating processes can use semaphores to synchronize access to resources, most commonly, shared memory. Semaphores can also protect the following resources available to multiple processes from uncontrolled access:

This chapter includes the following sections:


9.1    Overview of Semaphores

Semaphores are used to control access to shared resources by processes. Counting semaphores have a positive integral value representing the number of processes that can concurrently lock the semaphore.

There are named and unnamed semaphores. Named semaphores provide access to a resource between multiple processes. Unnamed semaphores provide multiple accesses to a resource within a single process or between related processes. Some semaphore functions are specifically designed to perform operations on named or unnamed semaphores.

The semaphore lock operation checks to see if the resource is available or is locked by another process. If the semaphore's value is a positive number, the lock is made, the semaphore value is decremented, and the process continues execution. If the semaphore's value is zero or a negative number, the process requesting the lock waits (is blocked) until another process unlocks the resource. Several processes may be blocked waiting for a resource to become available.

The semaphore unlock operation increments the semaphore value to indicate that the resource is not locked. A waiting process, if there is one, is unblocked and it accesses the resource. Each semaphore keeps count of the number of processes waiting for access to the resource.

Semaphores are global entities and are not associated with any particular process. In this sense, semaphores have no owners making it impossible to track semaphore ownership for any purpose, for example, error recovery.

Semaphore protection works only if all the processes using the shared resource cooperate by waiting for the semaphore when it is unavailable and incrementing the semaphore value when relinquishing the resource. Since semaphores lack owners, there is no way to determine whether one of the cooperating processes has become uncooperative. Applications using semaphores must carefully detail cooperative tasks. All of the processes that share a resource must agree on which semaphore controls the resource.

POSIX 1003.1b semaphores are persistent. The value of the individual semaphore is preserved after the semaphore is no longer open. For example, a semaphore may have a value of 3 when the last process using the semaphore closes it. The next time a process opens that semaphore, it will find the semaphore has a value of 3. For this reason, cleanup operations are advised when using semaphores.

Note that because semaphores are persistent, you should call the sem_unlink function after a system reboot. After calling sem_unlink, you should call the sem_open function to establish new semaphores.

The semaphore descriptor is inherited across a fork. A parent process can create a semaphore, open it, and fork. The child process does not need to open the semaphore and can close the semaphore if the application is finished with it.


9.2    The Semaphore Interface

The following functions allow you to create and control P1003.1b semaphores:

Function Description
sem_close Deallocates the specified named semaphore
sem_destroy Destroys an unnamed semaphore
sem_getvalue Gets the value of a specified semaphore
sem_init Initializes an unnamed semaphore
sem_open Opens/creates a named semaphore for use by a process
sem_post Unlocks a locked semaphore
sem_trywait Performs a semaphore lock on a semaphore only if it can lock the semaphore without waiting for another process to unlock it
sem_unlink Removes a specified named semaphore
sem_wait Performs a semaphore lock on a semaphore

You create an unnamed semaphore with a call to the sem_init function, which initializes a counting semaphore with a specific value. To create a named semaphore, call sem_open with the O_CREAT flag specified. The sem_open function establishes a connection between the named semaphore and a process.

Semaphore locking and unlocking operations are accomplished with calls to the sem_wait, sem_trywait, and sem_post functions. You use these functions for named and unnamed semaphores. To retrieve the value of a counting semaphore, use the sem_getvalue function.

When the application is finished with an unnamed semaphore, the semaphore name is destroyed with a call to sem_destroy. To deallocate a named semaphore, call the sem_close function. The sem_unlink function removes a named semaphore. The semaphore is removed only when all processes using the semaphore have deallocated it using the sem_close function.


9.2.1    Creating and Opening a Semaphore

A call to the sem_init function creates an unnamed counting semaphore with a specific value. If you specify a non-zero value for the pshared argument, the semaphore can be shared between processes. If you specify the value zero, the semaphore can be shared among threads of the same process.

The sem_open function establishes a connection between a named semaphore and the calling process. Two flags control whether the semaphore is created or only accessed by the call. Set the O_CREAT flag to create a semaphore if it does not already exist. Set the O_EXCL flag along with the O_CREAT flag to indicate that the call to sem_open should fail if the semaphore already exists.

Subsequent to creating a semaphore with either sem_init or sem_open, the calling process can reference the semaphore by using the semaphore descriptor address returned from the call. The semaphore is available in subsequent calls to the sem_wait, sem_trywait, and sem_post functions, which control access to the shared resource. You can also retrieve the semaphore value by calls to sem_getvalue.

If your application consists of multiple processes that will use semaphores to synchronize access to a shared resource, each of these processes must first open the semaphore by a call to the sem_open function. After the initial call to the sem_init or sem_open function to establish the semaphore, each cooperating function must also call the sem_open function. If all cooperating processes are in the same working directory, just the name is sufficient. If the processes are contained in different working directories, the full pathname must be used. It is strongly recommended that the full pathname be used, such as /tmp/mysem1. The directory must exist for the call to succeed.

On the first call to the sem_init or sem_open function, the semaphore is initialized to the value specified in the call.

The following example initializes an unnamed semaphore with a value of 5, which can be shared among related processes:

#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <semaphore.h>

.
.
.
sem_t mysem; int pshared = TRUE; unsigned int value = 5; int sts;
.
.
.
sts = sem_init(&mysem, pshared, value); if (sts) { perror("sem_init() failed"); }

The following example creates a semaphore named /tmp/mysem with a value of 3:

#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <sys/stat.h>

.
.
.
sem_t *mysemp; int oflag = O_CREAT; mode_t mode = 0644; const char semname[] = "/tmp/mysem" unsigned int value = 3; int sts;
.
.
.
mysemp = sem_open(semname, oflag, mode, value); if (mysemp == (void *)-1) { perror(sem_open() failed "); }

To access a previously created semaphore, a process must call the sem_open function using the name of the semaphore.

To determine the value of a previously created semaphore, use the sem_getvalue function. Pass the semaphore and the location for storing the value to the function; it returns the value of the semaphore specified when the sem_init or sem_open function was called.

The name of the semaphore remains valid until the semaphore is removed with a call to the sem_unlink function.


9.2.2    Locking and Unlocking Semaphores

After you create the semaphore with a call to the sem_init or sem_open function, you can use the sem_wait, sem_trywait, and sem_post functions to lock and unlock the semaphore.

Using semaphores to share resources among processes works only if processes unlock a resource immediately after they finish using it. As you code your application, do not attempt to unlock a semaphore you did not previously lock.

To lock a semaphore, you can use either the sem_wait or sem_trywait function. If the semaphore value is greater than zero, the sem_wait function locks the specified semaphore. If the semaphore value is less than or equal to zero, the process is blocked (sleeps) and must wait for another process to release the semaphore and increment the semaphore value.

To be certain that the process is not blocked while waiting for a semaphore to become available, use the sem_trywait function. The sem_trywait function will lock the specified semaphore if, and only if, it can do so without waiting. That is, the specified semaphore must be available at the time of the call to the sem_trywait function. If not, the sem_trywait function returns a -1 and errno is set to EAGAIN.

Example 9-1 locks a semaphore by using the sem_trywait function.

Example 9-1:  Locking a Semaphore


.
.
.
int oflag = 0; /* open an existing semaphore; do not create a new one */
.
.
.
mysemp = sem_open(semname, oflag, mode, value); if (mysemp == (void *)-1) { perror(sem_open() failed "); } sts = sem_trywait(mysemp); if (sts == 0) printf("sem_trywait() succeeded!\n"); else if (errno == EAGAIN) printf("semaphore is locked\n"); else perror("sem_trywait() failure");

The sem_post function unlocks the specified semaphore. Any process with access to the semaphore can call the sem_post function and unlock a semaphore. If more than one process is waiting for the semaphore, the highest priority process is allowed access to the semaphore first.


9.2.3    Priority Inversion with Semaphores

Process priority inversion can occur when using semaphores to lock a resource shared by processes of different priorities. If a low-priority process locks a semaphore to control access to a resource and a higher-priority process is waiting for the same resources, the higher-priority process is delayed if the semaphore value is equal to or less than zero. If the lower-priority process is then preempted by a medium-priority process, the higher-priority process is further delayed. In this situation, the higher-priority process is delayed while waiting for a resource locked by lower-priority processes, and the result is priority inversion.

Since semaphores are global in nature and lack owners, there is no mechanism for priority inheritance with semaphores. Therefore, semaphore locks are separate from process priorities. Be careful when designing the use of semaphores in your application.


9.2.4    Closing a Semaphore

When an application is finished using an unnamed semaphore, it should destroy the semaphore with a call to the sem_destroy function. For named semaphores, the application should deallocate the semaphore with a call to the sem_close function. The semaphore name is disassociated from the process. A named semaphore is removed using the sem_unlink function, which takes effect once all processes using the semaphore have deallocated the semaphore with calls to sem_close. If needed, the semaphore can be reopened for use through a call to the sem_open function. Since semaphores are persistent, the state of the semaphore is preserved, even though the semaphore is closed. When you reopen the semaphore, it will be in the state it was when it was closed, unless altered by another process.

As with other interprocess communication methods, you can set up a signal handler to remove the semaphore as one of the tasks performed by the last process in your application.

When the controlling process is finished using an unnamed semaphore, remove the semaphore from memory as follows:

/*
 * Removing unnamed semaphore
 */

.
.
.
sts = sem_destroy(&mysem);

When the controlling process is finished using a named semaphore, close and unlink the semaphore as follows:

/*
 * Closing named semaphore and then unlinking it
 */

.
.
.
sts = sem_close(mysemp); sts = sem_unlink(semname);


9.3    Semaphore Example

It is important that two processes not write to the same area of shared memory at the same time. Semaphores protect access to resources such as shared memory. Before writing to a shared memory region, a process can lock the semaphore to prevent another process from accessing the region until the write operation is completed. When the process is finished with the shared memory region, the process unlocks the semaphore and frees the shared memory region for use by another process.

Example 9-2 consists of two programs, both of which open the shared-memory object. The two processes, writer and reader, use semaphores to ensure that they have exclusive, alternating access to a shared memory region.

The writer.c program creates the semaphore with a call to the sem_open function. The reader.c program opens the semaphore previously created by the writer.c program. Because the writer.c program creates the semaphore, writer.c needs to be executed before reader.c.

Example 9-2:  Using Semaphores and Shared Memory

/*
** These examples use semaphores to ensure that writer and reader
** processes have exclusive, alternating access to the shared-memory region.
*/

/**********  writer.c  ***********/

#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

char shm_fn[] = "my_shm";
char sem_fn[] = "my_sem";

/**** WRITER ****/

main(){
  caddr_t shmptr;
  unsigned int mode;
  int shmdes, index;
  sem_t *semdes;
  int SHM_SIZE;

  mode = S_IRWXU|S_IRWXG;

  /* Open the shared memory object */

  if ( (shmdes = shm_open(shm_fn,O_CREAT|O_RDWR|O_TRUNC, mode)) == -1 ) {
     perror("shm_open failure");
     exit();
   }

  /* Preallocate a shared memory area */

  SHM_SIZE = sysconf(_SC_PAGE_SIZE);

  if(ftruncate(shmdes, SHM_SIZE) == -1){
    perror("ftruncate failure");
    exit();
  }

  if((shmptr = mmap(0, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED,
                shmdes,0)) == (caddr_t) -1){
    perror("mmap failure");
    exit();
  }

  /* Create a semaphore in locked state */

 sem_des = sem_open(sem_fn, O_CREAT, 0644, 0);

 if(sem_des == (void*)-1){
   perror("sem_open failure");
   exit();
 }

    /* Access to the shared memory area */

    for(index = 0; index < 100; index++){
       printf("write %d into the shared memory shmptr[%d]\n", index*2, index);
       shmptr[index]=index*2;
       }

  /* Release the semaphore lock */

  sem_post(semdes);
  munmap(shmptr, SHM_SIZE);

  /* Close the shared memory object */

  close(shmdes);

  /* Close the Semaphore */

  sem_close(semdes);

  /* Delete the shared memory object */

  shm_unlink(shm_fn);
}

/*******************************************************************
*******************************************************************/

/**********  reader.c  ***********/

#include <sys/types.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

char shm_fn[] = "my_shm";
char sem_fn[] = "my_sem";

/**** READER ****/

main(){
  caddr_t shmptr;
  int shmdes, index;
  sem_t *semdes;
  int SHM_SIZE;

  /* Open the shared memory object */

  SHM_SIZE = sysconf(_SC_PAGE_SIZE);

  if ( (shmdes = shm_open(shm_fn, O_RDWR, 0)) == -1 ) {
     perror("shm_open failure");
     exit();
   }

  if((shmptr = mmap(0, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED,
               shmdes,0)) == (caddr_t) -1){
     perror("mmap failure");
    exit();
  }

 /* Open the Semaphore */

 semdes = sem_open(sem_fn, 0, 0644, 0);

 if(semdes == (void*) -1){
   perror("sem_open failure");
   exit();
 }

 /* Lock the semaphore */

 if(!sem_wait(semdes)){

  /* Access to the shared memory area */

   for(index = 0; index < 100; index++)
        printf("The shared memory shmptr[%d] = %d\n", index,shmptr[index]);

  /* Release the semaphore lock */

   sem_post(semdes);
  }

  munmap(shmptr, SHM_SIZE);

  /* Close the shared memory object */

  close(shmdes);

  /* Close the Semaphore */

  sem_close(semdes);
  sem_unlink(sem_fn);
}