5.2 Processes Semaphores

As noted in the previous section, processes must coordinate access to shared memory. As we discussed in Section 4.4.5, "Semaphores for Threads," in Chapter 4, "Threads," semaphores are counters that permit synchronizing multiple threads. Linux provides a distinct alternate implementation of semaphores that can be used for synchronizing processes (called process semaphores or sometimes System V semaphores). Process semaphores are allocated, used, and deallocated like shared memory segments. Although a single semaphore is sufficient for almost all uses, process semaphores come in sets. Throughout this section, we present system calls for process semaphores, showing how to implement single binary semaphores using them.

5.2.1 Allocation and Deallocation

The calls semget and semctl allocate and deallocate semaphores, which is analogous to shmget and shmctl for shared memory. Invoke semget with a key specifying a semaphore set, the number of semaphores in the set, and permission flags as for shmget; the return value is a semaphore set identifier. You can obtain the identifier of an existing semaphore set by specifying the right key value; in this case, the number of semaphores can be zero.

Semaphores continue to exist even after all processes using them have terminated. The last process to use a semaphore set must explicitly remove it to ensure that the operating system does not run out of semaphores. To do so, invoke semctl with the semaphore identifier, the number of semaphores in the set, IPC_RMID as the third argument, and any union semun value as the fourth argument (which is ignored). The effective user ID of the calling process must match that of the semaphore's allocator (or the caller must be root). Unlike shared memory segments, removing a semaphore set causes Linux to deallocate immediately.

Listing 5.2 presents functions to allocate and deallocate a binary semaphore.

Listing 5.2 (sem_all_deall.c) Allocating and Deallocating a Binary Semaphore
#include <sys/ipc.h> 
#include <sys/sem.h> 
#include <sys/types.h> 
 
/* We must define union semun ourselves. */ 
 
union semun {
    int val; 
    struct semid_ds *buf; 
    unsigned short int *array; 
    struct seminfo *__buf; 
}; 
 
/* Obtain a binary semaphore's ID, allocating if necessary. */ 
 
int binary_semaphore_allocation (key_t key, int sem_flags) 
{
    return semget (key, 1, sem_flags); 
} 
 
/* Deallocate a binary semaphore. All users must have finished their 
   use. Returns -1 on failure. */ 
 
int binary_semaphore_deallocate (int semid) 
{
     union semun ignored_argument; 
     return semctl (semid, 1, IPC_RMID, ignored_argument); 
} 

5.2.2 Initializing Semaphores

Allocating and initializing semaphores are two separate operations. To initialize a semaphore, use semctl with zero as the second argument and SETALL as the third argument. For the fourth argument, you must create a union semun object and point its array field at an array of unsigned short values. Each value is used to initialize one semaphore in the set.

Listing 5.3 presents a function that initializes a binary semaphore.

Listing 5.3 (sem_init.c) Initializing a Binary Semaphore
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
/* We must define union semun ourselves. */ 
 
union semun {
    int val; 
    struct semid_ds *buf; 
    unsigned short int *array; 
    struct seminfo *__buf; 
}; 
 
/* Initialize a binary semaphore with a value of 1. */ 
 
int binary_semaphore_initialize (int semid) 
{
    union semun argument; 
    unsigned short values[1]; 
    values[0] = 1; 
    argument.array = values; 
    return semctl (semid, 0, SETALL, argument); 
} 

5.2.3 Wait and Post Operations

Each semaphore has a non-negative value and supports wait and post operations. The semop system call implements both operations. Its first parameter specifies a semaphore set identifier. Its second parameter is an array of struct sembuf elements, which specify the operations you want to perform. The third parameter is the length of this array.

The fields of struct sembuf are listed here:

·         sem_num is the semaphore number in the semaphore set on which the operation is performed.

·         sem_op is an integer that specifies the semaphore operation.

If sem_op is a positive number, that number is added to the semaphore value immediately.

If sem_op is a negative number, the absolute value of that number is subtracted from the semaphore value. If this would make the semaphore value negative, the call blocks until the semaphore value becomes as large as the absolute value of sem_op (because some other process increments it).

If sem_op is zero, the operation blocks until the semaphore value becomes zero.

·         sem_flg is a flag value. Specify IPC_NOWAIT to prevent the operation from blocking; if the operation would have blocked, the call to semop fails instead. If you specify SEM_UNDO, Linux automatically undoes the operation on the semaphore when the process exits.

Listing 5.4 illustrates wait and post operations for a binary semaphore.

Listing 5.4 (sem_pv.c) Wait and Post Operations for a Binary Semaphore
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
 
/* Wait on a binary semaphore. Block until the semaphore value is positive, then 
   decrement it by 1. */ 
 
int binary_semaphore_wait (int semid) 
{
    struct sembuf operations[1]; 
    /* Use the first (and only) semaphore. */ 
    operations[0].sem_num = 0; 
    /* Decrement by 1. */ 
    operations[0].sem_op = -1; 
    /* Permit undo'ing. */ 
    operations[0].sem_flg = SEM_UNDO; 
 
    return semop (semid, operations, 1); 
} 
 
/* Post to a binary semaphore: increment its value by 1. 
   This returns immediately. */ 
 
int binary_semaphore_post (int semid) 
{
    struct sembuf operations[1]; 
    /* Use the first (and only) semaphore. */ 
    operations[0].sem_num = 0; 
    /* Increment by 1. */ 
    operations[0].sem_op = 1; 
    /* Permit undo'ing. */ 
    operations[0].sem_flg = SEM_UNDO; 
 
    return semop (semid, operations, 1); 
} 

Specifying the SEM_UNDO flag permits dealing with the problem of terminating a process while it has resources allocated through a semaphore. When a process terminates, either voluntarily or involuntarily, the semaphore's values are automatically adjusted to "undo" the process's effects on the semaphore. For example, if a process that has decremented a semaphore is killed, the semaphore's value is incremented.

5.2.4 Debugging Semaphores

Use the command ipcs -s to display information about existing semaphore sets. Use the ipcrm sem command to remove a semaphore set from the command line. For example, to remove the semaphore set with identifier 5790517, use this line:

 
% ipcrm sem 5790517