Interprocess Communication With Shared Memory In C

There are various ways to communicate from one process to another. A few ways to do so are through Unix domain sockets, sockets on the local loopback, signals between processes, open files, message queues, pipes, and even memory mapped files. One interesting way is to use shared memory segments using a key to identify where in memory the shared segment will be. I have done a fair amount of interprocess communication (IPC) using sockets on the loopback as well as signalling, so I thought it would be good to delve into shared memory as a medium for IPC.

The main idea behind shared memory is based on the server / client paradigm. The server maps a section of memory and the client may have access to that shared memory for reading or writing, in doing so there is a window between the two processes in which data can be exchanged.

There are a set of functions that are used to take advantage of using shared memory.

Functions For Accessing Shared Memory

To open this so called window we have to locate the memory segment, map it, then perform an action upon it. The humanity! Here are the headers and function definitions for basic shared segment mapping.

// Required header files
#include <sys/ipc.h>
#include <sys/shm.h>
 
// Function definition
int shmget(key_t key, size_t size, int shmflg);

This function returns the identifier associated with the value of the first argument key. The shmget function takes three parameters. The first parameter ‘key’ is an integer value used as the identifier in which both processes use to map the memory segment. The second parameter, ‘size’ is the amount of memory to map, where size is equal to the value of size rounded up to a multiple of PAGE_SIZE. You can view the system PAGE_SIZE from the command line via “getconf PAGESIZE”, for more getconf information check out my earlier article. In my case PAGESIZE is 4096 bytes. Finally, the third parameter is used for access permissions to the shared memory segment. The values are analogous to the open(2) permission settings. In our case we use IPC_CREATE | 0666.

// Required header files
#include <sys/types.h>
#include <sys/shm.h>
 
// Function definition
void *shmat(int shmid, const void *shmaddr, int shmflg);

The shmat function returns the attached shared memory segment. The first argument is the return value from the shmget function call. The second argument is shmaddr, if it is NULL, the system chooses a suitable (unused) address at which to attach the segment. The third argument is akin to the shmflg mentioned above and is used for permissions. The return value is a pointer to the shared memory and can be acted upon.

Finally, once all work is complete on our address. We can close our handle using shmdt.

// Required header files
#include <sys/types.h>
#include <sys/shm.h>
 
// Function definition
int shmdt(const void *shmaddr);

If you specified a value other than NULL on the shmat call, then you would pass that value into shmaddr. I just pass the return value from shmat into shmdt and check the return value. If shmdt fails it returns -1 and sets errno appropriately.

Those are the three main functions for setting up memory mapping between processes. I always find examples useful, so I have created a server and client example. The client grabs user input from standard input and writes it to memory then the server reads that input from the first byte of memory and prints it to the screen.

Example Program Using Shared Memory

The first bit of code here is the server code, it initially creates the shared memory segment and sets the permissions accordingly. Its goal is to listen for changes to the first byte in memory and display them to standard output. You will notice the use of shmget, shmat, and shmdt within my code.

Shared Memory Server Side

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
#define SHMSZ 1024
 
main(int argc, char **argv)
{
	char c, tmp;
	int shmid;
	key_t key;
	char *shm, *s;	
 
    /*
     * Shared memory segment at 1234
     * "1234".
     */
	key = 1234;
 
    /*
     * Create the segment and set permissions.
     */
	if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
		perror("shmget");
		return 1;
	}
 
    /*
     * Now we attach the segment to our data space.
     */
	if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
		perror("shmat");
		return 1;
	}
 
	/*
	 * Zero out memory segment
	 */
	memset(shm,0,SHMSZ);
	s = shm;
 
	/*
	* Read user input from client code and tell
	* the user what was written.
	*/
	while (*shm != 'q'){
		sleep(1);
		if(tmp == *shm)
			continue;
 
		fprintf(stdout, "You pressed %c\n",*shm);
		tmp = *shm;
	}
 
	if(shmdt(shm) != 0)
		fprintf(stderr, "Could not close memory segment.\n");
 
	return 0;
}

Shared Memory Client Side

This client uses getchar() to retrieve user input and stores it in the first byte of the memory segment for the server to read.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
#define SHMSZ     1024
 
main()
{
	int shmid;
	key_t key;
	char *shm, *s;
 
	/*
	* We need to get the segment named
	* "1234", created by the server.
	*/
	key = 1234;
 
	/*
	* Locate the segment.
	*/
	if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
		perror("shmget");
		return 1;
	}
 
	/*
	* Now we attach the segment to our data space.
	*/
	if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
		perror("shmat");
		return 1;
	}
 
	/*
	* Zero out memory segment
	*/
	memset(shm,0,SHMSZ);
	s = shm;
 
	/*
	* Client writes user input character to memory
	* for server to read.
	*/
	for(;;){
		char tmp = getchar();
		// Eat the enter key
		getchar();
 
		if(tmp == 'q'){
			*shm = 'q';
			break;
		}
		*shm = tmp;
	}
 
	if(shmdt(shm) != 0)
		fprintf(stderr, "Could not close memory segment.\n");
 
	return 0;
}

The output of the two programs is as follows. I start the server first as it creates the memory segment, then within the client I enter the characters I want the server to output.

. . . 

This is a basic example of passing data between processes. Rather than simply modifying one character in memory I could pass a structure containing various fields or structs within structs, you are only limited by the amount of memory and imagination as to what you could pass back and forth. There you have it, a basic example of two separate processes passing data between each other.