System V shared memory

Problem: how can I share memory between processes?

The fastest way of getting data from process A to process B is by using the same piece of memory. Once things are set up so that more than one process can address the same memory space, the kernel is no longer involved in the passing of the data. In other words, no system calls need to be executed in order to manipulate data.

Of course, something needs to guard the access to the shared memory. For this purpose, semaphores are invented.

Shared memory is not really complicated. What actually happens, is summarized in the following steps:

  1. shmget() is used to create or open a piece of shared memory. This involves asking the kernel to set up a structure struct shmid_ds with information about the shared memory object. shmget() returns an unique identifier to the shared memory object.
  2. This unique identifier is a parameter when calling shmat(). This function returns a pointer. That pointer points to the beginning of the shared memory block.
  3. With this pointer, you can read and write in the shared memory block.
  4. Some bookkeeping involves shmdt(): access to the shared memory can be closed using shmdt().
  5. Really removing the shared memory object can be done using shmctl(). We say really removing because a shared memory object is kernel persistent: as long as the kernel is running, the shared memory object stays intact.

Creating and opening shared memory objects with shmget()

As can be read in the last couple of sentences of the previous section, shared memory objects are kernel persistent. So chances are that your application needs to take into account the fact that the shared memory object is already created and just needs opening. So, creating or opening a shared memory object is done by the same function. Note that creating or opening does not mean getting access. That is done by shmat(), as discussed in the section hereafter.

  int shmget ( key_t key, size_t size, int shmflag );
key_t key The key, a value to identify the semaphore set system-wide. Can also be IPC_PRIVATE, which will strip the third parameter int semflg, so only the permission part is left.
int size The size (in chars) which you want the piece of shared memory to be. Pass 0 if you are just opening, not creating.
int shmflag This parameter is divided in parts, which must be glued together with the bitwise OR operator ( | ). One part can be IPC_CREATE. Or IPC_CREATE|IPC_EXCL. The other part is the permission part. It consists of nine bits which represent the wellknown permission bits rwxrwxrwx. For more information, check the examples.

On success, shmget() will return an integer. This integer is needed when manipulating the object with shmat(), shmdt() and shmctl().

Some tips: create the key with ftok(const char *pathname, int proj_id) and as the pathname, use the path and filename of your binary. When any of the calls like shmget return an error, print out the key. The shared memory area can then always be cleaned up with the ipcrm command, see below.

In the examples below, it is assumed that the following macro and declarations have been made:

 #define DONTCARE 0          /* to indicate that a parameter is ignored */
 key_t shmem_key = 1234;     /* the key for our shared memory object */
 int n_shmem_id;             /* unique value for a shared memory object */
 int n_shmem_size = 16;      /* the size of the shared memory object */

Create a shared memory object with a size of 32 bytes. The 3rd and last parameter gives all rights to the creator (the first six). It gives read rights to the group of the creator and others (the second and third four). Remember that the prefixed 0 (zero) tells your compiler that the number is octal.

 n_shmem_id = shmget(IPC_PRIVATE, n_shmem_size, 0644);

Create a shared memory object. If creating doesn't succeed, because it already exists with the same key, -1 is returned and errno is set to EEXIST.

 n_shmem_id = shmget(shm_key, n_shmem_size, IPC_CREAT | IPC_EXCL | 0644);

Open a shared memory object. If it does not exist, -1 is returned and errno is set to ENOENT.

 n_shmem_id = shmget(shm_key, DONTCARE, DONTCARE);

Open the shared memory object with key shm_key. If opening doesn't succeed, because the object does not exist, try to create it.

 n_shmem_id = shmget(shm_key, n_shmem_size, IPC_CREAT | 0644);

Obtaining access with shmat()

If we want to use the shared memory, we need a pointer to the first entry. This pointer is obtained by calling:

 char* shmat ( int shmid, char* shmaddr, int shmflg )
int semid The unique identifier for the shared memory object. This integer was returned by shmget().
char* shmaddr Just pass NULL here. If you really want it, you can specify an offset value which will return the pointer to the address that is a certain amount of bytes away from the starting address. Check the man shmat page.
int shmflg Pass 0 here, or pass SHM_RDONLY so that the calling process only has read rights to the block of shared memory. Also, the constants SHM_RND and SHMLBA can be passed; they influence the results of passing a non-NULL pointer as the second parameter.

In the example below, it is assumed that the shared memory object shmem_id is open and that a pointer-to-char ptr_shmem is declared. The example obtains the pointer to the start of the block of shared memory.

 ptr_shmem = shmat(shmem_id, NULL, 0);
 if(ptr_shmem == (void*) -1)
 {
       /* do some error handling here */
 }
    

Detach and control with shmdt() and shmctl()

When a process is finished with a shared memory block, it detaches the block with:

 int shmdt ( char *shmaddr)

When a process exits, all shared memory objects currently attached to that process are implicitly detached.

Detaching is not the same as deleting. Besides others, deletion is accomplished by calling the following system call:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

Three commands are provided. Instead of explaining each, here are some examples. They assume that the shared memory object shmem_id is opened and that the following declarations have been made:

 struct shmid_ds buffer;    /* assuming it's filled with necessary data */
 int retval; /* to put the return value of shmctl() in for error checking */

The following piece of code will destroy the shared memory object that is identified by the integer shmem_id:

 retval = shmctl(shmem_id, IPC_RMID, NULL);
 if(retval == -1)
 {
       /* do some error checking */
 }

Each shared memory object has an associated kernel structure struct shmid_ds. The members that are important here, are shm_perm.uid, shm_perm.gid and shm_perm.mode. The following line of code sets these members to the corresponding members of the third parameter. Also, the member shm_ctime value is updated to the current time.

 retval = shmctl(shmem_id, IPC_SET, &buffer);

The following line of code copies the kernel structure to the struct that is passed in the third parameter:

 retval = shmctl(shmem_id, IPC_STAT, &buffer);

Checking and controlling shared memory from the commandline

To get an overview of all shared memory areas, use the command ipcs. Output could look like the following:

 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 0x4306f4ef 131076     telis497  666        95436      1
 0x43435049 163845     nobody    666        65536      0
 
 ------ Semaphore Arrays --------
 key        semid      owner      perms      nsems
 0x430610b1 0          bartvk    666        1
 0x4306f4ef 32769      telis497  666        1
 0x43435049 65538      nobody    666        3
 0x4306f48f 131076     bartvk    666        1
 
 ------ Message Queues --------
 key        msqid      owner      perms      used-bytes   messages

To remove shared memory areas that are left over because a process crashed or something, use the command:

  # ipcrm -M key

Or when you'd like to use the ID:

  # ipcrm -m 131076