Interprocess Communication

Thus far, we've been investigating the design of processes that do not interact with other processes.

This property of process independence is attractive in some respects because it can simplify security concerns.

However, there are also merits to considering processes that share resources or work collaboratively toward some goal.

Processes can either be independent if their execution is not contingent on the actions of other processes, or cooperative if data is shared between processes.

What are some reasons why processes may wish to share data or otherwise cooperate?

Among the many reasons:

  1. Information sharing: several users may be using the same resource (like a shared file) and concurrent access must be provided by OS.

  2. Parallelism: tasks can be broken down into smaller subcomponents, and multi-processor environments can exploit division of labor into multiple processes to accelerate the task.

  3. Modularity: similar to parallelism, but divided in a way that is simply clean code-base design or separation of concerns.

As the name suggests, cooperative processes must do just that: cooperate, which generally entails that they be able to communicate and share resources.

Thus, to enable this cooperation, we must equip processes with the means of exchanging information.

Interprocess Communication (IPC) defines the methods and protocols by which separate processes can share information.

There are two main approaches that implement IPC in modern OS design. Try to consider how processes might implement IPC.

Propose some means of enabling shared data / IPC.

The two main approaches used in practice are:

  1. Message passing: Kernel maintains a message queue for each process that other processes can send messages to.

  2. Shared memory: memory is reserved for cooperating processes to use communally.

Pictorially (again, shamelessly stolen from your textbook), these strategies look like:


What are some pros and cons of the message passing vs. shared memory IPC implementations?

  • Message passing:

    • Pros: easy to manage, useful for small amounts of shared information, no conflicts have to be considered.

    • Cons: must send and receive via system calls (interrupts costly), doesn't scale for larger shared data requirements

  • Shared memory:

    • Pros: fast access since memory resides in RAM and need not read / write via system calls, larger storage capacity.

    • Cons: synchronization between processes (have to make sure to "play nice" with shared resources).


Because message passing tends to be a little more small-scale, and some of the challenges with shared memory motivate later sections of the course, we'll focus on shared memory systems next.



Shared Memory

Shared memory is a form of indirect communication by which messages are left in some shared "mailbox" by a poster, and then can be retrieved at will by a receiver.

Since we typically limit the size of the mailbox, it is known as a bounded buffer storage medium, in which there is a maximum number of messages that can be stored at any time.

The idea of a mailbox is abstract and can accommodate any sort of data structure in which to store shared information.

Let's look at the mechanics in POSIX systems!


POSIX Shared Memory


(in case you hadn't seen it defined before, POSIX standards are those that govern the APIs for Unix OS', and stands for "Portable Operating System Interface").

The typical shared memory reservation process can be accomplished in the following steps using the following system calls (look up the documentation on the arguments separately, no need to do that in these notes):

Step

Syscall

Description

0

shmget(IPC_PRIVATE, size, S_IRUSR | S_IWUSR)

(Shared Memory Get) Reserves the specified size amount of memory and returns a unique segment ID for that mailbox.

1

shmat(segment_id, NULL, 0)

(Shared Memory Attach) Returns a (void*) pointer to the given shared memory block as indicated by segment_id.

2

shmdt(segment_id)

(Shared Memory Detach) Detaches the shared memory segment from this process (updates some shared memory tables in kernel).

3

shmctl(segment_id, IPC_RMID, NULL)

(Shared Memory Control) provides information about shared memory segments, but with IPC_RMID, marks the segment to be destroyed (i.e., memory freed).


Let's look at some examples!

Example 1: Basic syntax for shared memory allocation and release within a single process (lifted and modifed from textbook).

  #include <stdio.h>
  #include <sys/shm.h>
  #include <sys/stat.h>
  
  int main () {
      int seg_id;
      char* shared_mem;
      const int SIZE = 4096;
      
      // Allocate shared memory
      seg_id = shmget(IPC_PRIVATE, SIZE, S_IRUSR | S_IWUSR);
      
      // Attach shared mem
      shared_mem = (char*) shmat(seg_id, NULL, 0);
  
      // Write to shared mem
      sprintf(shared_mem, "wassup");
  
      // Verify that it got written
      printf("%s\n", shared_mem);
  
      // Detach from shared memory
      shmdt(shared_mem);
  
      // Free shared mem
      shmctl(seg_id, IPC_RMID, NULL);
  }

Example 2: Tiny mailbox example with 1 parent and child process.

  #include <stdio.h>
  #include <sys/shm.h>
  #include <sys/stat.h>
  #include <unistd.h>
  #include <string.h>
  
  int main () {
      int seg_id;
      char* shared_mem;
      const int SIZE = 4096;
      const int LIFE = 20;
      pid_t pid;
  
      // Allocate shared memory
      seg_id = shmget(IPC_PRIVATE, SIZE, S_IRUSR | S_IWUSR);
      
      pid = fork(); 
      
      // Child process
      if (pid == 0) {
          shared_mem = (char*) shmat(seg_id, NULL, 0);
          for (int lifespan = LIFE; lifespan > 0; lifespan--) {
              if (strlen(shared_mem)) {
                  printf("%s\n", shared_mem);
                  shared_mem[0] = '\0'; // Message received - wipe it!
              } else {
                  printf("No mail for me :(\n");
              }
              sleep(2); // Take a nap
          }
          shmdt(shared_mem);
      
      // Parent Process
      } else {
          shared_mem = (char*) shmat(seg_id, NULL, 0);
          for (int lifespan = LIFE; lifespan > 0; lifespan--) {
              sprintf(shared_mem, "wassup");
              sleep(3);
          }
          shmdt(shared_mem);
          shmctl(seg_id, IPC_RMID, NULL);
      }
      
      return 0;
  }

That's it for today! We'll continue with shared resources in future lectures... much excitement to come!



  PDF / Print