Linux : Inter-process communication

Linux : Inter-process communication

  • file
  • signal
  • socket
  • unix domain socket
  • Similar to an internet socket, but all communication occurs within the kernel.
  • pipes
  • 
           int pipe(int pipefd[2]);
    
    pipe() returns 2 file descriptors: pipefd[0] is open for reading and pipefd[1] is open for writing. Normally the process that calls pipe() then call fork(), creating IPC channel from the parent to the child or vice versa.
  • named pipe(FIFO)
  • 
           int mkfifo(const char *pathname, mode_t mode);
    
    Creating a FIFO is similar to creating a file. A pipe that is treated like a file. Processes write to and read from a named pipe, as if it were a regular file. If we write to a FIFO that no process has open for reading, the signal SIGPIPE is generated.
  • message queue
  • Message queues are a linked list of messages stored within the kernel and identified by a message queue identifier.
  • shared memory
  • Shared memory allows 2 or more processes to share a given region of memory. This is the fastest form of IPC because the data does not to be copied between the client and server. The only trick is to synchronize access to a given region among multiple processes. Often, semaphores are used to synchronize shared memory access.
  • semaphores
  • A semaphore is a counter used to provide access to a shared data object for multiple process. To obtain a shared resource a process needs to do the following:
    • Test the semaphore that controls the resource
    • If the value of the semaphore is positive, the process can use the resource
    • The process decrements the semaphore value by 1, indicating that it has used one unit of the resource.
    • If the value of the semaphore is 0, the process goes to sleep until the semaphore is greater than 0
  • memory-mapped I/O
  • On modern operating systems, it is possible to mmap (pronounced “em-map”) a file to a region of memory. When this is done, the file can be accessed just like an array in the program. Memory mapping only works on entire pages of memory. Thus, addresses for mapping must be page-aligned, and length values will be rounded up.
    
    void * mmap (void *address, size_t length, int protect, int flags, int filedes, off_t offset)
    
    The mmap function creates a new mapping, connected to bytes (offset) to (offset + length - 1) in the file open on filedes. filedes is the file descriptor specifying the file that is to be mapped. address gives a preferred starting address for the mapping. NULL expresses no preference.
    memory                    file
    
    high address ---                    ----- end of file
    
                  ---         <----      -----
    
                                          len
    
       start addr ---         <----      ----- 
    
             heap ---                     offset
    
    low address   ---                    ----- start of file
    

Mutex lock for Linux Thread Synchronization


When one thread starts executing the critical section (a serialized segment of the program) the other thread should wait until the first thread finishes.

The most popular way of achieving thread synchronization is by using Mutexes.
  • A Mutex is a lock that we set before using a shared resource and release after using it.
  • When the lock is set, no other thread can access the locked region of code.
  • So we see that even if thread 2 is scheduled while thread 1 was not done accessing the shared resource and the code is locked by thread 1 using mutexes then thread 2 cannot even access that region of code.
  • So this ensures synchronized access of shared resources in the code.

Working on a mutex:
  • mutex creation and initialization
  • 
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr) 
    
    The pthread_mutex_init() function shall initialize the mutex referenced by mutex with attributes specified by attr. If attr is NULL, the default mutex attributes are used.

    In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialize mutexes that are statically allocated. The effect shall be equivalent to dynamic initialization by a call to pthread_mutex_init() with parameter attr specified as NULL, except that no error checks are performed.

    
    static pthread_mutex_t foo_mutex;
    pthread_mutex_init(&foo_mutex, NULL);
    
    static pthread_mutex_t foo_mutex_2 = PTHREAD_MUTEX_INITIALIZER;
    
    
  • int pthread_mutex_lock(pthread_mutex_t *mutex)
  • Locks a mutex object. If the mutex is already locked by another thread, the thread waits for the mutex to become available. The thread that has locked a mutex becomes its current owner and remains the owner until the same thread has unlocked it.
  • int pthread_mutex_unlock(pthread_mutex_t *mutex)
  • Releases a mutex object.
  • int pthread_mutex_destroy(pthread_mutex_t *mutex)
  • Deletes a mutex object

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
  
pthread_t tid[2]; 
int counter; 
pthread_mutex_t lock; 
  
void* trythis(void* arg) 
{ 
    pthread_mutex_lock(&lock); 
  
    unsigned long i = 0; 
    counter += 1; 
    printf("\n Job %d has started\n", counter); 
  
    for (i = 0; i < (0xFFFFFFFF); i++) 
        ; 
  
    printf("\n Job %d has finished\n", counter); 
  
    pthread_mutex_unlock(&lock); 
  
    return NULL; 
} 
  
int main(void) 
{ 
    int i = 0; 
    int error; 
  
    if (pthread_mutex_init(&lock, NULL) != 0) { 
        printf("\n mutex init has failed\n"); 
        return 1; 
    } 
  
    while (i < 2) { 
        error = pthread_create(&(tid[i]), 
                               NULL, 
                               &trythis, NULL); 
        if (error != 0) 
            printf("\nThread can't be created :[%s]", 
                   strerror(error)); 
        i++; 
    } 
  
    pthread_join(tid[0], NULL); 
    pthread_join(tid[1], NULL); 
    pthread_mutex_destroy(&lock); 
  
    return 0; 
} 

Conditional wait and signal in multi-threading


When you want to sleep a thread, condition variable can be used.

  • there is a function pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex) to wait or sleep.
  • The pthread_cond_wait() release a lock specified by mutex and wait on condition cond variable.
  • there is a function pthread_cond_signal(pthread_cond_t *cond) to wake up sleeping or waiting thread.


#include <pthread.h> 
#include <stdio.h> 
#include <unistd.h> 
  
// Declaration of thread condition variable 
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; 
  
// declaring mutex 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 
  
int done = 1; 
  
// Thread function 
void* foo() 
{ 
  
    // acquire a lock 
    pthread_mutex_lock(&lock); 
    if (done == 1) { 
  
        // let's wait on conition variable cond1 
        done = 2; 
        printf("Waiting on condition variable cond1\n"); 
        pthread_cond_wait(&cond1, &lock); 
    } 
    else { 
  
        // Let's signal condition variable cond1 
        printf("Signaling condition variable cond1\n"); 
        pthread_cond_signal(&cond1); 
    } 
  
    // release lock 
    pthread_mutex_unlock(&lock); 
  
    printf("Returning thread\n"); 
  
    return NULL; 
} 
  
// Driver code 
int main() 
{ 
    pthread_t tid1, tid2; 
  
    // Create thread 1 
    pthread_create(&tid1, NULL, foo, NULL); 
  
    // sleep for 1 sec so that thread 1 
    // would get a chance to run first 
    sleep(1); 
  
    // Create thread 2 
    pthread_create(&tid2, NULL, foo, NULL); 
  
    // wait for the completion of thread 2 
    pthread_join(tid2, NULL); 
  
    return 0; 
} 

Multithreaded Programming Guide


Chapter 4 Programming with Synchronization Objects


Synchronization objects are variables in memory that you access just like data.
Threads in different processes can communicate with each other through synchronization objects placed in threads-controlled shared memory, even though the threads in different processes are generally invisible to each other.

The available types of synchronization objects are:
  • Mutex Locks
  • Condition Variables
  • Semaphores

"Using Condition Variables"


  • Initialize a Condition Variable
  • 
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    
    The pthread_cond_init() function shall initialize the condition variable referenced by cond with attributes referenced by attr. If attr is NULL, the default condition variable attributes shall be used. the macro PTHREAD_COND_INITIALIZER can be used to initialize condition variables that are statically allocated. The effect shall be equivalent to dynamic initialization by a call to pthread_cond_init() with parameter attr specified as NULL.
  • Block on a Condition Variable
  • 
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    
    
    The pthread_cond_timedwait() and pthread_cond_wait() functions shall block on a condition variable. They shall be called with mutex locked by the calling thread. These functions atomically release mutex and block the calling thread until the condition variable cond is signaled. The blocked thread can be awakened by a pthread_cond_signal(), a pthread_cond_broadcast(), or when interrupted by delivery of a signal.
    When the call returns, the mutex is locked again.
  • Un-block on a Condition Variable
  • 
    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);
    
    The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond. The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond. The thread(s) that are unblocked shall contend for the mutex.
  • Destroy Condition Variable State
  • 
    int pthread_cond_destroy(pthread_cond_t *cond);
    
    The pthread_cond_destroy() function shall destroy the given condition variable specified by cond; the object becomes, in effect, uninitialized.
  • Condition wait semantics
  • It is recommended that a condition wait be enclosed in a "while loop" that checks the predicate.
    
    pthread_mutex_lock(&condition_lock);
    while ( condition_predicate is not TRUR)
            pthread_cond_wait(&condition_variable, &condition_lock);
    ...
    pthread_mutex_unlock(&condition_lock);
    
  • The Producer/Consumer Problem
    • a finite-size buffer
    • 
      typedef struct {
          char buf[BSIZE];
          int occupied;
          int nextin;
          int nextout;
          pthread_mutex_t mutex;
          pthread_cond_t more;
          pthread_cond_t less;
      } buffer_t;
      
      buffer_t buffer;
      
    • two classes of threads, producers put items into the buffer, consumers take items out of the buffer.
    • 
      void producer(buffer_t *b, char item)
      {
          pthread_mutex_lock(&b->mutex);
         
          while (b->occupied >= BSIZE)
              pthread_cond_wait(&b->less, &b->mutex); // wait for more space
      
          assert(b->occupied < BSIZE);
      
          b->buf[b->nextin++] = item;
      
          b->nextin %= BSIZE;
          b->occupied++;
      
          /* now: either b->occupied < BSIZE and b->nextin is the index
             of the next empty slot in the buffer, or
             b->occupied == BSIZE and b->nextin is the index of the
             next (occupied) slot that will be emptied by a consumer
             (such as b->nextin == b->nextout) */
      
          pthread_cond_signal(&b->more);
      
          pthread_mutex_unlock(&b->mutex);
      }
      
      char consumer(buffer_t *b)
      {
          char item;
          pthread_mutex_lock(&b->mutex);
          while(b->occupied <= 0)
              pthread_cond_wait(&b->more, &b->mutex); // wait for more data
      
          assert(b->occupied > 0);
      
          item = b->buf[b->nextout++];
          b->nextout %= BSIZE;
          b->occupied--;
      
          /* now: either b->occupied > 0 and b->nextout is the index
             of the next occupied slot in the buffer, or
             b->occupied == 0 and b->nextout is the index of the next
             (empty) slot that will be filled by a producer (such as
             b->nextout == b->nextin) */
      
          pthread_cond_signal(&b->less);
          pthread_mutex_unlock(&b->mutex);
      
          return(item);
      }
      

留言

熱門文章