Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
      Languages 
      Kernels 
      Packages 
      Books 
      Tests 
      OS 
      Forum 
      Математика 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...3415 
 MINIX...3085 
 Solaris...2962 
 LD...2955 
 William Gropp...2272 
 Trees...2141 
 Rodriguez 6...2072 
 C++ Templates 3...1983 
 Kamran Husain...1934 
 Secure Programming for Li...1852 
 Максвелл 5...1762 
 DevFS...1752 
 Part 3...1736 
 Go Web ...1674 
 Ethreal 4...1668 
 Стивенс 9...1664 
 Stein-MacEachern-> Час...1662 
 Arrays...1641 
 Максвелл 1...1636 
 ffmpeg->tutorial...1585 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Chapter 14. Critical Sections and Semaphores

Programs that manage shared resources must execute portions of code called critical sections in a mutually exclusive manner. This chapter discusses how critical sections arise and how to protect their execution by means of semaphores. After presenting an overview of the semaphore abstraction, the chapter describes POSIX named and unnamed semaphores. The closing section outlines a license manager project based on semaphores.

Objectives

  • Learn about semaphores and their properties

  • Experiment with synchronization

  • Explore critical section behavior

  • Use POSIX named and unnamed semaphores

  • Understand semaphore management


14.1 Dealing with Critical Sections

Imagine a computer system in which all users share a single printer and can simultaneously print. How would the output appear? If lines of users' jobs were interspersed, the system would be unusable. Shared devices, such as printers, are called exclusive resources because they must be accessed by one process at a time. Processes must execute the code that accesses these shared resources in a mutually exclusive manner.

A critical section is a code segment that must be executed in a mutually exclusive manner, that is, only one thread of execution can be active in its boundaries. For example, code that modifies a shared variable is considered to be part of a critical section, if other threads of execution might possibly access the shared variable during the modification. The critical section problem refers to the problem of executing critical section code in a safe, fair and symmetric manner.

Program 14.1 contains a modification of Program 3.1 on page 67 to generate a process chain. It prints its message one character at a time. The program takes an extra command-line argument giving a delay after each character is output to make it more likely that the process quantum will expire in the output loop. The call to wait ensures that the original process does not terminate until all children have completed and prevents the shell prompt from appearing in the middle of the output of one of the children.

Exercise 14.1

Explain why the marked section of code in Program 14.1 is a critical section.

Answer:

After falling out of the forking loop, each process outputs an informative message to standard error one character at a time. Since standard error is shared by all processes in the chain, that part of the code is a critical section and should be executed in a mutually exclusive manner. Unfortunately, the critical section of Program 14.1 is not protected, so output from different processes can interleave in a random manner, different for each run.

Exercise 14.2

Run Program 14.1 with different values of the delay parameter. What happens?

Answer:

When the delay parameter is near 0, each process usually outputs its entire line without losing the CPU. Longer delays make it more likely that a process will lose the CPU before completing the entire message. For large enough values of the delay, each process outputs only one character before losing the CPU. Depending on the speed of the machine, you might need to use values of the delay in excess of 1 million for this last case.

Exercise 14.3

Program 3.1 on page 67 uses a single fprintf to standard error to produce the output. Does this have a critical section?

Answer:

Yes. Although the output is in a single C language statement, the compiled code is a sequence of assembly language instructions and the process can lose the CPU anywhere in this sequence. Although this might be less likely to happen in Program 3.1 than in Program 14.1, it is still possible.

Program 14.1 chaincritical.c

A program to generate a chain of processes that write to standard error.

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/wait.h>
 #include "restart.h"
 #define BUFSIZE 1024
 
 int main(int argc, char *argv[]) {
     char buffer[BUFSIZE];
     char *c;
     pid_t childpid = 0;
     int delay;
     volatile int dummy = 0;
     int i, n;
 
     if (argc != 3){   /* check for valid number of command-line arguments */
         fprintf (stderr, "Usage: %s processes delay\n", argv[0]);
         return 1;
     }
     n = atoi(argv[1]);
     delay = atoi(argv[2]);
     for (i = 1; i < n; i++)
         if (childpid = fork())
             break;
     snprintf(buffer, BUFSIZE,
              "i:%d  process ID:%ld  parent ID:%ld  child ID:%ld\n",
              i, (long)getpid(), (long)getppid(), (long)childpid);
 
     c = buffer;
    /********************** start of critical section **********************/
     while (*c != '\0') {
         fputc(*c, stderr);
         c++;
         for (i = 0; i < delay; i++)
             dummy++;
     }
    /********************** end of critical section ************************/
     if (r_wait(NULL) == -1)
         return 1;
     return 0;
 }
 

Each process in Program 14.1 executes the statements in sequential order, but the statements (and hence the output) from the different processes can be arbitrarily interleaved. An analogy to this arbitrary interleaving comes from a deck of cards. Cut a deck of cards. Think of each section of the cut as representing one process. The individual cards in each section represent the statements in the order that the corresponding process executes them. Now shuffle the two sections by interleaving. There are many possibilities for a final ordering, depending on the shuffling mechanics. Similarly, there are many possible interleavings of the statements of two processes because the exact timing of processes relative to each other depends on outside factors (e.g., how many other processes are competing for the CPU or how much time each process spent in previous blocked states waiting for I/O). The challenge for programmers is to develop programs that work for all realizable interleavings of program statements.

Code with synchronized critical sections can be organized into distinct parts. The entry section contains code to request permission to modify a shared variable or other resource. You can think of the entry section as the gatekeeperallowing only one thread of execution to pass through at a time. The critical section usually contains code to access a shared resource or to execute code that is nonreentrant. The explicit release of access provided in the exit section is necessary so that the gatekeeper knows it can allow the next thread of execution to enter the critical section. After releasing access, a thread may have other code to execute, which we separate into the remainder section to indicate that it should not influence decisions by the gatekeeper.

A good solution to the critical section problem requires fairness as well as exclusive access. Threads of execution that are trying to enter a critical section should not be postponed indefinitely. Threads should also make progress. If no thread is currently in the critical section, a waiting thread should be allowed to enter.

Critical sections commonly arise when two processes access a shared resource, such as the example of Program 14.1. Be aware that critical sections can arise in other ways. Code in a signal handler executes asynchronously with the rest of the program, so it can be thought of as logically executing in a separate thread of execution. Variables that are modified in the signal handler and used in the rest of the program must be treated as part of a critical section. In Program 8.6 on page 271, the signal handler and the results function compete for access to buf and buflen. The entry section or gatekeeper is the code in results to block SIGUSR1; the exit section is the code to unblock SIGUSR1 and to restore the original signal mask.

Program 2.3 on page 39 illustrates a related problem that can arise with recursive calls to nonreentrant functions such as strtok. Although this example is not strictly a critical section problem by the definition given above, it has the same characteristics because the single thread of execution changes its execution environment when a function call pushes a new activation record on the stack.


    14.2 Semaphores

    In 1965, E. W. Dijkstra [30] proposed the semaphore abstraction for high-level management of mutual exclusion and synchronization. A semaphore is an integer variable with two atomic operations, wait and signal. Other names for wait are down, P and lock. Other names for signal are up, V, unlock and post.

    If S is greater than zero, wait tests and decrements S in an atomic operation. If S is equal to zero, the wait tests S and blocks the caller in an atomic operation.

    If threads are blocked on the semaphore, then S is equal to zero and signal unblocks one of these waiting threads. If no threads are blocked on the semaphore, signal increments S. In POSIX:SEM terminology, the wait and signal operations are called semaphore lock and semaphore unlock, respectively. We can think of a semaphore as an integer value and a list of processes waiting for a signal operation.

    Example 14.4

    The following pseudocode shows a blocking implementation of semaphores.

     void wait(semaphore_t *sp) {
        if (sp->value > 0)
           sp->value--;
        else {
           <Add this process to sp->list>
           <block>
        }
     }
     
     void signal(semaphore_t *sp) {
        if (sp->list != NULL)
           <remove a process from sp->list and put in ready state>
        else
           sp->value++;
     }
     

    The wait and signal operations must be atomic. An atomic operation is an operation that, once started, completes in a logically indivisible way (i.e., without any other related instructions interleaved). In this context, being atomic means that if a process calls wait, no other process can change the semaphore until the semaphore is decremented or the calling process is blocked. The signal operation is atomic in a similar way. Semaphore implementations use atomic operations of the underlying operating system to ensure correct execution.

    Example 14.5

    The following pseudocode protects a critical section if the semaphore variable S is initially 1.

     wait(&S);                                /* entry section or gatekeeper */
     <critical section>
     signal(&S);                                             /* exit section */
     <remainder section>
     

    Processes using semaphores must cooperate to protect a critical section. The code of Example 14.5 works, provided that all processes call wait(&S) before entering their critical sections and that they call signal(&S) when they leave. If any process fails to call wait(&S) because of a mistake or oversight, the processes may not execute the code of the critical section in a mutually exclusive manner. If a process fails to call signal(&S) when it finishes its critical section, other cooperative processes are blocked from entering their critical sections.

    Exercise 14.6

    What happens if S is initially 0 in the previous example? What happens if S is initially 8? Under what circumstances might initialization to 8 prove useful?

    Answer:

    If S is initially 0, every wait(&S) blocks and a deadlock results unless some other process calls signal for this semaphore. If S is initially 8, at most eight processes execute concurrently in their critical sections. The initialization to 8 might be used when there are eight identical copies of the resource that can be accessed concurrently.

    Example 14.7

    Suppose process 1 must execute statement a before process 2 executes statement b. The semaphore sync enforces the ordering in the following pseudocode, provided that sync is initially 0.

     Process 1 executes:           Process 2 executes:
       a;                            wait(&sync);
       signal(&sync);                b;
     

    Because sync is initially 0, process 2 blocks on its wait until process 1 calls signal.

    Exercise 14.8

    What happens in the following pseudocode if the semaphores S and Q are both initially 1? What about other possible initializations?

     Process 1 executes:           Process 2 executes:
        for( ; ; ) {                  for( ; ; ) {
           wait(&S);                     wait(&Q);
           a;                            b;
           signal(&Q);                   signal(&S);
        }                             }
     

    Answer:

    Either process might execute its wait statement first. The semaphores ensure that a given process is no more than one iteration ahead of the other. If one semaphore is initially 1 and the other 0, the processes proceed in strict alternation. If both semaphores are initially 0, a deadlock occurs.

    Exercise 14.9

    What happens when S is initially 8 and Q is initially 0 in Exercise 14.8? Hint: Think of S as representing buffer slots and Q as representing items in a buffer.

    Answer:

    Process 1 is always between zero and eight iterations ahead of process 2. If the value of S represents empty slots and the value of Q represents items in the slots, process 1 acquires slots and produces items, and process 2 acquires items and produces empty slots. This generalization synchronizes access to a buffer with room for no more than eight items.

    Exercise 14.10

    What happens in the following pseudocode if semaphores S and Q are both initialized to 1?

     Process 1 executes:           Process 2 executes:
        for( ; ; ) {                  for( ; ; ) {
           wait(&Q);                     wait(&S);
           wait(&S);                     wait(&Q);
           a;                            b;
           signal(&S);               signal(&Q);
           signal(&Q);               signal(&S);
        }                             }
     

    Answer:

    The result depends on the order in which the processes get the CPU. It should work most of the time, but if process 1 loses the CPU after executing wait(&Q) and process 2 gets in, both processes block on their second wait call and a deadlock occurs.

    A semaphore synchronizes processes by requiring that the value of the semaphore variable be nonnegative. More general forms of synchronization allow synchronization on arbitrary conditions and have mechanisms for combining synchronization conditions. OR synchronization refers to waiting until any condition in a specified set is satisfied. The use of select or poll to monitor multiple file descriptors for input is a form of OR synchronization. NOT synchronization refers to waiting until some condition in a set is not true. NOT synchronization can be used to enforce priority ordering [76]. AND synchronization refers to waiting until all the conditions in a specified set of conditions are satisfied. AND synchronization can be used for simultaneous control of multiple resources such as that needed for Exercise 14.10. POSIX:XSI semaphore sets described in Chapter 15 are capable of providing AND synchronization.


    14.3 POSIX:SEM Unnamed Semaphores

    A POSIX:SEM semaphore is a variable of type sem_t with associated atomic operations for initializing, incrementing and decrementing its value. The POSIX:SEM Semaphore Extension defines two types of semaphores, named and unnamed. An implementation supports POSIX:SEM semaphores if it defines _POSIX_SEMAPHORES in unistd.h. The difference between unnamed and named semaphores is analogous to the difference between ordinary pipes and named pipes (FIFOs). This section discusses unnamed semaphores. Named semaphores are discussed in Section 14.5.

    Example 14.11

    The following code segment declares a semaphore variable called sem.

     #include <semaphore.h>
     sem_t sem;
     

    The POSIX:SEM Extension does not specify the underlying type of sem_t. One possibility is that sem_t acts like a file descriptor and is an offset into a local table. The table values point to entries in a system table. A particular implementation may not use the file descriptor table model but instead may store information about the semaphore with the sem_t variable. The semaphore functions take a pointer to the semaphore variable as a parameter, so system implementers are free to use either model. You may not make a copy of a sem_t variable and use it in semaphore operations.

    POSIX:SEM semaphores must be initialized before they are used. The sem_init function initializes the unnamed semaphore referenced by sem to value. The value parameter cannot be negative. Our examples use unnamed semaphores with pshared equal to 0, meaning that the semaphore can be used only by threads of the process that initializes the semaphore. If pshared is nonzero, any process that can access sem can use the semaphore. Be aware that simply forking a child after creating the semaphore does not provide access for the child. The child receives a copy of the semaphore, not the actual semaphore.

     SYNOPSIS
     
       #include <semaphore.h>
     
       int sem_init(sem_t *sem, int pshared, unsigned value);
                                                                     POSIX:SEM
     

    If successful, sem_init initializes sem. Interestingly, POSIX does not specify the return value on success, but the rationale mentions that sem_init may be required to return 0 in a future specification. If unsuccessful, sem_init returns 1 and sets errno. The following table lists the mandatory errors for sem_init.

    errno

    cause

    EINVAL

    value is greater than SEM_VALUE_MAX

    ENOSPC

    initialization resource was exhausted, or number of semaphores exceeds SEM_NSEMS_MAX

    EPERM

    caller does not have the appropriate privileges

    Example 14.12

    The following code segment initializes an unnamed semaphore to be used by threads of the process.

     sem_t semA;
     
     if (sem_init(&semA, 0, 1) == -1)
        perror("Failed to initialize semaphore semA");
     

    The sem_destroy function destroys a previously initialized unnamed semaphore referenced by the sem parameter.

     SYNOPSIS
     
         #include <semaphore.h>
     
         int sem_destroy(sem_t *sem);
                                                     POSIX:SEM
     

    If successful, sem_destroy returns 0. If unsuccessful, sem_destroy returns 1 and sets errno. The sem_destroy function sets errno to EINVAL if *sem is not a valid semaphore.

    Example 14.13

    The following code destroys semA.

     sem_t semA;
     
     if (sem_destroy(&semA) == -1)
        perror("Failed to destroy semA");
     
    Exercise 14.14

    What happens if Example 14.13; executes after semA has already been destroyed? What happens if another thread or process is blocked on semA when the sem_destroy function is called?

    Answer:

    The POSIX standard states that the result of destroying a semaphore that has already been destroyed is undefined. The result of destroying a semaphore on which other threads are blocked is also undefined.


      14.4 POSIX:SEM Semaphore Operations

      The semaphore operations described in this section apply both to POSIX:SEM unnamed semaphores and to POSIX:SEM named semaphores described in Section 14.5.

      The sem_post function implements classic semaphore signaling. If no threads are blocked on sem, then sem_post increments the semaphore value. If at least one thread is blocked on sem, then the semaphore value is zero. In this case, sem_post causes one of the threads blocked on sem to return from its sem_wait function, and the semaphore value remains at zero. The sem_post function is signal-safe and can be called from a signal handler.

       SYNOPSIS
       
           #include <semaphore.h>
       
           int sem_post(sem_t *sem);
                                                         POSIX:SEM
       

      If successful, sem_post returns 0. If unsuccessful, sem_post returns 1 and sets errno. The sem_post operation sets errno to EINVAL if *sem does not correspond to a valid semaphore.

      The sem_wait function implements the classic semaphore wait operation. If the semaphore value is 0, the calling thread blocks until it is unblocked by a corresponding call to sem_post or until it is interrupted by a signal. The sem_trywait function is similar to sem_wait except that instead of blocking when attempting to decrement a zero-valued semaphore, it returns 1 and sets errno to EAGAIN.

       SYNOPSIS
       
           #include <semaphore.h>
       
           int sem_trywait(sem_t *sem);
           int sem_wait(sem_t *sem);
                                                         POSIX:SEM
       

      If successful, these functions return 0. If unsuccessful, these functions return 1 and set errno. These functions set errno to EINVAL if *sem does not correspond to a valid semaphore. The sem_trywait sets errno to EAGAIN if it would block on an ordinary sem_wait.

      The sem_wait and sem_trywait functions may set errno to EINTR if they are interrupted by a signal. Any program that catches signals must take care when using semaphore operations, since the standard allows sem_wait and sem_trywait to return when a signal is caught and the signal handler returns. Program 14.2 restarts the sem_wait if it is interrupted by a signal.

      Program 14.2 shows how to implement a shared variable that is protected by semaphores. The initshared function initializes the value of the shared variable. It would normally be called only once. The getshared function returns the current value of the variable, and the incshared function atomically increments the variable. If successful, these functions return 0. If unsuccessful, these functions return 1 and set errno. The shared variable (shared) is static, so it can be accessed only through the functions of semshared.c. Although shared is a simple integer in Program 14.2, functions of the same form can be used to implement any type of shared variable or structure.

      Program 14.2 semshared.c

      A shared variable protected by semaphores.

       #include <errno.h>
       #include <semaphore.h>
       
       static int shared = 0;
       static sem_t sharedsem;
       
       int initshared(int val) {
           if (sem_init(&sharedsem, 0, 1) == -1)
               return -1;
           shared = val;
           return 0;
       }
       
       int getshared(int *sval) {
           while (sem_wait(&sharedsem) == -1)
               if (errno != EINTR)
                   return -1;
           *sval = shared;
           return sem_post(&sharedsem);
       }
       
       int incshared() {
           while (sem_wait(&sharedsem) == -1)
               if (errno != EINTR)
                   return -1;
           shared++;
           return sem_post(&sharedsem);
       }
       
      Exercise 14.15

      Suppose a variable were to be incremented in the main program and also in a signal handler. Explain how Program 14.2 could be used to protect this variable.

      Answer:

      It could not be used without some additional work. If the signal were caught while a call to one of the functions in Program 14.2 had the semaphore locked, a call to one of these in the signal handler would cause a deadlock. The application should block the signals in the main program before calling getshared and incshared.

      Programs 14.3 and 14.4 return to the original critical section problem of Program 14.1. The new version uses threads to illustrate the need to protect the critical section. The function in Program 14.3 is meant to be used as a thread. It outputs a message, one character at a time. To make it more likely to be interrupted in the middle of the message, the thread sleeps for 10 ms after each character is output. Program 14.4 creates a number of threadout threads and waits for them to terminate.

      Program 14.3 threadcritical.c

      A thread with an unprotected critical section.

       #include <pthread.h>
       #include <stdio.h>
       #include <unistd.h>
       #define BUFSIZE 1024
       #define TEN_MILLION 10000000L
       
       /* ARGSUSED */
       void *threadout(void *args) {
           char buffer[BUFSIZE];
           char *c;
           struct timespec sleeptime;
       
           sleeptime.tv_sec = 0;
           sleeptime.tv_nsec = TEN_MILLION;
           snprintf(buffer, BUFSIZE, "This is a thread from process %ld\n",
                    (long)getpid());
           c = buffer;
          /*****************start of critical section ********************/
           while (*c != '\0') {
               fputc(*c, stderr);
               c++;
               nanosleep(&sleeptime, NULL);
           }
          /*******************end of critical section ********************/
           return NULL;
       }
       
      Exercise 14.16

      What would happen if Program 14.4 were run with four threads?

      Answer:

      Most likely each thread would print the first character of its message, and then each would print the second character of its message, etc. All four messages would appear on one line followed by four newline characters.

      Exercise 14.17

      Why did we use nanosleep instead of a busy-waiting loop as in Program 14.1?

      Answer:

      Some thread-scheduling algorithms allow a busy-waiting thread to exclude other threads of the same process from executing.

      Exercise 14.18

      Why didn't we have the thread in Program 14.3 print its thread ID?

      Answer:

      The thread ID is of type pthread_t. Although many systems implement this as an integral type that can be cast to an int and printed, the standard does not require that pthread_t be of integral type. It may be a structure.

      Program 14.4 maincritical.c

      A main program that creates a number of threads.

       #include <pthread.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       
       void *threadout(void *args);
       
       int main(int argc, char *argv[]) {
           int error;
           int i;
           int n;
           pthread_t *tids;
       
           if (argc != 2){   /* check for valid number of command-line arguments */
               fprintf (stderr, "Usage: %s numthreads\n", argv[0]);
               return 1;
           }
           n = atoi(argv[1]);
           tids = (pthread_t *)calloc(n, sizeof(pthread_t));
           if (tids == NULL) {
               perror("Failed to allocate memory for thread IDs");
               return 1;
           }
           for (i = 0; i < n; i++)
               if (error = pthread_create(tids+i, NULL, threadout, NULL)) {
                   fprintf(stderr, "Failed to create thread:%s\n", strerror(error));
                   return 1;
               }
           for (i = 0; i < n; i++)
               if (error = pthread_join(tids[i], NULL)) {
                   fprintf(stderr, "Failed to join thread:%s\n", strerror(error));
                   return 1;
               }
           return 0;
       }
       

      Program 14.5 is a version of Program 14.3 that protects its critical section by using a semaphore passed as its parameter. Although the main program does not use signals, this program restarts sem_wait if interrupted by a signal to demonstrate how to use semaphores with signals. Program 14.6 shows the corresponding main program. The main program initializes the semaphore to 1 before any of the threads are created.

      Program 14.5 threadcriticalsem.c

      A thread with a critical section protected by a semaphore passed as its parameter.

       #include <errno.h>
       #include <pthread.h>
       #include <semaphore.h>
       #include <stdio.h>
       #include <unistd.h>
       #define TEN_MILLION 10000000L
       #define BUFSIZE 1024
       
       void *threadout(void *args) {
           char buffer[BUFSIZE];
           char *c;
           sem_t *semlockp;
           struct timespec sleeptime;
       
           semlockp = (sem_t *)args;
           sleeptime.tv_sec = 0;
           sleeptime.tv_nsec = TEN_MILLION;
           snprintf(buffer, BUFSIZE, "This is a thread from process %ld\n",
                    (long)getpid());
           c = buffer;
          /****************** entry section *******************************/
           while (sem_wait(semlockp) == -1)        /* Entry section */
               if(errno != EINTR) {
                   fprintf(stderr, "Thread failed to lock semaphore\n");
                   return NULL;
               }
          /****************** start of critical section *******************/
           while (*c != '\0') {
               fputc(*c, stderr);
               c++;
               nanosleep(&sleeptime, NULL);
           }
          /****************** exit section ********************************/
           if (sem_post(semlockp) == -1)         /* Exit section */
               fprintf(stderr, "Thread failed to unlock semaphore\n");
          /****************** remainder section ***************************/
           return NULL;
       }
       
      Exercise 14.19

      What happens if you replace the following line of Program 14.6

       semlock = sem_init(*semlock, 0, 1)
       

      with the following?

       semlock = sem_init(*semlock, 0, 0)
       

      Answer:

      The original sem_init sets the initial value of semlock to 1, which allows the first process to successfully acquire the semaphore lock when it executes sem_wait. The replacement sets the initial value of semlock to 0, causing a deadlock. All of the processes block indefinitely on sem_wait.

      Program 14.6 maincriticalsem.c

      A main program that creates a semaphore and passes it to a number of threads.

       #include <pthread.h>
       #include <semaphore.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       
       void *threadout(void *args);
       
       int main(int argc, char *argv[]) {
           int error;
           int i;
           int n;
           sem_t semlock;
           pthread_t *tids;
       
           if (argc != 2){   /* check for valid number of command-line arguments */
               fprintf (stderr, "Usage: %s numthreads\n", argv[0]);
               return 1;
           }
           n = atoi(argv[1]);
           tids = (pthread_t *)calloc(n, sizeof(pthread_t));
           if (tids == NULL) {
               perror("Failed to allocate memory for thread IDs");
               return 1;
           }
           if (sem_init(&semlock, 0, 1) == -1) {
               perror("Failed to initialize semaphore");
               return 1;
           }
           for (i = 0; i < n; i++)
               if (error = pthread_create(tids + i, NULL, threadout, &semlock)) {
                   fprintf(stderr, "Failed to create thread:%s\n", strerror(error));
                   return 1;
               }
           for (i = 0; i < n; i++)
               if (error = pthread_join(tids[i], NULL)) {
                   fprintf(stderr, "Failed to join thread:%s\n", strerror(error));
                   return 1;
               }
           return 0;
       }
       

      Exercise 14.19 illustrates the importance of properly initializing the semaphore value. The sem_getvalue function allows a user to examine the value of either a named or unnamed semaphore. This function sets the integer referenced by sval to the value of the semaphore without affecting the state of the semaphore. Interpretation of sval is a little tricky: It holds the value that the semaphore had at some unspecified time during the call, but not necessarily the value at the time of return. If the semaphore is locked, sem_getvalue either sets sval to zero or to a negative value indicating the number of threads waiting for the semaphore at some unspecified time during the call.

       SYNOPSIS
       
           #include <semaphore.h>
       
           int sem_getvalue(sem_t *restrict sem, int *restrict sval);
                                                                       POSIX:SEM
       

      If successful, sem_getvalue returns 0. If unsuccessful, sem_getvalue returns 1 and sets errno. The sem_getvalue function sets errno to EINVAL if *sem does not correspond to a valid semaphore.


        14.5 POSIX:SEM Named Semaphores

        POSIX:SEM named semaphores can synchronize processes that do not share memory. Named semaphores have a name, a user ID, a group ID and permissions just as files do. A semaphore name is a character string that conforms to the construction rules for a pathname. POSIX does not require that the name appear in the filesystem, nor does POSIX specify the consequences of having two processes refer to the same name unless the name begins with the slash character. If the name begins with a slash (/), then two processes (or threads) that open the semaphore with that name refer to the same semaphore. Consequently, always use names beginning with a / for POSIX:SEM named semaphores. Some operating systems impose other restrictions on semaphore names.

        14.5.1 Creating and opening named semaphores

        The sem_open function establishes the connection between a named semaphore and a sem_t value. The name parameter is a string that identifies the semaphore by name. This name may or may not correspond to an actual object in the file system. The oflag parameter determines whether the semaphore is created or just accessed by the function. If the O_CREAT bit of oflag is set, the sem_open requires two more parameters: a mode parameter of type mode_t giving the permissions and a value parameter of type unsigned giving the initial value of the semaphore. If both the O_CREAT and O_EXCL bits of oflag are set, the sem_open returns an error if the semaphore already exists. If the semaphore already exists and O_CREAT is set but O_EXCL is not set, the semaphore ignores O_CREAT and the additional parameters. POSIX:SEM does not provide a way to directly set the value of a named semaphore once it already exists.

         SYNOPSIS
         
           #include <semaphore.h>
         
           sem_t *sem_open(const char *name, int oflag, ...);
                                                                 POSIX:SEM
         

        If successful, the sem_open function returns the address of the semaphore. If unsuccessful, sem_open returns SEM_FAILED and sets errno. The following table lists the mandatory errors for sem_open.

        errno

        cause

        EACCES

        permissions incorrect

        EEXIST

        O_CREAT and O_EXCL are set and semaphore exists

        EINTR

        sem_open was interrupted by a signal

        EINVAL

        name can't be opened as a semaphore, or tried to create semaphore with value greater than SEM_VALUE_MAX

        EMFILE

        too many file descriptors or semaphores in use by process

        ENAMETOOLONG

        name is longer than PATH_MAX, or it has a component that exceeds NAME_MAX

        ENFILE

        too many semaphores open on the system

        ENOENT

        O_CREAT is not set and the semaphore doesn't exist

        ENOSPC

        not enough space to create the semaphore

        Program 14.7 shows a getnamed function that creates a named semaphore if it doesn't already exist. The getnamed function can be called as an initialization function by multiple processes. The function first tries to create a new named semaphore. If the semaphore already exists, the function then tries to open it without the O_CREAT and O_EXCL bits of the oflag parameter set. If successful, getnamed returns 0. If unsuccessful, getnamed returns 1 and sets errno.

        Program 14.7 getnamed.c

        A function to access a named semaphore, creating it if it doesn't already exist.

         #include <errno.h>
         #include <fcntl.h>
         #include <semaphore.h>
         #include <sys/stat.h>
         #define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
         #define FLAGS (O_CREAT | O_EXCL)
         
         int getnamed(char *name, sem_t **sem, int val) {
             while (((*sem = sem_open(name, FLAGS, PERMS, val)) == SEM_FAILED) &&
                     (errno == EINTR)) ;
             if (*sem != SEM_FAILED)
                 return 0;
             if (errno != EEXIST)
                 return -1;
             while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ;
             if (*sem != SEM_FAILED)
                 return 0;
             return -1;
         }
         

        The first parameter of getnamed is the name of the semaphore and the last parameter is the value to use for initialization if the semaphore does not already exist. The second parameter is a pointer to a pointer to a semaphore. This double indirection is necessary because getnamed needs to change a pointer. Note that if the semaphore already exists, getnamed does not initialize the semaphore.

        Program 14.8 shows a modification of Program 14.1 that uses named semaphores to protect the critical section. Program 14.8 takes three command-line arguments: the number of processes, the delay and the name of the semaphore to use. Each process calls the getnamed function of Program 14.7 to gain access to the semaphore. At most, one of these will create the semaphore. The others will gain access to it.

        Exercise 14.20

        What happens if two copies of chainnamed, using the same named semaphore run simultaneously on the same machine?

        Answer:

        With the named semaphores, each line will be printed without interleaving.

        Exercise 14.21

        What happens if you enter Ctrl-C while chainnamed is running and then try to run it again with the same named semaphore?

        Answer:

        Most likely the signal generated by Ctrl-C will be delivered while the semaphore has value 0. The next time the program is run, all processes will block and no output will result.

        14.5.2 Closing and unlinking named semaphores

        Like named pipes or FIFOs (Section 6.3), POSIX:SEM named semaphores have permanence beyond the execution of a single program. Individual programs can close named semaphores with the sem_close function, but doing so does not cause the semaphore to be removed from the system. The sem_close takes a single parameter, sem, specifying the semaphore to be closed.

         SYNOPSIS
         
           #include <semaphore.h>
         
           int sem_close(sem_t *sem);
                                         POSIX:SEM
         

        If successful, sem_close returns 0. If unsuccessful, sem_close returns 1 and sets errno. The sem_close function sets errno to EINVAL if *sem is not a valid semaphore.

        Program 14.8 chainnamed.c

        A process chain with a critical section protected by a POSIX:SEM named semaphore.

         #include <errno.h>
         #include <semaphore.h>
         #include <stdio.h>
         #include <stdlib.h>
         #include <unistd.h>
         #include <sys/wait.h>
         #include "restart.h"
         #define BUFSIZE 1024
         int getnamed(char *name, sem_t **sem, int val);
         
         int main  (int argc, char *argv[]) {
             char buffer[BUFSIZE];
             char *c;
             pid_t childpid = 0;
             int delay;
             volatile int dummy = 0;
             int i, n;
             sem_t *semlockp;
         
             if (argc != 4){       /* check for valid number of command-line arguments */
                 fprintf (stderr, "Usage: %s processes delay semaphorename\n", argv[0]);
                 return 1;
             }
             n = atoi(argv[1]);
             delay = atoi(argv[2]);
             for (i = 1; i < n; i++)
                 if (childpid = fork())
                     break;
             snprintf(buffer, BUFSIZE,
                      "i:%d  process ID:%ld  parent ID:%ld  child ID:%ld\n",
                      i, (long)getpid(), (long)getppid(), (long)childpid);
             c = buffer;
             if (getnamed(argv[3], &semlockp, 1) == -1) {
                 perror("Failed to create named semaphore");
                 return 1;
             }
             while (sem_wait(semlockp) == -1)                         /* entry section */
                 if (errno != EINTR) {
                     perror("Failed to lock semlock");
                     return 1;
                 }
             while (*c != '\0') {                                  /* critical section */
                 fputc(*c, stderr);
                 c++;
                 for (i = 0; i < delay; i++)
                     dummy++;
             }
             if (sem_post(semlockp) == -1) {                           /* exit section */
                 perror("Failed to unlock semlock");
                 return 1;
             }
             if (r_wait(NULL) == -1)                              /* remainder section */
                 return 1;
             return 0;
         }
         

        The sem_unlink function, which is analogous to the unlink function for files or FIFOs, performs the removal of the named semaphore from the system after all processes have closed the named semaphore. A close operation occurs when the process explicitly calls sem_close, _exit, exit, exec or executes a return from main. The sem_unlink function has a single parameter, a pointer to the semaphore that is to be unlinked.

         SYNOPSIS
         
           #include <semaphore.h>
         
           int sem_unlink(const char *name);
                                               POSIX:SEM
         

        If successful, sem_unlink returns 0. If unsuccessful, sem_unlink returns 1 and sets errno. The following table lists the mandatory errors for sem_unlink.

        errno

        cause

        EACCES

        permissions incorrect

        ENAMETOOLONG

        name is longer than PATH_MAX, or it has a component that exceeds NAME_MAX

        ENOENT

        the semaphore doesn't exist

        Calls to sem_open with the same name refer to a new semaphore after a sem_unlink, even if other processes still have the old semaphore open. The sem_unlink function always returns immediately, even if other processes have the semaphore open.

        Exercise 14.22

        What happens if you call sem_close for an unnamed semaphore that was initialized by sem_init rather than sem_open?

        Answer:

        The POSIX standard states that the result of doing this is not defined.

        Program 14.9 shows a function that closes and unlinks a named semaphore. The destroynamed calls the sem_unlink function, even if the sem_close function fails. If successful, destroynamed returns 0. If unsuccessful, destroynamed returns 1 and sets errno.

        Remember that POSIX:SEM named semaphores are persistent. If you create one of these semaphores, it stays in the system and retains its value until destroyed, even after the process that created it and all processes that have access to it have terminated. POSIX:SEM does not provide a method for determining which named semaphores exist. They may or may not show up when you display the contents of a directory. They may or may not be destroyed when the system reboots.

        Program 14.9 destroynamed.c

        A function that closes and unlinks a named semaphore.

         #include <errno.h>
         #include <semaphore.h>
         
         int destroynamed(char *name, sem_t *sem) {
             int error = 0;
         
             if (sem_close(sem) == -1)
                 error = errno;
             if ((sem_unlink(name) != -1) && !error)
                 return 0;
             if (error)        /* set errno to first error that occurred */
                 errno = error;
             return -1;
         }
         

          14.7 Additional Reading

          Most books on operating systems [107, 122] discuss the classical semaphore abstraction. The book UNIX Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers by Schimmel [103] presents an advanced look at how these issues apply to design of multiprocessor kernels.


          Chapter 15. POSIX IPC

          The classical UNIX interprocess communication (IPC) mechanisms of shared memory, message queues and semaphore sets are standardized in the POSIX:XSI Extension. These mechanisms, which allow unrelated processes to exchange information in a reasonably efficient way, use a key to identify, create or access the corresponding entity. The entities may persist in the system beyond the lifetime of the process that creates them, but conveniently, POSIX:XSI also provides shell commands to list and remove them.

          Objectives

          • Learn about classical interprocess communication

          • Experiment with synchronized shared memory

          • Explore semaphore implementations

          • Use message queues for interprocess logging

          • Understand the consequences of persistence


          15.1 POSIX:XSI Interprocess Communication

          The POSIX interprocess communication (IPC) is part of the POSIX:XSI Extension and has its origin in UNIX System V interprocess communication. IPC, which includes message queues, semaphore sets and shared memory, provides mechanisms for sharing information among processes on the same system. These three communication mechanisms have a similar structure, and this chapter emphasizes the common elements of their use. Table 15.1 summarizes the POSIX:XSI interprocess communication functions.

          Table 15.1. POSIX:XSI interprocess communication functions.

          mechanism

          POSIX function

          meaning

          message queues

          msgctl

          control

           

          msgget

          create or access

           

          msgrcv

          receive message

           

          msgsnd

          send message

          semaphores

          semctl

          control

           

          semget

          create or access

           

          semop

          execute operation (wait or post)

          shared memory

          shmat

          attach memory to process

           

          shmctl

          control

           

          shmdt

          detach memory from process

           

          shmget

          create and initialize or access

          15.1.1 Identifying and accessing IPC objects

          POSIX:XSI identifies each IPC object by a unique integer that is greater than or equal to zero and is returned from the get function for the object in much the same way as the open function returns an integer representing a file descriptor. For example, msgget returns an integer identifier for message queue objects. Similarly, semget returns an integer identifier for a specified semaphore set, and shmget returns an integer identifier for a shared memory segment. These identifiers are associated with additional data structures that are defined in sys/msg.h, sys/sem.h or sys/shm.h, respectively. The integer identifiers within each IPC object type are unique, but you might well have an integer identifier 1 for two different types of objects, say, a semaphore set and a message queue.

          When creating or accessing an IPC object, you must specify a key to designate the particular object to be created or accessed. Pick a key in one of these three ways.

          • Let the system pick a key (IPC_PRIVATE).

          • Pick a key directly.

          • Ask the system to generate a key from a specified path by calling ftok.

          The ftok function allows independent processes to derive the same key based on a known pathname. The file corresponding to the pathname must exist and be accessible to the processes that want to access an IPC object. The combination of path and id uniquely identifies the IPC object. The id parameter allows several IPC objects of the same type to be keyed from a single pathname.

           SYNOPSIS
           
              #include <sys/ipc.h>
           
              key_t ftok(const char *path, int id);
                                                                     POSIX:XSI
           

          If successful, ftok returns a key. If unsuccessful, ftok returns (key_t)-1 and sets errno. The following table lists the mandatory errors for ftok.

          errno

          cause

          EACCES

          search permission on a path component denied

          ELOOP

          a loop exists in resolution of path

          ENAMETOOLONG

          length of path exceeds PATH_MAX, or length of a pathname component exceeds NAME_MAX

          ENOENT

          a component of path is not a file or is empty

          ENOTDIR

          a component of path's prefix is not a directory

          Example 15.1

          The following code segment derives a key from the filename /tmp/trouble.c.

           if ((thekey = ftok("tmp/trouble.c", 1)) == (key_t)-1))
              perror("Failed to derive key from /tmp/trouble.c");
           

          15.1.2 Accessing POSIX:XSI IPC resources from the shell

          The POSIX:XSI Extension for shells and utilities defines shell commands for examining and deleting IPC resources, a convenient feature that is missing for the POSIX:SEM semaphores.

          The ipcs command displays information about POSIX:XSI interprocess communication resources. If you forget which ones you created, you can list them from the shell command line.

           SYNOPSIS
           
             ipcs [-qms][-a | -bcopt]
                                                   POSIX:XSI,Shell and Utilities
           

          If no options are given, ipcs outputs, in an abbreviated format, information about message queues, shared memory segments and semaphore sets. You can restrict the display to specific types of IPC resources with the -q, -m and -s options for message queues, shared memory and semaphores, respectively. The -a option displays a long format giving all information available. The -bcopt options specify which components of the available information to print.

          Example 15.2

          The following command displays all the available information about the semaphores currently allocated on the system.

           ipcs -s -a
           

          You can remove an individual resource by giving either an ID or a key. Use the ipcrm command to remove POSIX:XSI interprocess communication resources.

           SYNOPSIS
           
             ipcrm [-q msgid | -Q msgkey | -s semid | -S semkey |
                    -m shmid | -M shmkey] ....
                                                   POSIX:XSI,Shell and Utilities
           

          The lower case -q, -s and -m options use the object ID to specify the removal of a message queue, semaphore set or shared memory segment, respectively. The uppercase options use the original creation key.


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

           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 != '\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;
           }
           

            15.3 POSIX:XSI Shared Memory

            Shared memory allows processes to read and write from the same memory segment. The sys/shm.h header file defines the data structures for shared memory, including shmid_ds, which has the following members.

             struct ipc_perm shm_perm; /* operation permission structure */
             size_t shm_segsz;         /* size of segment in bytes */
             pid_t shm_lpid;           /* process ID of last operation */
             pid_t shm_cpid;           /* process ID of creator */
             shmatt_t shm_nattch;      /* number of current attaches */
             time_t shm_atime;         /* time of last shmat */
             time_t shm_dtime;         /* time of last shmdt */
             time_t shm_ctime;         /* time of last shctl */
             

            The shmatt_t data type is an unsigned integer data type used to hold the number of times the memory segment is attached. This type must be at least as large as an unsigned short.

            15.3.1 Accessing a shared memory segment

            The shmget function returns an identifier for the shared memory segment associated with the key parameter. It creates the segment if either the key is IPC_PRIVATE or shmflg & IPC_CREAT is nonzero and no shared memory segment or identifier is already associated with key. Shared memory segments are initialized to zero.

             SYNOPSIS
             
               #include <sys/shm.h>
             
               int shmget(key_t key, size_t size, int shmflg);
                                                                 POSIX:XSI
             

            If successful, shmget returns a nonnegative integer corresponding to the shared memory segment identifier. If unsuccessful, shmget returns 1 and sets errno. The following table lists the mandatory errors for shmget.

            errno

            cause

            EACCES

            shared memory identifier exists for key but permissions are not granted

            EEXIST

            shared memory identifier exists for key but ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) != 0

            EINVAL

            shared memory segment is to be created but size is invalid

            EINVAL

            no shared memory segment is to be created but size is inconsistent with system-imposed limits or with the segment size of key

            ENOENT

            shared memory identifier does not exist for key but (shmflg & IPC_CREAT) == 0

            ENOMEM

            not enough memory to create the specified shared memory segment

            ENOSPC

            systemwide limit on shared memory identifiers would be exceeded

            15.3.2 Attaching and detaching a shared memory segment

            The shmat function attaches the shared memory segment specified by shmid to the address space of the calling process and increments the value of shm_nattch for shmid. The shmat function returns a void * pointer, so a program can use the return value like an ordinary memory pointer obtained from malloc. Use a shmaddr value of NULL. On some systems it may be necessary to set shmflg so that the memory segment is properly aligned.

             SYNOPSIS
             
               #include <sys/shm.h>
             
               void *shmat(int shmid, const void *shmaddr, int shmflg);
                                                                     POSIX:XSI
             

            If successful, shmat returns the starting address of the segment. If unsuccessful, shmat returns 1 and sets errno. The following table lists the mandatory errors for shmat.

            errno

            cause

            EACCES

            operation permission denied to caller

            EINVAL

            value of shmid or shmaddr is invalid

            EMFILE

            number of shared memory segments attached to process would exceed limit

            ENOMEM

            process data space is not large enough to accommodate the shared memory segment

            When finished with a shared memory segment, a program calls shmdt to detach the shared memory segment and to decrement shm_nattch. The shmaddr parameter is the starting address of the shared memory segment.

             SYNOPSIS
             
               #include <sys/shm.h>
             
               int shmdt(const void *shmaddr);
                                                              POSIX:XSI
             

            If successful, shmdt returns 0. If unsuccessful, shmdt returns 1 and sets errno. The shmdt function sets errno to EINVAL when shmaddr does not correspond to the starting address of a shared memory segment.

            The last process to detach the segment should deallocate the shared memory segment by calling shmctl.

            15.3.3 Controlling shared memory

            The shmctl function provides a variety of control operations on the shared memory segment shmid as specified by the cmd parameter. The interpretation of the buf parameter depends on the value of cmd, as described below.

             SYNOPSIS
             
               #include <sys/shm.h>
             
               int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                                                              POSIX:XSI
             

            If successful, shmctl returns 0. If unsuccessful, shmctl returns 1 and sets errno. The following table lists the mandatory errors for shmctl.

            errno

            cause

            EACCES

            cmd is IPC_STAT and caller does not have read permission

            EINVAL

            value of shmid or cmd is invalid

            EPERM

            cmd is IPC_RMID or IPC_SET and caller does not have correct permissions

            Table 15.3 gives the POSIX:XSI values of cmd for shmctl.

            Table 15.3. POSIX:XSI values of cmd for shmctl.

            cmd

            description

            IPC_RMID

            remove shared memory segment shmid and destroy corresponding shmid_ds

            IPC_SET

            set values of fields for shared memory segment shmid from values found in buf

            IPC_STAT

            copy current values for shared memory segment shmid into buf

            Example 15.11 detachandremove.c

            The detachandremove function detaches the shared memory segment shmaddr and then removes the shared memory segment specified by semid.

             #include <stdio.h>
             #include <errno.h>
             #include <sys/shm.h>
             
             int detachandremove(int shmid, void *shmaddr) {
                int error = 0;
             
                if (shmdt(shmaddr) == -1)
                   error = errno;
                if ((shmctl(shmid, IPC_RMID, NULL) == -1) && !error)
                   error = errno;
                if (!error)
                   return 0;
                errno = error;
                return -1;
             }
             

            15.3.4 Shared memory examples

            Program 4.11 on page 108 monitors two file descriptors by using a parent and a child. Each process echoes the contents of the files to standard output and then writes to standard error the total number of bytes received. There is no simple way for this program to report the total number of bytes received by the two processes without using a communication mechanism such as a pipe.

            Program 15.5 modifies Program 4.11 so that the parent and child share a small memory segment. The child stores its byte count in the shared memory. The parent waits for the child to finish and then outputs the number of bytes received by each process along with the sum of these values. The parent creates the shared memory segment by using the key IPC_PRIVATE, which allows the memory to be shared among its children. The synchronization of the shared memory is provided by the wait function. The parent does not access the shared memory until it has detected the termination of the child. Program 15.5 calls detachandremove of Example 15.11 when it must both detach and remove the shared memory segment.

            Program 15.5 monitorshared.c

            A program to monitor two file descriptors and keep information in shared memory. The parent waits for the child, to ensure mutual exclusion.

             #include <errno.h>
             #include <fcntl.h>
             #include <stdio.h>
             #include <string.h>
             #include <unistd.h>
             #include <sys/shm.h>
             #include <sys/stat.h>
             #include <sys/wait.h>
             #include "restart.h"
             #define PERM (S_IRUSR | S_IWUSR)
             
             int detachandremove(int shmid, void *shmaddr);
             
             int main(int argc, char *argv[]) {
                int bytesread;
                int childpid;
                int fd, fd1, fd2;
                int id;
                int *sharedtotal;
                int totalbytes = 0;
             
                if (argc != 3) {
                   fprintf(stderr, "Usage: %s file1 file2\n", argv[0]);
                   return 1;
                }
                if (((fd1 = open(argv[1], O_RDONLY)) == -1) ||
                    ((fd2 = open(argv[2], O_RDONLY)) == -1)) {
                   perror("Failed to open file");
                   return 1;
                }
                if ((id = shmget(IPC_PRIVATE, sizeof(int), PERM)) == -1) {
                   perror("Failed to create shared memory segment");
                   return 1;
                }
                if ((sharedtotal = (int *)shmat(id, NULL, 0)) == (void *)-1) {
                   perror("Failed to attach shared memory segment");
                   if (shmctl(id, IPC_RMID, NULL) == -1)
                      perror("Failed to  remove memory segment");
                   return 1;
                }
                if ((childpid = fork()) == -1) {
                   perror("Failed to create child process");
                   if (detachandremove(id, sharedtotal) == -1)
                      perror("Failed to destroy shared memory segment");
                   return 1;
                }
                if (childpid > 0)                                         /* parent code */
                   fd = fd1;
                else
                   fd = fd2;
                while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
                   totalbytes += bytesread;
                if (childpid == 0) {                                      /* child code */
                   *sharedtotal = totalbytes;
                   return 0;
                }
                if (r_wait(NULL) == -1)
                   perror("Failed to wait for child");
                else {
                   fprintf(stderr, "Bytes copied: %8d by parent\n", totalbytes);
                   fprintf(stderr, "              %8d by child\n", *sharedtotal);
                   fprintf(stderr, "              %8d total\n", totalbytes + *sharedtotal);
                }
                if (detachandremove(id, sharedtotal) == -1) {
                   perror("Failed to destroy shared memory segment");
                   return 1;
                }
                return 0;
             }
             

            Using shared memory between processes that do not have a common ancestor requires the processes to agree on a key, either directly or with ftok and a pathname.

            Program 13.5 on page 456 used mutex locks to keep a sum and count for threads of a given process. This was particularly simple because the threads automatically share the mutex and the mutex could be initialized statically. Implementing synchronized shared memory for independent processes is more difficult because you must set up the sharing of the synchronization mechanism as well as the memory for the sum and the count.

            Program 15.6 uses a semaphore and a small shared memory segment to keep a sum and count. Each process must first call the initshared function with an agreed-on key. This function first tries to create a shared memory segment with the given key. If successful, initshared initializes the sum and count. Otherwise, initshared just accesses the shared memory segment. In either case, initshared calls initsemset with the ready flag in shared memory to access a semaphore set containing a single semaphore initialized to 1. This semaphore element protects the shared memory segment. The add and getcountandsum functions behave as in Program 13.5, this time using the semaphore, rather than a mutex, for protection.

            Program 15.6 sharedmemsum.c

            A function that keeps a synchronized sum and count in shared memory.

             #include <errno.h>
             #include <signal.h>
             #include <stdio.h>
             #include <sys/sem.h>
             #include <sys/shm.h>
             #include <sys/stat.h>
             #define PERM (S_IRUSR | S_IWUSR)
             
             int initsemset(key_t mykey, int value, sig_atomic_t *readyp);
             void setsembuf(struct sembuf *s, int num, int op, int flg);
             
             typedef struct {
                int count;
                double sum;
                sig_atomic_t ready;
             } shared_sum_t;
             
             static int semid;
             static struct sembuf semlock;
             static struct sembuf semunlock;
             static shared_sum_t *sharedsum;
             
             int initshared(int key) {              /* initialize shared memory segment */
                int shid;
             
                setsembuf(&semlock, 0, -1, 0);         /* setting for locking semaphore */
                setsembuf(&semunlock, 0, 1, 0);      /* setting for unlocking semaphore */
                                       /* get attached memory, creating it if necessary */
                shid = shmget(key, sizeof(shared_sum_t), PERM | IPC_CREAT | IPC_EXCL);
                if ((shid == -1) && (errno != EEXIST))                    /* real error */
                   return -1;
                if (shid == -1) {              /* already created, access and attach it */
                   if (((shid = shmget(key, sizeof(shared_sum_t), PERM)) == -1) ||
                       ((sharedsum = (shared_sum_t *)shmat(shid, NULL, 0)) == (void *)-1) )
                      return -1;
                }
                else {    /* successfully created, must attach and initialize variables */
                   sharedsum = (shared_sum_t *)shmat(shid, NULL, 0);
                   if (sharedsum == (void *)-1)
                      return -1;
                   sharedsum -> count = 0;
                   sharedsum -> sum = 0.0;
                }
                semid = initsemset(key, 1, &sharedsum->ready);
                if (semid == -1)
                   return -1;
                return 0;
             }
             
             int add(double x) {                                       /* add x to sum */
                if (semop(semid, &semlock, 1) == -1)
                   return -1;
                sharedsum -> sum += x;
                sharedsum -> count++;
                if (semop(semid, &semunlock, 1) == -1)
                   return -1;
                return 0;
             }
             
             int getcountandsum(int *countp, double *sum) {    /* return sum and count */
                if (semop(semid, &semlock, 1) == -1)
                   return -1;
                *countp = sharedsum -> count;
                *sum = sharedsum -> sum;
                if (semop(semid, &semunlock, 1) == -1)
                   return -1;
                return 0;
             }
             

            Each process must call initshared at least once before calling add or getcountandsum. A process may call initshared more than once, but one thread of the process should not call initshared while another thread of the same process is calling add or getcountandsum.

            Example 15.12

            In Program 15.6, the three fields of the shared memory segment are treated differently. The sum and count are explicitly initialized to 0 whereas the function relies on the fact that ready is initialized to 0 when the shared memory segment is created. Why is it done this way?

            Answer:

            All three fields are initialized to 0 when the shared memory segment is created, so in this case the explicit initialization is not necessary. The program relies on the atomic nature of the creation and initialization of ready to 0, but sum and count can be initialized to any values.

            Program 15.7 displays the shared count and sum when it receives a SIGUSR1 signal. The signal handler is allowed to use fprintf for output, even though it might not be async-signal safe, since no output is done by the main program after the signal handler is set up and the signal is unblocked.

            Program 15.8 modifies Program 15.5 by copying information from a single file to standard output and saving the number of bytes copied in a shared sum implemented by Program 15.6. Program 15.8 has two command-line arguments: the name of the file; and the key identifying the shared memory and its protecting semaphore. You can run multiple copies of Program 15.8 simultaneously with different filenames and the same key. The common shared memory stores the total number of bytes copied.

            Program 15.7 showshared.c

            A program to display the shared count and sum when it receives a SIGUSR1 signal.

             #include <signal.h>
             #include <stdio.h>
             #include <stdlib.h>
             #include <unistd.h>
             
             int getcountandsum(int *countp, double *sump);
             int initshared(int key);
             
             /* ARGSUSED */
             static void showit(int signo) {
                int count;
                double sum;
                if (getcountandsum(&count, &sum) == -1)
                   printf("Failed to get count and sum\n");
                else
                   printf("Sum is %f and count is %d\n", sum, count);
             }
             
             int main(int argc, char *argv[]) {
                struct sigaction act;
                int key;
                sigset_t mask, oldmask;
             
                if (argc != 2) {
                   fprintf(stderr, "Usage: %s key\n", argv[0]);
                   return 1;
                }
                key = atoi(argv[1]);
                if (initshared(key) == -1) {
                   perror("Failed to initialize shared memory");
                   return 1;
                }
                if ((sigfillset(&mask) == -1) ||
                    (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1)) {
                   perror("Failed to block signals to set up handlers");
                   return 1;
                }
                printf("This is process %ld waiting for SIGUSR1 (%d)\n",
                        (long)getpid(), SIGUSR1);
             
                act.sa_handler = showit;
                act.sa_flags = 0;
                if ((sigemptyset(&act.sa_mask) == -1) ||
                    (sigaction(SIGUSR1, &act, NULL) == -1)) {
                   perror("Failed to set up signal handler");
                   return 1;
                }
                if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
                   perror("Failed to unblock signals");
                   return 1;
                }
                for ( ; ; )
                   pause();
             }
             
            Program 15.8 monitoroneshared.c

            A program to monitor one file and send the output to standard output. It keeps track of the number of bytes received by calling add from Program 15.6.

             #include <fcntl.h>
             #include <stdio.h>
             #include <stdlib.h>
             #include <unistd.h>
             #include "restart.h"
             
             int add(double x);
             int initshared(int key);
             
             int main(int argc, char *argv[]) {
                 int bytesread;
                 int fd;
                 int key;
             
                 if (argc != 3) {
                     fprintf(stderr,"Usage: %s file key\n",argv[0]);
                     return 1;
                 }
                 if ((fd = open(argv[1],O_RDONLY)) == -1) {
                     perror("Failed to open file");
                     return 1;
                 }
                 key = atoi(argv[2]);
                 if (initshared(key) == -1) {
                     perror("Failed to initialize shared sum");
                     return 1;
                 }
                 while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
                     if (add((double)bytesread) == -1) {
                         perror("Failed to add to count");
                         return 1;
                     }
                 return 0;
             }
             
            Example 15.13

            Start Program 15.7 in one window, using key 12345, with the following command.

             showshared 12345
             

            Create a few named pipes, say, pipe1 and pipe2. Start copies of monitoroneshared in different windows with the following commands.

             monitoroneshared pipe1 12345
             monitoroneshared pipe2 12345
             

            In other windows, send characters to the pipes (e.g., cat > pipe1). Periodically send SIGUSR1 signals to showshared to monitor the progress.


            15.4 POSIX:XSI Message Queues

            The message queue is a POSIX:XSI interprocess communication mechanism that allows a process to send and receive messages from other processes. The data structures for message queues are defined in sys/msg.h. The major data structure for message queues is msqid_ds, which has the following members.

             struct ipc_perm msg_perm; /* operation permission structure */
             msgqnum_t msg_qnum;       /* number of messages currently in queue */
             msglen_t msg_qbytes;      /* maximum bytes allowed in queue */
             pid_t msg_lspid;          /* process ID of msgsnd */
             pid_t msg_lrpid;          /* process ID of msgrcv */
             time_t msg_stime;         /* time of last msgsnd */
             time_t msg_rtime;         /* time of last msgrcv */
             time_t msg_ctime;         /* time of last msgctl */
             

            The msgqnum_t data type holds the number of messages in the message queue; the msglen_t type holds the number of bytes allowed in a message queue. Both types must be at least as large as an unsigned short.

            15.4.1 Accessing a message queue

            The msgget function returns the message queue identifier associated with the key parameter. It creates the identifier if either the key is IPC_PRIVATE or msgflg & IPC_CREAT is nonzero and no message queue or identifier is already associated with key.

             SYNOPSIS
             
                 #include <sys/msg.h>
             
                 int msgget(key_t key, int msgflg);
                                                             POSIX:XSI
             

            If successful, msgget returns a nonnegative integer corresponding to the message queue identifier. If unsuccessful, msgget returns 1 and sets errno. The following table lists the mandatory errors for msgget.

            errno

            cause

            EACCES

            message queue exists for key, but permission denied

            EEXIST

            message queue exists for key, but ((msgflg & IPC_CREAT) && (msgflg & IPC_EXCL)) != 0

            ENOENT

            message queue does not exist for key, but (msgflg & IPC_CREAT) == 0

            ENOSPC

            systemwide limit on message queues would be exceeded

            Example 15.14

            Create a new message queue.

             #define PERMS (S_IRUSR | S_IWUSR)
             
             int msqid;
             if ((msqid = msgget(IPC_PRIVATE, PERMS)) == -1)
                perror("Failed to create new private message queue");
             

            After obtaining access to a message queue with msgget, a program inserts messages into the queue with msgsnd. The msqid parameter identifies the message queue, and the msgp parameter points to a user-defined buffer that contains the message to be sent, as described below. The msgsz parameter specifies the actual size of the message text. The msgflg parameter specifies actions to be taken under various conditions.

             SYNOPSIS
             
                #include <sys/msg.h>
             
                int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
                                                                       POSIX:XSI
             

            If successful, msgsnd returns 0. If unsuccessful, msgsnd returns 1 and sets errno. The following table lists the mandatory errors for msgsnd.

            errno

            cause

            EACCES

            operation is denied to the caller

            EAGAIN

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

            EIDRM

            msqid has been removed from the system

            EINTR

            msgsnd was interrupted by a signal

            EINVAL

            msqid is invalid, the message type is < 1, or msgsz is out of range

            The msgp parameter points to a user-defined buffer whose first member must be a long specifying the type of message, followed by space for the text of the message. The structure might be defined as follows.

             struct mymsg{
                long mtype;    /* message type */
                char mtext[1]; /* message text */
             } mymsg_t;
             

            The message type must be greater than 0. The user can assign message types in any way appropriate to the application.

            Here are the steps needed to send the string mymessage to a message queue.

            1. Allocate a buffer, mbuf, which is of type mymsg_t and size

               sizeof(mymsg_t) + strlen(mymessage).
               
            2. Copy mymessage into the mbuf->mtext member.

            3. Set the message type in the mbuf->mtype member.

            4. Send the message.

            5. Free mbuf.

            Remember to check for errors and to free mbuf if an error occurs. Code for this is provided in Program 15.9, discussed later.

            A program can remove a message from a message queue with msgrcv. The msqid parameter identifies the message queue, and the msgp parameter points to a user-defined buffer for holding the message to be retrieved. The format of msgp is as described above for msgsnd. The msgsz parameter specifies the actual size of the message text. The msgtyp parameter can be used by the receiver for message selection. The msgflg specifies actions to be taken under various conditions.

             SYNOPSIS
             
                #include <sys/msg.h>
             
                ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,
                               long msgtyp, int msgflg);
                                                                 POSIX:XSI
             

            If successful, msgrcv returns the number of bytes in the text of the message. If unsuccessful, msgrcv returns (ssize_t) 1 and sets errno. The following table lists the mandatory errors for msgrcv.

            errno

            cause

            E2BIG

            value of the mtext member of msgp is greater than msgsize and (msgflg & MSG_NOERROR) == 0

            EACCES

            operation is denied to the caller

            EIDRM

            msqid has been removed from the system

            EINTR

            msgrcv was interrupted by a signal

            EINVAL

            value of msqid is invalid

            ENOMSG

            queue does not contain a message of requested type and (msgflg & IPC_NOWAIT) != 0

            Table 15.4 shows how msgrcv uses the msgtyp parameter to determine the order in which it removes messages from the queue.

            Use msgctl to deallocate or change permissions for the message queue identified by msqid. The cmd parameter specifies the action to be taken as listed in Table 15.5. The msgctl function uses its buf parameter to write or read state information, depending on cmd.

            Table 15.4. The POSIX:XSI values for the msgtyp parameter determine the order in which msgrcv removes messages from the queue.

            msgtyp

            action

            0

            remove first message from queue

            > 0

            remove first message of type msgtyp from the queue

            < 0

            remove first message of lowest type that is less than or equal to the absolute value of msgtyp

            Table 15.5. POSIX:XSI values for the cmd parameter of msgctl.

            cmd

            description

            IPC_RMID

            remove the message queue msqid and destroy the corresponding msqid_ds

            IPC_SET

            set members of the msqid_ds data structure from buf

            IPC_STAT

            copy members of the msqid_ds data structure into buf

             SYNOPSIS
             
                 #include <sys/msg.h>
             
                 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
                                                                      POSIX:XSI
             

            If successful, msgctl returns 0. If unsuccessful, msgctl returns 1 and sets errno. The following table lists the mandatory errors for msgctl.

            errno

            cause

            EACCES

            cmd is IPC_STAT and the caller does not have read permission

            EINVAL

            msqid or cmd is invalid

            EPERM

            cmd is IPC_RMID or IPC_SET and caller does not have privileges

            Program 15.9 contains utilities for accessing a message queue similar to that of Program 15.6, but simpler because no initialization or synchronization is needed. Each process should call the initqueue function before accessing the message queue. The msgprintf function has syntax similar to printf for putting formatted messages in the queue. The msgwrite function is for unformatted messages. Both msgprintf and msgwrite allocate memory for each message and free this memory after calling msgsnd. The removequeue function removes the message queue and its associated data structures. The msgqueuelog.h header file contains the prototypes for these functions. If successful, these functions return 0. If unsuccessful, these functions return 1 and set errno.

            Program 15.9 msgqueuelog.c

            Utility functions that access and output to a message queue.

             #include <errno.h>
             #include <stdio.h>
             #include <stdarg.h>
             #include <stdlib.h>
             #include <string.h>
             #include <sys/msg.h>
             #include <sys/stat.h>
             #include "msgqueuelog.h"
             #define PERM (S_IRUSR | S_IWUSR)
             
             typedef struct {
                long mtype;
                char mtext[1];
             } mymsg_t;
             static int queueid;
             
             int initqueue(int key) {                    /* initialize the message queue */
                queueid = msgget(key, PERM | IPC_CREAT);
                if (queueid == -1)
                   return -1;
                return 0;
             }
             
             int msgprintf(char *fmt, ...) {               /* output a formatted message */
                va_list ap;
                char ch;
                int error = 0;
                int len;
                mymsg_t *mymsg;
             
                va_start(ap, fmt);                       /* set up the format for output */
                len = vsnprintf(&ch, 1, fmt, ap);              /* how long would it be ? */
                if ((mymsg = (mymsg_t *)malloc(sizeof(mymsg_t) + len)) == NULL)
                   return -1;
                vsprintf(mymsg->mtext, fmt, ap);                 /* copy into the buffer */
                mymsg->mtype = 1;                            /* message type is always 1 */
                if (msgsnd(queueid, mymsg, len + 1, 0) == -1)
                   error = errno;
                free(mymsg);
                if (error) {
                   errno = error;
                   return -1;
                }
                return 0;
             }
             
             int msgwrite(void *buf, int len) {     /* output buffer of specified length */
                int error = 0;
                mymsg_t *mymsg;
             
                if ((mymsg = (mymsg_t *)malloc(sizeof(mymsg_t) + len - 1)) == NULL)
                   return -1;
                memcpy(mymsg->mtext, buf, len);
                mymsg->mtype = 1;                            /* message type is always 1 */
                if (msgsnd(queueid, mymsg, len, 0) == -1)
                   error = errno;
                free(mymsg);
                if (error) {
                   errno = error;
                   return -1;
                }
                return 0;
             }
             
             int remmsgqueue(void) {
                return msgctl(queueid, IPC_RMID, NULL);
             }
             
            Example 15.15

            Why does the msgprintf function of Program 15.9 use len in malloc and len+1 in msgsnd?

            Answer:

            The vsnprintf function returns the number of bytes to be formatted, not including the string terminator, so len is the string length. We need one extra byte for the string terminator. One byte is already included in mymsg_t.

            Program 15.10, which outputs the contents of a message queue to standard output, can save the contents of a message queue to a file through redirection. The msgqueuesave program takes a key that identifies the message queue as a command-line argument and calls the initqueue function of Program 15.9 to access the queue. The program then outputs the contents of the queue to standard output until an error occurs. Program 15.10 does not deallocate the message queue when it completes.

            Program 15.11 reads lines from standard input and sends each to the message queue. The program takes a key as a command-line argument and calls initqueue to access the corresponding message queue. Program 15.11 sends an informative message containing its process ID before starting to copy from standard input.

            You should be able to run multiple copies of Program 15.11 along with a single copy of Program 15.10. Since none of the programs call removequeue, be sure to execute the ipcrm command when you finish.

            Example 15.16

            Why does Program 15.10 use r_write from the restart library even though the program does not catch any signals?

            Answer:

            In addition to restarting when interrupted by a signal (which is not necessary here), r_write continues writing if write did not output all of the requested bytes.

            Example 15.17

            How would you modify these programs so that messages from different processes could be distinguished?

            Answer:

            Modify the functions in Program 15.9 to send the process ID as the message type. Modify Program Program 15.10 to output the message type along with the message.

            Program 15.10 msgqueuesave.c

            A program that copies messages from a message queue to standard output.

             #include <stdio.h>
             #include <stdlib.h>
             #include <unistd.h>
             #include <sys/msg.h>
             #include "msgqueuelog.h"
             #include "restart.h"
             #define MAXSIZE 4096
             typedef struct {
                long mtype;
                char mtext[MAXSIZE];
             } mymsg_t;
             
             int main(int argc, char *argv[]) {
                int id;
                int key;
                mymsg_t mymsg;
                int size;
             
                if (argc != 2) {
                   fprintf(stderr, "Usage: %s key\n", argv[0]);
                   return 1;
                }
                key = atoi(argv[1]);
                if ((id = initqueue(key)) == -1) {
                   perror("Failed to initialize message queue");
                   return 1;
                }
                for ( ; ; ) {
                   if ((size = msgrcv(id, &mymsg, MAXSIZE, 0, 0)) == -1) {
                      perror("Failed to read message queue");
                      break;
                   }
                   if (r_write(STDOUT_FILENO, mymsg.mtext, size) == -1) {
                      perror("Failed to write to standard output");
                      break;
                   }
                }
                return 1;
             }
             
            Program 15.11 msgqueuein.c

            A program that sends standard input to a message queue.

             #include <stdio.h>
             #include <stdlib.h>
             #include <string.h>
             #include <sys/msg.h>
             #include <unistd.h>
             #include "msgqueuelog.h"
             #include "restart.h"
             #define MAXLINE 1024
             
             int main(int argc, char *argv[]) {
                char buf[MAXLINE];
                int key;
                int size;
             
                if (argc != 2) {
                   fprintf(stderr, "Usage: %s key\n", argv[0]);
                   return 1;
                }
                key = atoi(argv[1]);
                if (initqueue(key) == -1) {
                   perror("Failed to initialize message queue");
                   return 1;
                }
                if (msgprintf("This is process %ld\n", (long)getpid()) == -1) {
                   perror("Failed to write header to message queue");
                   return 1;
                }
                for ( ; ; ) {
                   if ((size = readline(STDIN_FILENO, buf, MAXLINE)) == -1) {
                      perror("Failed to read from standard input");
                      break;
                   }
                   if (msgwrite(buf, size) == -1) {
                      perror("Failed to write message to standard output");
                      break;
                   }
                }
                return 0;
             }
             

              15.5 Exercise: POSIX Unnamed Semaphores

              This exercise describes an implementation of POSIX:SEM-like unnamed semaphores in terms of semaphore sets. Represent the unnamed semaphore by a data structure of type mysem_t, which for this exercise is simply an int. The mysem.h header file should contain the definition of mysem_t and the prototypes for the semaphore functions.

               int mysem_init(mysem_t *sem, int pshared, unsigned int value);
               int mysem_destroy(mysem_t *sem);
               int mysem_wait(mysem_t *sem);
               int mysem_post(mysem_t *sem);
               

              All these functions return 0 if successful. On error, they return 1 and set errno appropriately. Actually, the last point is a little subtle. It will probably turn out that the only statements that can cause an error are the semaphore set calls and they set errno. If that is the case, the functions return the correct errno value as long as there are no intervening functions that might set errno.

              Assume that applications call mysem_init before creating any threads. The mysem_t value is the semaphore ID of a semaphore set. Ignore the value of pshared, since semaphore sets are sharable among processes. Use a key of IPC_PRIVATE.

              Implement the mysem_wait and mysem_post directly with calls to semop. The details will depend on how sem_init initializes the semaphore. Implement mysem_destroy with a call to semctl.

              Test your implementation with Programs 14.5 and 14.6 to see that it enforces mutual exclusion.

              Before logging out, use ipcs -s from the command line. If semaphores still exist (because of a program bug), delete each of them, using the following command.

               ipcrm -s n
               

              This command deletes the semaphore with ID n. The semaphore should be created only once by the test program. It should also be deleted only once, not by all the children in the process chain.


              15.6 Exercise: POSIX Named Semaphores

              This exercise describes an implementation of POSIX:SEM-like named semaphores in terms of semaphores sets. Represent the named semaphore by a structure of type mysem_t. The mysemn.h file should include the definition of mysem_t and the prototypes of the following functions.

               mysem_t *mysem_open(const char *name, int oflag, mode_t mode,
                                    unsigned int value);
               int mysem_close(mysem_t *sem);
               int mysem_unlink(const char *name);
               int mysem_wait(mysem_t *sem);
               int mysem_post(mysem_t *sem);
               

              The mysem_open function returns NULL and sets errno when there is an error. All the other functions return 1 and set errno when there is an error. To simplify the interface, always call mysem_open with four parameters.

              Represent the named semaphore by an ordinary file that contains the semaphore ID of the semaphore set used to implement the POSIX semaphore. First try to open the file with open, using O_CREAT | O_EXCL. If you created the file, use fdopen to get a FILE pointer for the file. Allocate the semaphore set and store the ID in the file. If the file already exists, open the file for reading with fopen. In either case, return the file pointer. The mysem_t data type will just be the type FILE.

              The mysem_close function makes the semaphore inaccessible to the caller by closing the file. The mysem_unlink function deletes the semaphore and its corresponding file. The mysem_wait function decrements the semaphore, and the mysem_post function increments the semaphore. Each function reads the semaphore ID from the file by first calling rewind and then reading an integer. It is possible to get an end-of-file if the process that created the semaphore has not yet written to the file. In this case, try again.

              Put all the semaphore functions in a separate library and treat this as an object in which the only items with external linkage are the five functions listed above. Do not worry about race conditions in using mysem_open to create the file until a rudimentary version of the test program works. Devise a mechanism that frees the semaphore set after the last mysem_unlink but only after the last process closes this semaphore. The mysem_unlink cannot directly do the freeing because other processes may still have the semaphore open. One possibility is to have mysem_close check the link count in the inode and free the semaphore set if the link count becomes 0.

              Try to handle the various race conditions by using an additional semaphore set to protect the critical sections for semaphore initialization and access. What happens when two threads try to access the semaphore concurrently? Use the same semaphore for all copies of your library to protect against interaction between unrelated processes. Refer to this semaphore by a filename, which you can convert to a key with ftok.


                15.7 Exercise: Implementing Pipes with Shared Memory

                This section develops a specification for a software pipe consisting of a semaphore set to protect access to the pipe and a shared memory segment to hold the pipe data and state information. The pipe state information includes the number of bytes of data in the pipe, the position of next byte to be read and status information. The pipe can hold at most one message of maximum size _POSIX_PIPE_BUF. Represent the pipe by the following pipe_t structure allocated in shared memory.

                 typedef struct pipe {
                    int semid;                    /* ID of protecting semaphore set */
                    int shmid;                   /* ID of the shared memory segment */
                    char data[_POSIX_PIPE_BUF];         /* buffer for the pipe data */
                    int data_size;                   /* bytes currently in the pipe */
                    void *current_start;        /* pointer to current start of data */
                    int end_of_file;          /* true after pipe closed for writing */
                 } pipe_t;
                 

                A program creates and references the pipe by using a pointer to pipe_t as a handle. For simplicity, assume that only one process can read from the pipe and one process can write to the pipe. The reader must clean up the pipe when it closes the pipe. When the writer closes the pipe, it sets the end_of_file member of pipe_t so that the reader can detect end-of-file.

                The semaphore set protects the pipe_t data structure during shared access by the reader and the writer. Element zero of the semaphore set controls exclusive access to data. It is initially 1. Readers and writers acquire access to the pipe by decrementing this semaphore element, and they release access by incrementing it. Element one of the semaphore set controls synchronization of writes so that data contains only one message, that is, the output of a single write operation. When this semaphore element is 1, the pipe is empty. When it is 0, the pipe has data or an end-of-file has been encountered. Initially, element one is 1. The writer decrements element one before writing any data. The reader waits until element one is 0 before reading. When it has read all the data from the pipe, the reader increments element one to indicate that the pipe is now available for writing. Write the following functions.

                 pipe_t *pipe_open(void);
                 

                creates a software pipe and returns a pointer of type pipe_t * to be used as a handle in the other calls. The algorithm for pipe_open is as follows.

                1. Create a shared memory segment to hold a pipe_t data structure by calling shmget. Use a key of IPC_PRIVATE and owner read/write permissions.

                2. Attach the segment by calling shmat. Cast the return value of shmat to a pipe_t * and assign it to a local variable p.

                3. Set p->shmid to the ID of the shared memory segment returned by the shmget.

                4. Set p->data_size and p->end_of_file to 0.

                5. Create a semaphore set containing two elements by calling semget with IPC_PRIVATE key and owner read, write, execute permissions.

                6. Initialize both semaphore elements to 1, and put the resulting semaphore ID value in p->semid.

                7. If all the calls were successful, return p.

                8. If an error occurs, deallocate all resources, set errno, and return a NULL pointer.

                 int pipe_read(pipe_t *p, char *buf, int bytes);
                 

                behaves like an ordinary blocking read function. The algorithm for pipe_read is as follows.

                1. Perform semop on p->semid to atomically decrement semaphore element zero, and test semaphore element one for 0. Element zero provides mutual exclusion. Element one is only 0 if there is something in the buffer.

                2. If p->data_size is greater than 0 do the following.

                  1. Copy at most bytes bytes of information starting at position p->current_start of the software pipe into buf. Take into account the number of bytes in the pipe.

                  2. Update the p->current_start and p->data_size members of the pipe data structure.

                  3. If successful, set the return value to the number of bytes actually read.

                3. Otherwise, if p->data_size is 0 and p->end_of_file is true, set the return value to 0 to indicate end-of-file.

                4. Perform another semop operation to release access to the pipe. Increment element zero. If no more data is in the pipe, also increment element one unless p->end_of_file is true. Perform these operations atomically by a single semop call.

                5. If an error occurs, return 1 with errno set.

                 int pipe_write(pipe_t *p, char *buf, int bytes);
                 

                behaves like an ordinary blocking write function. The algorithm for pipe_write is as follows.

                1. Perform a semop on p->semid to atomically decrement both semaphore elements zero and one.

                2. Copy at most _POSIX_PIPE_BUF bytes from buf into the pipe buffer.

                3. Set p->data_size to the number of bytes actually copied, and set p->current_start to 0.

                4. Perform another semop call to atomically increment semaphore element zero of the semaphore set.

                5. If successful, return the number of bytes copied.

                6. If an error occurs, return 1 with errno set.

                 int pipe_close(pipe_t *p, int how);
                 

                closes the pipe. The how parameter determines whether the pipe is closed for reading or writing. Its possible values are O_RDONLY and O_WRONLY. The algorithm for pipe_close is as follows.

                1. Use the semop function to atomically decrement element zero of p->semid. If the semop fails, return 1 with errno set.

                2. If how & O_WRONLY is true, do the following.

                  1. Set p->end_of_file to true.

                  2. Perform a semctl to set element one of p->semid to 0.

                  3. Copy p->semid into a local variable, semid_temp.

                  4. Perform a shmdt to detach p.

                  5. Perform a semop to atomically increment element zero of semid_temp.

                  If any of the semop, semctl, or shmdt calls fail, return 1 immediately with errno set.

                3. If how & O_RDONLY is true, do the following.

                  1. Perform a semctl to remove the semaphore p->semid. (If the writer is waiting on the semaphore set, its semop returns an error when this happens.)

                  2. Copy p->shmid into a local variable, shmid_temp.

                  3. Call shmdt to detach p.

                  4. Call shmctl to deallocate the shared memory segment identified by shmid_temp.

                  If any of the semctl, shmdt, or shmctl calls fail, return 1 immediately with errno set.

                Test the software pipe by writing a main program that is similar to Program 6.4. The program creates a software pipe and then forks a child. The child reads from standard input and writes to the pipe. The parent reads what the child has written to the pipe and outputs it to standard output. When the child detects end-of-file on standard input, it closes the pipe for writing. The parent then detects end-of-file on the pipe, closes the pipe for reading (which destroys the pipe), and exits. Execute the ipcs command to check that everything was properly destroyed.

                The above specification describes blocking versions of the functions pipe_read and pipe_write. Modify and test a nonblocking version also.


                15.8 Exercise: Implementing Pipes with Message Queues

                Formulate a specification of a software pipe implementation in terms of message queues. Implement the following functions.

                 pipe_t *pipe_open(void);
                 int pipe_read(pipe_t *p, char *buf, int chars);
                 int pipe_write(pipe_t *p, char *buf, int chars);
                 int pipe_close(pipe_t *p);
                 

                Design a pipe_t structure to fit the implementation. Test the implementation as described in Section 15.7.


                15.9 Additional Reading

                Most books on operating systems [107, 122] discuss the classical semaphore abstraction. UNIX Network Programming by Stevens [116] has an extensive discussion on System V Interprocess Communication including semaphores, shared memory and message queues.

                Оставьте свой комментарий !

                Ваше имя:
                Комментарий:
                Оба поля являются обязательными

                 Автор  Комментарий к данной статье
                efbhergtklnlllllllll
                  m,l;egtmbh;lmetbh;m
                2012-04-17 14:45:03
                Bawe NMR
                  KoMMeHTapNN:
                2012-04-17 14:46:10