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:
Global variables, such as file variables, pointers, counters, and data structures. Protecting these variables prevents simultaneous access by more than one process, such as reading information as it is being written by another process.
Hardware resources, such as disk and tape drives. Hardware resources require controlled access because simultaneous access can result in corrupted data.
This chapter includes the following sections:
Overview of Semaphores, Section 9.1
The Semaphore Interface, Section 9.2
Semaphore Example, Section 9.3
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.
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.
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.
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.
.
.
.
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.
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.
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);
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
.
/* ** 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); }