This chapter describes System V semaphores. Unlike the IPC mechanisms described in previous chapters, System V semaphores are not used to transfer data between processes. Instead, they allow processes to synchronize their actions. One common use of a semaphore is to synchronize access to a block of shared memory, in order to prevent one process from accessing the shared memory at the same time as another process is updating it.
A semaphore is a kernel-maintained integer whose value is restricted to being greater than or equal to 0. Various operations (i.e., system calls) can be performed on a semaphore, including the following:
setting the semaphore to an absolute value;
adding a number to the current value of the semaphore;
subtracting a number from the current value of the semaphore; and
waiting for the semaphore value to be equal to 0.
The last two of these operations may cause the calling process to block. When lowering a semaphore value, the kernel blocks any attempt to decrease the value below 0. Similarly, waiting for a semaphore to equal 0 blocks the calling process if the semaphore value is not currently 0. In both cases, the calling process remains blocked until some other process alters the semaphore to a value that allows the operation to proceed, at which point the kernel wakes the blocked process. Figure 47-1 shows the use of a semaphore to synchronize the actions of two processes that alternately move the semaphore value between 0 and 1.
In terms of controlling the actions of a process, a semaphore has no meaning in and of itself. Its meaning is determined only by the associations given to it by the processes using the semaphore. Typically, processes agree on a convention that associates a semaphore with a shared resource, such as a region of shared memory. Other uses of semaphores are also possible, such as synchronization between parent and child processes after fork(). (In Avoiding Race Conditions by Synchronizing with Signals, we looked at the use of signals to accomplish the same task.)
The general steps for using a System V semaphore are the following:
Create or open a semaphore set using semget().
Initialize the semaphores in the set using the semctl() SETVAL
or SETALL
operation. (Only one process should do this.)
Perform operations on semaphore values using semop(). The processes using the semaphore typically use these operations to indicate acquisition and release of a shared resource.
When all processes have finished using the semaphore set, remove the set using the semctl() IPC_RMID
operation. (Only one process should do this.)
Most operating systems provide some type of semaphore primitive for use in application programs. However, System V semaphores are rendered unusually complex by the fact that they are allocated in groups called semaphore sets. The number of semaphores in a set is specified when the set is created using the semget() system call. While it is common to operate on a single semaphore at a time, the semop() system call allows us to atomically perform a group of operations on multiple semaphores in the same set.
Because System V semaphores are created and initialized in separate steps, race conditions can result if two processes try to perform these steps at the same time. Describing this race condition and how to avoid it requires that we describe semctl() before describing semop(), which means that there is quite a lot of material to cover before we have all of the details required to fully understand semaphores.
In the meantime, we provide Example 47-1 as a simple example of the use of the various semaphore system calls. This program operates in two modes:
Given a single integer command-line argument, the program creates a new semaphore set containing a single semaphore, and initializes the semaphore to the value supplied in the command-line argument. The program displays the identifier of the new semaphore set.
Given two command-line arguments, the program interprets them as (in order) the identifier of an existing semaphore set and a value to be added to the first semaphore (numbered 0) in that set. The program carries out the specified operation on that semaphore. To enable us to monitor the semaphore operation, the program prints messages before and after the operation. Each of these messages begins with the process ID, so that we can distinguish the output of multiple instances of the program.
The following shell session log demonstrates the use of the program in Example 47-1. We begin by creating a semaphore that is initialized to 0:
$ ./svsem_demo 0
Semaphore ID = 98307 ID of new semaphore set
We then execute a background command that tries to decrease the semaphore value by 2:
$ ./svsem_demo 98307 -2 &
23338: about to semop at 10:19:42
[1] 23338
This command blocked, because the value of the semaphore can’t be decreased below 0. Now, we execute a command that adds 3 to the semaphore value:
$ ./svsem_demo 98307 +3
23339: about to semop at 10:19:55
23339: semop completed at 10:19:55
23338: semop completed at 10:19:55
[1]+ Done ./svsem_demo 98307 -2
The semaphore increment operation succeeded immediately, and caused the semaphore decrement operation in the background command to proceed, since that operation could now be performed without leaving the semaphore’s value below 0.
Example 47-1. Creating and operating on System V semaphores
svsem/svsem_demo.c
#include <sys/types.h> #include <sys/sem.h> #include <sys/stat.h> #include "curr_time.h" /* Declaration of currTime() */ #include "semun.h" /* Definition of semun union */ #include "tlpi_hdr.h" int main(int argc, char *argv[]) { int semid; if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0) usageErr("%s init-value\n" " or: %s semid operation\n", argv[0], argv[0]); if (argc == 2) { /* Create and initialize semaphore */ union semun arg; semid = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR); if (semid == -1) errExit("semid"); arg.val = getInt(argv[1], 0, "init-value"); if (semctl(semid, /* semnum= */ 0, SETVAL, arg) == -1) errExit("semctl"); printf("Semaphore ID = %d\n", semid); } else { /* Perform an operation on first semaphore */ struct sembuf sop; /* Structure defining operation */ semid = getInt(argv[1], 0, "semid"); sop.sem_num = 0; /* Specifies first semaphore in set */ sop.sem_op = getInt(argv[2], 0, "operation"); /* Add, subtract, or wait for 0 */ sop.sem_flg = 0; /* No special options for operation */ printf("%ld: about to semop at %s\n", (long) getpid(), currTime("%T")); if (semop(semid, &sop, 1) == -1) errExit("semop"); printf("%ld: semop completed at %s\n", (long) getpid(), currTime("%T")); } exit(EXIT_SUCCESS); }svsem/svsem_demo.c