15.2 POSIX:XSI Semaphore SetsA POSIX:XSI semaphore consists of an array of semaphore elements. The semaphore elements are similar, but not identical, to the classical integer semaphores proposed by Dijsktra, as described in Chapter 14. A process can perform operations on the entire set in a single call. Thus, POSIX:XSI semaphores are capable of AND synchronization, as described in Section 14.2. We refer to POSIX:XSI semaphores as semaphore sets to distinguish them from the POSIX:SEM semaphores described in Chapter 14.Each semaphore element includes at least the following information.
The major data structure for semaphores is semid_ds, which is defined in sys/sem.h and has the following members.
Each semaphore element has two queues associated with ita queue of processes waiting for the value to equal 0 and a queue of processes waiting for the value to increase. The semaphore element operations allow a process to block until a semaphore element value is 0 or until it increases to a specific value greater than zero. 15.2.1 Semaphore creationThe semget function returns the semaphore identifier associated with the key parameter. The semget function creates the identifier and its associated semaphore set if either the key is IPC_PRIVATE or semflg & IPC_CREAT is nonzero and no semaphore set or identifier is already associated with key. The nsems parameter specifies the number of semaphore elements in the set. The individual semaphore elements within a semaphore set are referenced by the integers 0 through nsems - 1. Semaphores have permissions specified by the semflg argument of semget. Set permission values in the same way as described in Section 4.3 for files, and change the permissions by calling semctl. Semaphore elements should be initialized with semctl before they are used.
If successful, semget returns a nonnegative integer corresponding to the semaphore identifier. If unsuccessful, the semget function returns 1 and sets errno. The following table lists the mandatory errors for semget.
Example 15.3The following code segment creates a new semaphore set containing three semaphore elements.
This semaphore can only be read or written by the owner.The IPC_PRIVATE key guarantees that semget creates a new semaphore. To get a new semaphore set from a made-up key or a key derived from a pathname, the process must specify by using the IPC_CREAT flag that it is creating a new semaphore. If both ICP_CREAT and IPC_EXCL are specified, semget returns an error if the semaphore already exists. Example 15.4The following code segment accesses a semaphore set with a single element identified by the key value 99887.
The IPC_CREAT flag ensures that if the semaphore set doesn't exist, semget creates it. The permissions allow all users to access the semaphore set.Giving a specific key value allows cooperating processes to agree on a common semaphore set. If the semaphore already exists, semget returns a handle to the existing semaphore. If you replace the semflg argument of semget with PERMS | IPC_CREAT | IPC_EXCL, semget returns an error when the semaphore already exists.Program 15.1 demonstrates how to identify a semaphore set by using a key generated from a pathname and an ID, which are passed as command-line arguments. If semfrompath executes successfully, the semaphores will exist after the program exits. You will need to call the ipcrm command to get rid of them. Program 15.1 semfrompath.cA program that creates a semaphore from a pathname key.
15.2.2 Semaphore controlEach element of a semaphore set must be initialized with semctl before it is used. The semctl function provides control operations in element semnum for the semaphore set semid. The cmd parameter specifies the type of operation. The optional fourth parameter, arg, depends on the value of cmd.
If successful, semctl returns a nonnegative value whose interpretation depends on cmd. The GETVAL, GETPID, GETNCNT and GETZCNT values of cmd cause semctl to return the value associated with cmd. All other values of cmd cause semctl to return 0 if successful. If unsuccessful, semctl returns 1 and sets errno. The following table lists the mandatory errors for semctl.
Example 15.5 initelement.cThe initelement function sets the value of the specified semaphore element to semvalue.
The semid and semnum parameters identify the semaphore set and the element within the set whose value is to be set to semvalue.If successful, initelement returns 0. If unsuccessful, initelement returns 1 with errno set (since semctl sets errno). Example 15.6 removesem.cThe removesem function deletes the semaphore specified by semid.
If successful, removesem returns 0. If unsuccessful, removesem returns 1 with errno set (since semctl sets errno). 15.2.3 POSIX semaphore set operationsThe semop function atomically performs a user-defined collection of semaphore operations on the semaphore set associated with identifier semid. The sops parameter points to an array of element operations, and the nsops parameter specifies the number of element operations in the sops array.
If successful, semop returns 0. If unsuccessful, semop returns 1 and sets errno. The following table lists the mandatory errors for semop.
The description of semop assumes that sem_flg is 0 for all the element operations. If sem_flg & IPC_NOWAIT is true, the element operation never causes the semop call to block. If a semop returns because it would have blocked on that element operation, it returns 1 with errno set to EAGAIN. If sem_flg & SEM_UNDO is true, the function also modifies the semaphore adjustment value for the process. This adjustment value allows the process to undo its effect on the semaphore when it exits. You should read the man page carefully regarding the interaction of semop with various settings of the flags. Example 15.7What is wrong with the following code to declare myopbuf and initialize it so that sem_num is 1, sem_op is 1, and sem_flg is 0?
Answer:The direct assignment assumes that the members of struct sembuf appear in the order sem_num, sem_op and sem_flg. You may see this type of initialization in legacy code and it may work on your system, but try to avoid it. Although the POSIX:XSI Extension specifies that the struct sembuf structure has sem_num, sem_op and sem_flg members, the standard does not specify the order in which these members appear in the definition nor does the standard restrict struct sembuf to contain only these members. Example 15.8 setsembuf.cThe function setsembuf initializes the struct sembuf structure members sem_num, sem_op and sem_flg in an implementation-independent manner.
Example 15.9The following code segment atomically increments element zero of semid by 1 and element one of semid by 2, using setsembuf of Example 15.8.
Example 15.10Suppose a two-element semaphore set, S, represents a tape drive system in which Process 1 uses Tape A, Process 2 uses Tape A and B, and Process 3 uses Tape B. The following pseudocode segment defines semaphore operations that allow the processes to access one or both tape drives in a mutually exclusive manner.
S[0] represents tape A, and S[1] represents tape B. We assume that both elements of S have been initialized to 1.If semop is interrupted by a signal, it returns 1 and sets errno to EINTR. Program 15.2 shows a function that restarts semop if it is interrupted by a signal. Program 15.2 r_semop.cA function that restarts semop after a signal.
Program 14.1 to use POSIX:XSI semaphore sets to protect a critical section. Program 15.3 calls setsembuf (Example 15.8) and removesem (Example 15.6). It restarts semop operations if interrupted by a signal, even though the program does not catch any signals. You should get into the habit of restarting functions that can set errno equal to EINTR.Once the semaphore of Program 15.3 is created, it persists until it is removed. If a child process generates an error, it just exits. If the parent generates an error, it falls through to the wait call and then removes the semaphore. A program that creates a semaphore for its own use should be sure to remove the semaphore before the program terminates. Be careful to remove the semaphore exactly once. Program 15.3 chainsemset.cA modification of Program 14.1 that uses semaphore sets to protect the critical section.
A program calls semget to create or access a semaphore set and calls semctl to initialize it. If one process creates and initializes a semaphore and another process calls semop between the creation and initialization, the results of the execution are unpredictable. This unpredictability is an example of a race condition because the occurrence of the error depends on the precise timing between instructions in different processes. Program 15.3 does not have a race condition because the original parent creates and initializes the semaphore before doing a fork. The program avoids a race condition because only the original process can access the semaphore at the time of creation. One of the major problems with semaphore sets is that the creation and initialization are separate operations and therefore not atomic. Recall that POSIX:SEM named and unnamed semaphores are initialized at the time of creation and do not have this problem.Program 15.4 can be used to create or access a semaphore set containing a single semaphore element. It takes three parameters, a semaphore key, an initial value and a pointer to a variable of type sig_atomic_t that is initialized to 0 and shared among all processes and threads that call this function. If this function is used among threads of a single process, the sig_atomic_t variable could be defined outside a block and statically initialized. Using initsemset among processes requires shared memory. We use Program 15.4 later in the chapter to protect a shared memory segment. The busy-waiting used in initsemset is not as inefficient as it may seem, since it is only used when the thread that creates the semaphore set loses the CPU before it can initialize it. Program 15.4 initsemset.cA function that creates and initializes a semaphore set containing a single semaphore.
|