Team-FLY |
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 . struct ipc_perm sem_perm; /* operation permission structure */ unsigned short sem_nsems; /* number of semaphores in the set */ time_t sem_otime; /* time of last semop */ time_t sem_ctime; /* time of last semctl */ Each semaphore element has two queues associated with it ”a 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 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. SYNOPSIS #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); POSIX:XSI 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 .
If a process attempts to create a semaphore that already exists, it receives a handle to the existing semaphore unless the semflg value includes both IPC_CREAT and IPC_EXCL . In the latter case, semget fails and sets errno equal to EEXIST . Example 15.3The following code segment creates a new semaphore set containing three semaphore elements. #define PERMS (S_IRUSR S_IWUSR) int semid; if ((semid = semget(IPC_PRIVATE, 3, PERMS)) == -1) perror("Failed to create new private semaphore"); 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 . #define PERMS (S_IRUSR S_IWUSR S_IRGRP S_IWGRP S_IROTH S_IWOTH) #define KEY ((key_t)99887) int semid; if ((semid = semget(KEY, 1, PERMS IPC_CREAT)) == -1) perror ("Failed to access semaphore with key 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 . #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/sem.h> #include <sys/stat.h> #define PERMS (S_IRUSR S_IWUSR S_IRGRP S_IWGRP S_IROTH S_IWOTH) #define SET_SIZE 2 int main(int argc, char *argv[]) { key_t mykey; int semid; if (argc != 3) { fprintf(stderr, "Usage: %s pathname id\n", argv[0]); return 1; } if ((mykey = ftok(argv[1], atoi(argv[2]))) == (key_t)-1) { fprintf(stderr, "Failed to derive key from filename %s:%s\n", argv[1], strerror(errno)); return 1; } if ((semid = semget(mykey, SET_SIZE, PERMS IPC_CREAT)) == -1) { fprintf(stderr, "Failed to create semaphore with key %d:%s\n", (int)mykey, strerror(errno)); return 1; } printf("semid = %d\n", semid); return 0; } 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 . SYNOPSIS #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); POSIX:XSI 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 .
Table 15.2 gives the POSIX:XSI values for the cmd parameter of semctl . Table 15.2. POSIX:XSI values for the cmd parameter of semctl .
Several of these commands, such as GETALL and SETALL , require an arg parameter to read or store results. The arg parameter is of type union semun , which must be defined in programs that use it, as follows . union semun { int val; struct semid_ds *buf; unsigned short *array; } arg; Example 15.5 initelement.cThe initelement function sets the value of the specified semaphore element to semvalue . #include <sys/sem.h> int initelement(int semid, int semnum, int semvalue) { union semun { int val; struct semid_ds *buf; unsigned short *array; } arg; arg.val = semvalue; return semctl(semid, semnum, SETVAL, arg); } 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 . #include <sys/sem.h> int removesem(int semid) { return semctl(semid, 0, IPC_RMID); } 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. SYNOPSIS #include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops); POSIX:XSI If successful, semop returns 0. If unsuccessful, semop returns “1 and sets errno . The following table lists the mandatory errors for semop .
The semop function performs all the operations specified in sops array atomically on a single semaphore set. If any of the individual element operations would cause the process to block, the process blocks and none of the operations are performed. The struct sembuf structure, which specifies a semaphore element operation, includes the following members.
The sem_op element operations are values specifying the amount by which the semaphore value is to be changed.
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? struct sembuf myopbuf = {1, -1, 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. #include <sys/sem.h> void setsembuf(struct sembuf *s, int num, int op, int flg) { s->sem_num = (short)num; s->sem_op = (short)op; s->sem_flg = (short)flg; return; } 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. struct sembuf myop[2]; setsembuf(myop, 0, 1, 0); setsembuf(myop + 1, 1, 2, 0); if (semop(semid, myop, 2) == -1) perror("Failed to perform semaphore operation"); 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. struct sembuf get_tapes[2]; struct sembuf release_tapes[2]; setsembuf(&(get_tapes[0]), 0, -1, 0); setsembuf(&(get_tapes[1]), 1, -1, 0); setsembuf(&(release_tapes[0]), 0, 1, 0); setsembuf(&(release_tapes[1]), 1, 1, 0); Process 1: semop(S, get_tapes, 1); <use tape A> semop(S, release_tapes, 1); Process 2: semop(S, get_tapes, 2); <use tapes A and B> semop(S, release_tapes, 2); Process 3: semop(S, get_tapes + 1, 1); <use tape B> semop(S, release_tapes + 1, 1); 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 . #include <errno.h> #include <sys/sem.h> int r_semop(int semid, struct sembuf *sops, int nsops) { while (semop(semid, sops, nsops) == -1) if (errno != EINTR) return -1; return 0; } Program 15.3 modifies 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 . #include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/sem.h> #include <sys/stat.h> #include <sys/wait.h> #include "restart.h" #define BUFSIZE 1024 #define PERMS (S_IRUSR S_IWUSR) int initelement(int semid, int semnum, int semvalue); int r_semop(int semid, struct sembuf *sops, int nsops); int removesem(int semid); void setsembuf(struct sembuf *s, int num, int op, int flg); void printerror(char *msg, int error) { fprintf(stderr, "[%ld] %s: %s\n", (long)getpid(), msg, strerror(error)); } int main (int argc, char *argv[]) { char buffer[MAX_CANON]; char *c; pid_t childpid; int delay; int error; int i, j, n; int semid; struct sembuf semsignal[1]; struct sembuf semwait[1]; if ((argc != 3) ((n = atoi(argv[1])) <= 0) ((delay = atoi(argv[2])) < 0)) { fprintf (stderr, "Usage: %s processes delay\n", argv[0]); return 1; } /* create a semaphore containing a single element */ if ((semid = semget(IPC_PRIVATE, 1, PERMS)) == -1) { perror("Failed to create a private semaphore"); return 1; } setsembuf(semwait, 0, -1, 0); /* decrement element 0 */ setsembuf(semsignal, 0, 1, 0); /* increment element 0 */ if (initelement(semid, 0, 1) == -1) { perror("Failed to initialize semaphore element to 1"); if (removesem(semid) == -1) perror("Failed to remove failed semaphore"); return 1; } for (i = 1; i < n; i++) if (childpid = fork()) break; snprintf(buffer, BUFSIZE, "i:%d PID:%ld parent PID:%ld child PID:%ld\n", i, (long)getpid(), (long)getppid(), (long)childpid); c = buffer; /******************** entry section ************************************/ if (((error = r_semop(semid, semwait, 1)) == -1) && (i > 1)) { printerror("Child failed to lock semid", error); return 1; } else if (!error) { /***************** start of critical section ************************/ while (*c != '#include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/sem.h> #include <sys/stat.h> #include <sys/wait.h> #include "restart.h" #define BUFSIZE 1024 #define PERMS (S_IRUSR S_IWUSR) int initelement(int semid, int semnum, int semvalue); int r_semop(int semid, struct sembuf *sops, int nsops); int removesem(int semid); void setsembuf(struct sembuf *s, int num, int op, int flg); void printerror (char *msg, int error) { fprintf(stderr, "[%ld] %s: %s\n", (long)getpid(), msg, strerror(error)); } int main (int argc, char *argv[]) { char buffer[MAX_CANON]; char *c; pid_t childpid; int delay; int error; int i, j, n; int semid; struct sembuf semsignal[1]; struct sembuf semwait[1]; if ((argc != 3) ((n = atoi(argv[1])) <= 0) ((delay = atoi(argv[2])) < 0)) { fprintf (stderr, "Usage: %s processes delay\n", argv[0]); return 1; } /* create a semaphore containing a single element */ if ((semid = semget(IPC_PRIVATE, 1, PERMS)) == -1) { perror("Failed to create a private semaphore"); return 1; } setsembuf(semwait, 0, -1, 0); /* decrement element 0 */ setsembuf(semsignal, 0, 1, 0); /* increment element 0 */ if (initelement(semid, 0, 1) == -1) { perror("Failed to initialize semaphore element to 1"); if (removesem(semid) == -1) perror("Failed to remove failed semaphore"); return 1; } for (i = 1; i < n; i++) if (childpid = fork()) break; snprintf (buffer, BUFSIZE, "i:%d PID:%ld parent PID:%ld child PID:%ld\n", i, (long)getpid(), (long)getppid(), (long)childpid); c = buffer; /******************** entry section ************************************/ if (((error = r_semop(semid, semwait, 1)) == -1) && (i > 1)) { printerror("Child failed to lock semid", error); return 1; } else if (!error) { /***************** start of critical section ************************/ while (*c != '\0') { fputc (*c, stderr); c++; for (j = 0; j < delay; j++) ; } /***************** exit section ************************************/ if ((error = r_semop(semid, semsignal, 1)) == -1) printerror("Failed to unlock semid", error); } /******************** remainder section *******************************/ if ((r_wait(NULL) == -1) && (errno != ECHILD)) printerror("Failed to wait", errno); if ((i == 1) && ((error = removesem(semid)) == -1)) { printerror("Failed to clean up", error); return 1; } return 0; }') { fputc(*c, stderr); c++; for (j = 0; j < delay; j++) ; } /***************** exit section ************************************/ if ((error = r_semop(semid, semsignal, 1)) == -1) printerror("Failed to unlock semid", error); } /******************** remainder section *******************************/ if ((r_wait(NULL) == -1) && (errno != ECHILD)) printerror("Failed to wait", errno); if ((i == 1) && ((error = removesem(semid)) == -1)) { printerror("Failed to clean up", error); return 1; } return 0; } 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 . #include <errno.h> #include <signal.h> #include <stdio.h> #include <time.h> #include <sys/sem.h> #include <sys/stat.h> #define PERMS (S_IRUSR S_IWUSR) #define TEN_MILLION 10000000L int initelement(int semid, int semnum, int semvalue); int initsemset(key_t mykey, int value, sig_atomic_t *readyp) { int semid; struct timespec sleeptime; sleeptime.tv_sec = 0; sleeptime.tv_nsec = TEN_MILLION; semid = semget(mykey, 2, PERMS IPC_CREAT IPC_EXCL); if ((semid == -1) && (errno != EEXIST)) /* real error, so return */ return -1; if (semid >= 0) { /* we created the semaphore, so initialize it */ if (initelement(semid, 0, value) == -1) return -1; *readyp = 1; return semid; } if ((semid = semget(mykey, 2, PERMS)) == -1) /* just access it */ return -1; while (*readyp == 0) /* wait for initialization */ nanosleep(&sleeptime, NULL); return semid; } |
Team-FLY |