parameter of semctl.

Team-FLY

15.2 POSIX:XSI Semaphore Sets

A 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.

  • A nonnegative integer representing the value of the semaphore element ( semval )

  • The process ID of the last process to manipulate the semaphore element ( sempid )

  • The number of processes waiting for the semaphore element value to increase ( semncnt )

  • The number of processes waiting for the semaphore element value to equal 0 ( semzcnt )

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 creation

The 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 .

errno

cause

EACCES

semaphore exists for key but permission not granted

EEXIST

semaphore exists for key but ( ( semflg & IPC_CREAT) && (semflg & IPC_EXCL) ) != 0

EINVAL

nsems <= 0 or greater than system limit, or nsems doesn't agree with semaphore set size

ENOENT

semaphore does not exist for key and (semflg & IPC_CREAT) == 0

ENOSPC

systemwide limit on semaphores would be exceeded

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.3

The 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.4

The 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.c

A 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 control

Each 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 .

errno

cause

EACCES

operation is denied to the caller

EINVAL

value of semid or of cmd is invalid, or value of semnum is negative or too large

EPERM

value of cmd is IPC_RMID or IPC_SET and caller does not have required privileges

ERANGE

cmd is SETVAL or SETALL and value to be set is out of range

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 .

cmd

description

GETALL

return values of the semaphore set in arg.array

GETVAL

return value of a specific semaphore element

GETPID

return process ID of last process to manipulate element

GETNCNT

return number of processes waiting for element to increment

GETZCNT

return number of processes waiting for element to become 0

IPC_RMID

remove semaphore set identified by semid

IPC_SET

set permissions of the semaphore set from arg.buf

IPC_STAT

copy members of semid_ds of semaphore set semid into arg.buf

SETALL

set values of semaphore set from arg.array

SETVAL

set value of a specific semaphore element to arg.val

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.c

The 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.c

The 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 operations

The 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 .

errno

cause

E2BIG

value of nsops is too big

EACCES

operation is denied to the caller

EAGAIN

operation would block the process but (sem_flg & IPC_NOWAIT) != 0

EFBIG

value of sem_num for one of the sops entries is less than 0 or greater than the number elements in the semaphore set

EIDRM

semaphore identifier semid has been removed from the system

EINTR

semop was interrupted by a signal

EINVAL

value of semid is invalid, or number of individual semaphores for a SEM_UNDO has exceeded limit

ENOSPC

limit on processes requesting SEM_UNDO has been exceeded

ERANGE

operation would cause an overflow of a semval or semadj value

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.

short sem_num

number of the semaphore element

short sem_op

particular element operation to be performed

short sem_flg

flags to specify options for the operation

The sem_op element operations are values specifying the amount by which the semaphore value is to be changed.

  • If sem_op is an integer greater than zero, semop adds the value to the corresponding semaphore element value and awakens all processes that are waiting for the element to increase.

  • If sem_op is 0 and the semaphore element value is not 0, semop blocks the calling process (waiting for 0) and increments the count of processes waiting for a zero value of that element.

  • If sem_op is a negative number, semop adds the sem_op value to the corresponding semaphore element value provided that the result would not be negative. If the operation would make the element value negative, semop blocks the process on the event that the semaphore element value increases. If the resulting value is 0, semop wakes the processes waiting for 0.

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.7

What 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.c

The 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.9

The 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.10

Suppose 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.c

A 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.c

A 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.c

A 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


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net