Skip to main content

Inter Process Communication-Shared Memory

Interprocess communication (IPC) is a set of programming interfaces that allow a programmer to coordinate activities among different program processes that can run concurrently in an operating system. This allows a program to handle many user requests at the same time. Since even a single user request may result in multiple processes running in the operating system on the user's behalf, the processes need to communicate with each other. The IPC interfaces make this possible. Each IPC method has its own advantages and limitations so it is not unusual for a single program to use all of the IPC methods.
Shared Memory Communication
One very commonly employed strategy for IPC is to use shared memory. Ordinarily, processes use memory areas within the scope of virtual memory space. However, memory management systems ensure that every process has a well-defined and distinct data and code area. For shared memory communication, one process would write into a certain commonly accessed area and another process would read subsequently from that area. One other point which we can debate is: do the processes have to be related? We have seen that a parent may share a data area or files with a child. Also, by using the exec() function call we may be able to populate a process with another code segment or data. Clearly, the shared memory method can allow access to a common data area even amongst the processes that are not related. However, in that case an area like a process stack may not be shareable. Also, it should be noted that it is important that the shared data integrity may get compromised when an arbitrary sequence of reads and writes occurs. To maintain data integrity, the access is planned carefully under a user program control. That then is the key to shared memory protocol.
The shared memory model has the following steps of execution.
1. First we have to set up a shared memory mechanism in the kernel.
 2. Next an identified “safe area" is attached to each of the processes.
3. Use this attached shared data space in a consistent manner.
4. When finished, detach the shared data space from all processes to which it was attached.
 5. Delete the information concerning the shared memory from the kernel.
Two important .h files in this context are: shm.h and ipc.h which are included in all the process definitions. The first step is to set up shared memory mechanism in kernel. The required data structure is obtained by using shmget() system call with the following syntax
int shmget( key_t key, int size, int flag );
The parameter key_t is usually a long int. It is declared internally as key_t key. key_t is an alias defined in sys/types.h using a typedef structure. If this key is set to IPC_PRIVATE, then it always creates a shared memory region. The second parameter, size is the size of the sh-mem-region in bytes. The third parameter is a combination of usual file access permissions of r/w/e for o/g/w
struct shmid_ds
{ struct ipc_perm shm_perm;
 int shm_seg_segsz /* size of segments in bytes */
struct region *shm_reg; /* pointer to region struct */
 char pad[4]; /* for swap compatibility */
ushort shm_lpid; /* pid of last shmop */
 ushort shm_cpid; /* pid of creator */
ushort shm_nattch; /* used for shm_info */
ushort shm_cnattch; /* used for shm_info */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
 }
Once this is done we would have created a shared memory data space. The next step requires that we attach it to processes that would share it. This can be done using the system call shmat(). The system call shmat() has its syntax shown below.
char *shmat( int shmid, char *shmaddr, int shmflg );
The second argument should be set to zero as in (char *)0, if the kernel is to determine the attachment. The system uses three possible flags which are: SHM_RND, SHM_RDONLY and the combination SHM_RND | SHM_RDONLY. The SHM_RDONLY flag indicates the shared region is read only. Otherwise, it is both for read and write operations. The flag SHM_RND requires that the system enforces use of the byte address of the shared memory region to coincide with a double word boundary by rounding.
Now that we have a well-defined shared common area, reading and writing can be done in this shared memory region. However, the user must write a code to ensure locking of the shared region. For instance, we should be able to block a process attempting to write while a reader process is reading. This can be done by using a synchronization method such as semaphores. In most versions of Unix, semaphores are available to enforce mutual exclusion. At some stage a process may have finished using the shared memory region. In that case this region can be detached for that process. This is done by using the shmdt() system call. This system call detaches that process from future access. This information is kept within the kernel data-space. The system call shmdt() takes a single argument, the address of the shared memory region. The return value from the system call is rarely used except to check if an error has occurred (with -1 as the return value).
The last step is to clean up the kernel's data space using the system call shmctl(). The system call shmctl() takes three parameters as input, a shared memory id, a set of flags, and a buffer that allows copying between the user and the kernel data space. A considerable amount of information is pointed to by the third parameter. A call to shmctl() with the command parameter set to IPC_STAT gives the following information.
 User's id
Creator's group id 
Operation permissions
Key ™ segment size
 Process id of creator *
Current number of attached segments in the memory.
Last time of attachment
User's group id ™
Creator's id ™
Last time of detachment ™
Last time of change ™
Current no. of segments attached ™
Process id of the last shared memory operation

Now let us examine the shmget() system call.
int shmget( key_t key, int region_size, int flags );
Here key is a user-defined integer, the size of the shared region to be attached is in bytes. The flags usually turn on the bits in IPC_CREAT. Depending upon whether there is key entry in the kernel's shared memory table, the shmget() call takes on one of the following two actions. If there is an entry, then shmget() returns an integer indicating the position of the entry. If there is no entry, then an entry is made in the kernel's shared memory table.
 Also, note that the size of the shared memory is specified by the user. It, however, should satisfy some system constraints which may be as follows.
struct shminfo
 { int shmmax, /* Maximum shared memory segment size 131072 for some */
 shmmin, /* minimum shared memory segment size 1 for some */
shmni, /* No. of shared memory identifiers */
shmseg, /* Maximum attached segments per process */
shmall; /* Max. total shared memory system in pages */
};
 The third parameter in shmget() corresponds to the flags which set access permissions as shown below: 400 read by user ...... Typically in shm.h file as constant SHM_R
200 write by user .......Typically in shm.h file as constant SHM_W
040 read by group
020 write by group
 004 read by others
002 read by others ......All these are octal constants. For example, let us take a case where we have read/write permissions by the user's group and no access by others. To be able to achieve this we use the following values. SHM_R | SHM_W | 0040 | IPC_CREAT as a flag to a call to shmget().

Now consider the shmat() system call.
char *shmat( int shmid, char *address, int flags );
This system call returns a pointer to the shared memory region to be attached. It must be preceded by a call to shmget(). The first argument is a shmid (returned by shmget()). It is an integer. The second argument is an address. We can let the compiler decide where to attach the shared memory data space by giving the second argument as (char *) 0. The flags in arguments list are to communicate the permissions only as SHM_RND and SHM_RDONLY.

The shmdt() system call syntax is as follows: int shmdt(char * addr ); This system call is used to detach. It must follow a call shmat() with the same base address which is returned by shmat().

The last system call we need is shmctl(). It has the following syntax.
 int shmctl( int shmid, int command, struct shm_ds *buf_ptr );
The shmctl() call is used to change the ownership and permissions of the shared region. The first argument is the one earlier returned by shmget() and is an integer. The command argument has five possibilities:
• IPC_STAT : returns the status of the associated data structure for the shared memory pointed by buffer pointer.
• IPC_RMID : used to remove the shared memory id.
• SHM_LOCK : used to lock
• SHM_UNLOCK : used to unlock
• IPC_SET : used to set permissions.

When a region is used as a shared memory data space it must be from a list of free data space. Based on the above explanations, we can arrive at the code given below.
#nclude  <stdio.h>`
#include <string.h
 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#define MAXBYTES 4096 /* Maximum bytes per shared segment */
main(int argc; char *argv[])
{ /* Inter process communication using shared memory */
 char message[MAXBYTES];
int i, message_num, j, no_of_mess, nbytes;
 int key = getpid();
int semid; int segid;
char *addr;
 if (argc != 3) {
          printf("Usage : %s num_messages");
         printf("num_of_bytes \n", argv[0]); exit(1);
     }
else {
        no_of_mess = atoi(argv[1]);
       nbytes = atoi(argv[2]);
     if (nbytes > MAXBYTES)
    nbytes = MAXBYTES;
   if ( (semid=semget( (key_t)key, 1, 0666 | IPC_CREAT ))== -1)
    { printf("semget error \n"); exit(1); }
     /* Initialise the semaphore to 1 */
    V(semid);
   if ( (segid = shmget( (key_t) key, MAXBYTES, 0666 | IPC_CREAT ) ) == -1 )
         { printf("shmget error \n"); exit(1); }      /*if ( (addr = shmat(segid, (char * )0,0)) == (char *)-1) */
   if ( (addr = shmat(segid, 0, 0)) == (char *) -1 )
        { printf("shmat error \n"); exit(1); }
  switch (fork())
  { case -1 : printf("Error in fork \n"); exit(1);
   case 0 : /* Child process, receiving messages */
              for (i=0; i < no_of_mess; i++)
                  if(receive(semid, message, sizeof(message))); exit(0);
                   default : /* Parent process, sends messages */
             for ( i=0; i < no_of_mess; i++) { for ( j=i; j < nbytes; j++) message[j] = 'd';
                  if (!send(semid, message, sizeof(message)))
                                 printf("Cannot send the message \n");
               }  /* end of for loop */
     } /* end of switch */
  } /* end of else part */
  }
/****************************************/
/* Semaphores */
#nclude  <stdio.h>`
#include <string.h
 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
int sid;
cleanup(int semid,int  segid, char *addr)

{ int status;
/* wait for the child process to die first */
/* removing semaphores */
 wait(&status);
semctl(semid, 0, IPC_RMID, 0);
shmdt(addr);
shmctl(segid, 0, IPC_RMID, 0);
}
P(int sid;)
{ /* Note the difference in this and previous structs */
 struct sembuf *sb;
sb = (struct sembuf *) malloc(sizeof(struct sembuf *));
 sb -> sem_num = 0;
sb -> sem_op = -1;
sb -> sem_flg = SEM_UNDO;
 if( (semop(sid, sb, 1)) == -1) puts("semop error");
}
V( int sid)
 { struct sembuf *sb;
sb = (struct sembuf *) malloc(sizeof(struct sembuf *));
 sb -> sem_num = 0;
sb -> sem_op = 1;
sb -> sem_flg = SEM_UNDO;
 if( (semop(sid, sb, 1)) == -1) puts("semop error");
 }
/* send message from addr to buf */
send(int semid, char *addr, char *buf, int nbytes)
{
P(semid);
 memcpy(addr, buf, nbytes);
V(semid);
 }
/* receive message from addr to buf */
receive(int semid, char *addr, char *buf, int nbytes)
{ P(semid);
memcpy(buf, addr, nbytes);
V(semid);
}

From the programs above, we notice that any process is capable of accessing the shared memory area once the key is known to that process. This is one clear advantage over any other method. Also, within the shared area the processes enjoy random access for the stored information. This is a major reason why shared memory access is considered efficient. In addition, shared memory can support many-to-many communication quite easily.

Comments

Popular posts from this blog

Server/Client Communication-python

The basic mechanisms of client-server setup are:

A client app send a request to a server app. The server app returns a reply.  Some of the basic data communications between client and server are:
File transfer - sends name and gets a file. Web page - sends url and gets a page. Echo - sends a message and gets it back.  Client server communication uses socket.
             To connect to another machine, we need a socket connection. What's a connection?  A relationship between two machines, where two pieces of software know about each other. Those two pieces of software know how to communicate with each other. In other words, they know how to send bits to each other. A socket connection means the two machines have information about each other, including network location (IP address) and TCP port. (If we can use anology, IP address is the phone number and the TCP port is the extension).  A socket is an object similar to a file that allows a program to accept incoming connections, make o…

Inter Process Communication(IPC)- Pipes

Interprocess communication (IPC) is a set of programming interfaces that allow a programmer to coordinate activities among different program processes that can run concurrently in an operating system. This allows a program to handle many user requests at the same time. Since even a single user request may result in multiple processes running in the operating system on the user's behalf, the processes need to communicate with each other. The IPC interfaces make this possible. Each IPC method has its own advantages and limitations so it is not unusual for a single program to use all of the IPC methods. pipe In computer programming, especially in UNIX operating systems, a pipe is a technique for passing information from one program process to another. Unlike other forms of interprocess communication (IPC), a pipe is one-way communication only. Basically, a pipe passes a parameter such as the output of one process to another process which accepts it as input. The system temporarily ho…

Inter Process Communication-Message Queue

Interprocess communication (IPC) is a set of programming interfaces that allow a programmer to coordinate activities among different program processes that can run concurrently in an operating system. This allows a program to handle many user requests at the same time. Since even a single user request may result in multiple processes running in the operating system on the user's behalf, the processes need to communicate with each other. The IPC interfaces make this possible. Each IPC method has its own advantages and limitations so it is not unusual for a single program to use all of the IPC methods. Message Based Communication Messages are a very general form of communication. Messages can be used to send and receive formatted data streams between arbitrary processes. Messages may have types. This helps in message interpretation. The type may specify appropriate permissions for processes. Usually at the receiver end, messages are put in a queue. Messages may also be formatted in …