Saturday 3 December 2016

Interprocess communication

Interprocess communication
      Interprocess communication (IPC) is the transfer of data among processes.
      There are various ways of communicating between parent and child processes, between unrelated processes and even between processes on different machines
      Types of communications

Types of IPC

  • Signals: which are used to indicate that an event has occurred.
  • Shared memory: permits processes to communicate by simply reading and writing to a specified memory location.
  • Pipes: permit sequential communication from one process to a related process.
  • FIFOs: are similar to pipes, except that unrelated processes can communicate because the pipe is given a name in the filesystem.
  • Sockets: support communication between unrelated processes even on different computers

Signals
Signals are often described as “software interrupts.” The arrival of a signal informs a process that some event or exceptional condition has occurred. There are various types of signals, each of which identifies a different event or condition. Each signal type is identified by a different integer, defined with symbolic names of the form SIGxxxx.
Signals are sent to a process by the kernel, by another process, or by the process itself. For example, the kernel may send a signal to a process when one of the following occurs:
       the user typed the interrupt by Control-C on the keyboard;
       one of the process’s children has terminated;
       a timer (alarm clock) set by the process has expired; or
       the process attempted to access an invalid memory address.

Within the shell, the kill command can be used to send a signal to a process. The kill() system call provides the same facility within programs.
When a process receives a signal, it takes one of the following actions, depending on the signal:
       it ignores the signal;
       it is killed by the signal; or
       it is suspended until later being resumed by receipt of a special-purpose signal.

Shared memory
      One of the simplest interprocess communication methods is using shared memory.
      Shared memory allows two or more processes to access the same memory as if they all called malloc and were returned pointers to the same actual memory.
      When one process changes the memory, all the other processes see the modification.
      Shared memory is the fastest form of interprocess communication because all processes share the same piece of memory.
     Access to this shared memory is as fast as accessing a process’s nonshared memory, and it does not require a system call or entry to the kernel. It also avoids copying data unnecessarily.
      Because the kernel does not synchronize accesses to shared memory, you must provide your own synchronization.
     For example, a process should not read from the memory until after data is written there, and two processes must not write to the same memory location at the same time. A common strategy to avoid these race conditions is to use semaphores

Shared memory allocation
      A process allocates a shared memory segment using shmget (“SHared Memory GET”).
     Its first parameter is an integer key that specifies which segment to create.
     Its second parameter specifies the number of bytes in the segment. Because segments are allocated using pages, the number of actually allocated bytes is rounded up to an integral multiple of the page size.
     The third parameter is the bitwise or of flag values that specify options to shmget. The flag values include these:
      IPC_CREAT—this flag indicates that a new segment should be created.
      Mode flags—this value is made of 9 bits indicating permissions (R, W, X) granted to owner, group, and world to control access to the segment. .An easy way to specify permissions is to use the constants defined in <sys/stat.h>.
      For e.g. S_IRUSR and S_IWUSR specify read and write permissions for the owner of the shared memory segment, and S_IROTH and S_IWOTH specify read and write permissions for others.
     E.g
int segment_id = shmget (shm_key, getpagesize (), IPC_CREAT | S_IRUSR | S_IWUSER);
      If the call succeeds, shmget returns a segment identifier.        
     Synopsis
      #include <sys/ipc.h>
      #include <sys/shm.h>
      int shmget(key_t key, size_t size, int shmflg);

Attachment and detachment
      To make the shared memory segment available, a process must use shmat, “Shared Memory ATtach.”
     First parameter is the shared memory segment identifier SHMID returned by shmget.
     The second argument is a pointer that specifies where in your process’s address space you want to map the shared memory; if you specify NULL, Linux will choose an available address.
     The third argument is a flag, which can include the following:
      SHM_RND indicates that the address specified for the second parameter should be rounded down to a multiple of the page size. If you don’t specify this flag, you must page-align the second argument to shmat yourself.
      SHM_RDONLY indicates that the segment will be only read, not written.
     If the call succeeds, it returns the address of the attached shared segment.
     Synonpsis
      #include <sys/types.h>
      #include <sys/shm.h>
      void *shmat(int shmid, const void *shmaddr, int shmflg);

Dettachment
      When you’re finished with a shared memory segment, the segment should be detached using shmdt (“SHared Memory DeTach”).
     Pass it the address returned by shmat.
     If the segment has been deallocated and this was the last process using it, it is removed.
     Calls to exit and any of the exec family automatically detach segments.
     Synopsis
      #include <sys/types.h>
      #include <sys/shm.h>
      int shmdt(const void *shmaddr);

Controlling and Deallocating Shared Memory
      The shmctl (“SHared Memory ConTroL”) call returns information about a shared memory segment and can modify it.
     The first parameter is a shared memory segment identifier.
     The second parameter is the operation to be performed on the shared memory
      IPC_STAT To obtain information about a shared memory segment,
      IPC_SET Write the values of some members of the shmid_ds structure pointed to by buf to the kernel data structure associated with this shared memory segment
      IPC_RMID Remove the segment
      The segment is removed when the last process that has attached it finally detaches it.
      Each shared memory segment should be explicitly deallocated using “shmctl” when you’re finished with it, to avoid violating the system wide limit on the total number of shared memory segments. Invoking exit and exec detaches memory segments but does not deallocate them.
      SYNOPSIS
     #include <sys/ipc.h>
     #include <sys/shm.h>
     int shmctl(int shmid, int cmd, struct shmid_ds *buf);
     The buf argument is a pointer to a shmid_ds structure, defined in <sys/shm.h> as follows:
     struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
 size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat/shmdt */
shmatt_t shm_nattch; /* No. of current attaches */ ...
};

Example of shared memory
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{        int segment_id;
          char* shared_memory;
          struct shmid_ds shmbuffer;
          int segment_size;
          const int shared_segment_size = 0x6400;
          /* Allocate a shared memory segment.  */
          segment_id = shmget (IPC_PRIVATE,      shared_segment_size,
                   IPC_CREAT | IPC_EXCL | S_IRUSR |     S_IWUSR);
          /* Attach the shared memory segment.  */
          shared_memory = (char*) shmat (segment_id, 0, 0);
          printf (“shared memory attached at address %p\n”, shared_memory);
          /* Determine the segment’s size.  */
          shmctl (segment_id, IPC_STAT, &shmbuffer);
          segment_size = shmbuffer.shm_segsz;
          printf (“segment size: %d\n”, segment_size);
          /* Determine the segment’s size.  */
          shmctl (segment_id, IPC_STAT, &shmbuffer);
          segment_size = shmbuffer.shm_segsz;
          printf (“segment size: %d\n”, segment_size);
/* Write a string to the shared memory segment.  */
          sprintf (shared_memory, “Hello, world.”);
/* Detach the shared memory segment.  */
          shmdt (shared_memory);
/* Reattach the shared memory segment, at a different address.  */
          shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
          printf (“shared memory reattached at address %p\n”, shared_memory);
/* Print out the string from shared memory.  */
          printf (“%s\n”, shared_memory);
/* Detach the shared memory segment.  */
          shmdt (shared_memory);
/* Deallocate the shared memory segment.  */
          shmctl (segment_id, IPC_RMID, 0);
          return 0;
}

Debugging
      The “ipcs” command provides information on interprocess communication facilities, including shared segments.
      Use the -m flag to obtain information about shared memory. For example, this code illustrates that one shared memory segment,
      numbered 1627649, is in use:
      $ ipcs -m
------ Shared Memory Segments --------
key       shmid     owner     perms     bytes     nattch    status
0x00000000 1627649   user    640       25600     0
      If this memory segment was erroneously left behind by a program, you can use the “ipcrm” command to remove it.
      $ipcrm shm 1627649

Pros and cons of share memory
      Shared memory segments permit fast bidirectional communication among any number of processes.
      Each user can both read and write, but a program must establish and follow some protocol for preventing race conditions such as overwriting information before it is read.
      Also, for multiple processes to use a shared segment, they must make arrangements to use the same key

Pipes
      A pipe is a communication device that permits unidirectional communication.
      Data is written to the “write end” of the pipe and is read back from the “read end.”
      Pipes are serial devices; the data is always read from the pipe in the same order it was written.
      Typically, a pipe is used to communicate between two threads in a single process or between parent and child processes.
      In a shell, the symbol “| “creates a pipe.
      For example, this shell command causes the shell to produce two child processes, one for ls and one for less:
      $ ls | less
     The shell also creates a pipe connecting the standard output of the “ls” subprocess with the standard input of the “less” process.
     The filenames listed by “ls” are sent to “less” in exactly the same order as if they were sent directly to the terminal.
      A pipe’s data capacity is limited. If the writer process writes faster than the reader process consumes the data, and if the pipe cannot store more data, the writer process blocks until more capacity becomes available.
      If the reader tries to read but no data is available, it blocks until data becomes available. Thus, the pipe automatically synchronizes the two processes
      To create a pipe, invoke the “pipe” command.
      Synonpsis
     #include <unistd.h>
     int pipe(int pipefd[2]);
     Supply an integer array of size 2. The array is used to return two file descriptors referring to ends of the pipe
     The call to pipe stores the reading file descriptor in array position 0 and the writing file descriptor in position 1.
     For example, consider this code:
int pipe_fds[2];
int read_fd;
int write_fd;
pipe (pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];

Communication between Parent and Child Processes
      A call to pipe creates file descriptors, which are valid only within that process and its children.
      A process’s file descriptors cannot be passed to unrelated processes; however, when the process calls fork, file descriptors are copied to the new child process.
      Thus, pipes can connect only related  processes

Example: The parent writes the string contained in the program's command-line argument to the pipe, and the child reads this string a byte at a time from the pipe and echoes it on standard output.
#include <sys/wait.h>
#include <stdio.h>
 #include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]) {      
int pipefd[2];
          pid_t cpid;
          char buf;
          if (argc != 2) {
                    exit(EXIT_FAILURE);
          }
          if (pipe(pipefd) == -1) {
                   exit(EXIT_FAILURE);
          }
          cpid = fork();
          if (cpid == -1) {   
                   exit(EXIT_FAILURE);
          }
if (cpid == 0) {
/* Child reads from pipe */
          close(pipefd[1]); // Close unused write end
          while (read(pipefd[0], &buf, 1) > 0) write(STDOUT_FILENO, &buf, 1);
          close(pipefd[0]); exit(EXIT_SUCCESS);
}
else { /* Parent writes argv[1] to pipe */
          close(pipefd[0]); /* Close unused read end */
          write(pipefd[1], argv[1],  strlen(argv[1]));
          close(pipefd[1]);
          wait(NULL); /* Wait for child */ exit(EXIT_SUCCESS); } }

FIFOs
      A first-in, first-out (FIFO) file is a pipe that has a name in the filesystem. Any process can open or close the FIFO; the processes on either end of the pipe need not be related to each other.
      FIFOs are also called named pipes.
      You can make a FIFO using the “mkfifo” command. Specify the path to the FIFO on the command line. For example, create a FIFO in /tmp/fifo by invoking this command:
      $ mkfifo /tmp/fifo
      $ ls -l /tmp/fifo
prw-rw-rw-    1 samuel   users           0 Jan 16 14:04 /tmp/fifo
     The first character of the output from ls is p, indicating that this file is actually a FIFO (named pipe).

Creating a FIFO
      Create a FIFO programmatically using the mkfifo function.
      Synopsis
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
     The first argument is the path at which to create the FIFO;
     the second parameter specifies the pipe’s owner, group, and world permissions,

Accessing a FIFO
      Access a FIFO just like an ordinary file.
      To communicate through a FIFO, one program must open it for writing, and another program must open it for reading.
      Either low-level I/O functions (open, write, read, close, and so on) or C library I/O functions (fopen, fprintf, fscanf, fclose, and so on) may be used.
      For example, to write a buffer of data to a FIFO using low-level I/O routines, you could use this code:
int fd = open (fifo_path, O_WRONLY);
write (fd, data, data_length);
close (fd);
      Bytes from each writer are written atomically up to a maximum size of PIPE_BUF (4KB on Linux)

Sockets
      A socket is a bidirectional communication device that can be used to communicate with another process on the same machine or with a process running on other machines.

      Sockets are the only interprocess communication we’ll discuss in this session that permit communication between processes on different computers.

      Internet programs such as Telnet, FTP, and the World Wide Web use sockets


No comments:

Post a Comment