Chapter 3. Sharing Memory Between Processes
Prev | Part II. Interprocess Communication | Next

## Chapter 3. Sharing Memory Between Processes

There are three families of functions that let you create a segment of memory and share it among the address spaces of multiple processes. All produce the same result: a segment of memory that can be accessed or updated asynchronously by more than one process. You have to design protocols that prevent one process from changing shared data while another process is using the same data (see Chapter 4, “Mutual Exclusion”).

This chapter covers three major topics:

- “POSIX Shared Memory Operations” describes the POSIX functions for sharing memory.

- “IRIX Shared Memory Arenas” describes IRIX shared memory arenas.

- “System V Shared Memory Functions” describes the SVR4 functions.

## Overview of Memory Sharing

The address space is the range of memory locations that a process can use without an error. (The concept of the address space is covered in detail in Chapter 1, “Process Address Space.”) In a pthreads program, all threads use the same address space and share its contents. In a program that starts multiple, lightweight processes with `sproc()`, all processes share the same address space and its contents. In these programs, the entire address space is shared automatically.

Normally, distinct processes (created by the `fork()` or `exec()` system calls) have distinct address spaces, with no writable contents in common. The facilities described in this chapter allow you to define a segment of memory that can be part of the address space of more than one process. Then processes or threads running in different address spaces can share data simply by referring to the contents of the shared segment in memory.

### Shared Memory Based on mmap()

The basic IRIX system operation for shared memory is the `mmap()` function, with which a process makes the contents of a file part of its address space. The fundamental uses of `mmap()` are covered under “Mapping Segments of Memory” (see also the `mmap(2)` reference page). When two or more processes map the same file into memory with the MAP_SHARED option, that single segment is part of both address spaces, and the processes can update its contents concurrently.

The POSIX shared memory facility is a simple, formal interface to the use of `mmap()` to share segments. The IRIX support for shared arenas is an extension of `mmap()` to make it simpler to create a shared allocation arena and coordinate its use. The SVR4 facilities do not directly use `mmap()` but have similar results.

### Sharing Memory Between 32-Bit and 64-Bit Processes

Larger Silicon Graphics systems support both 32-bit and 64-bit programs at the same time. It is possible for a memory segment to be mapped by programs using 32-bit addresses, and simultaneously mapped by programs that use 64-bit addresses. There is nothing to prevent such sharing.

However, such sharing can work satisfactorily only when the contents of the shared segment include no addresses at all. Pointer values stored by a 64-bit program can't be used by a 32-bit program and vice versa. Also the two programs will disagree about the size and offset of structure fields when structures contain addresses. For example, if you initialize an allocation arena with `acreate()` from a 64-bit program, a 32-bit program calling `amalloc()` on that same arena will almost certainly crash or corrupt the arena pointers.

You can use POSIX shared memory, SVR4 shared memory, or basic `mmap()` to share a segment between a 32-bit and a 64-bit program, provided you take pains to ensure that both programs view the data contents as having the same binary structure, and that no addresses are shared. You cannot use an IRIX shared memory arena between 32-bit and 64-bit programs at all, because the `usinit()` function stores addresses in the arena.

## POSIX Shared Memory Operations

Shared-memory support specified by POSIX is based on the functions summarized in Table 3-1.

Table 3-1. POSIX Shared Memory Functions

Function Name | Purpose and Operation
--- | ---
mmap(2) | Map a file or shared memory object into the address space.
shm_open(2) | Create, or gain access to, a shared memory object.
shm_unlink(2) | Destroy a shared memory object when no references to it remain open.

The use of `mmap()` is described at length under “Mapping Segments of Memory”. In essence, `mmap()` takes a file descriptor and makes the contents of the described object accessible as a segment of memory in the address space. In IRIX, a file descriptor can describe a disk file, or a device, or a special pseudo-device such as `/dev/kmem`. Thus `mmap()` can make a variety of objects part of the address space. POSIX adds one more type of mappable object, a persistent shared segment you create using the `shm_open()` function.

### Creating a Shared Object

The `shm_open()` function is very similar to the `open()` function and takes the same arguments (compare the `shm_open(2)` and `open(2)` reference pages). The arguments are as follows:

path | Name of object, a character string in the form of a file pathname.
oflag | Option flags, detailed in the reference page and discussed in following text.
mode | Access mode for the opened object

In order to declare `shm_open()` and its arguments you need to include both `sys/mman.h` and `fcntl.h` header files.

#### Shared Object Pathname

The POSIX standard says that a shared object name has the form of a file pathname, but the standard leaves it “implementation defined” whether the object is actually a file or not. In the IRIX implementation, a shared memory object is also a file. The pathname you specify for a shared memory object is interpreted exactly like the pathname of a disk file that you pass to `open()`. When you create a new object, you also create a disk file of the same name. (See “POSIX IPC Name Space”.)

You can display the size, ownership, and permissions of an existing shared segment using `ls -l`. You can dump its contents with a command such as `od -X`. You can remove it with `rm`.

#### Shared Object Open Flags

The flags you pass to `shm_open()` control its actions, as follows:

O_RDONLY | Access can be used only for reading.
O_RDWR | Access can be read-write (however, you can enforce read-only access when calling `mmap()`).
O_CREAT | If the object does not exist, create it.
O_TRUNC | If the object does exist and O_RDWR is specified, truncate it to zero length.
O_EXCL | If the object does exist and O_CREAT is specified, return the EEXIST error code.

The flags have the same meaning when opening a disk file with `open()`. However, a number of other flags allowed by `open()` are not relevant to shared memory objects.

You can use the combination O_CREAT+O_EXCL to ensure that only one process initializes a shared object.

#### Shared Object Access Mode

The access mode that you specify when creating an object governs the users and groups that can open the object later, exactly as with a disk file.

#### Using the Shared Object File Descriptor

The value returned by `shm_open()` is a file descriptor and you can use it as such; for example you can apply the `dup()` function to make a copy of it. You can also use it as an argument to `fcntl()`, but most of the features of `fcntl()` are irrelevant to a shared memory object. (See the `dup(2)` and `fcntl(2)` reference pages.)

### Using a Shared Object

In order to use a shared object, your program first opens it with `shm_open()`, then maps it into memory with `mmap()`. The arguments to `mmap()` include

- the file descriptor for the shared object

- the size of the memory segment

- access protection flags

The returned value is the base address of the segment in memory. You can then use it like any block of memory. For example, you could create an allocation arena in the segment using the `acreate()` function (see the `amalloc(3)` reference page). For more on the use of `mmap()`, read “Segment Mapping Function mmap()” and “Mapping a File for Shared Memory”.

### Example Program

The program in Example 3-1 allows you to experiment with `shm_open()` and `mmap()` from the command line. The program accepts the following command-line arguments:

path | The pathname of a shared memory segment (file) that exists or that is to be created.
-p perms | The access permissions to apply to a newly-created segment, for example -p 0664.
-s bytes | The initial size at which to map the segment, for example -s 0x80000.
-c | Use the O_CREAT flag with `open()`, creating the segment if it doesn't exist.
-x | Use the O_EXCL flag with `open()`, requiring the segment to not exist.
-t | Use the O_TRUNC flag with `open()`, truncating the file to zero length.
-r | Use the O_RDONLY flag with `open()` and PROT_READ with `mmap()`. If this option is not used, the program uses O_RDWR with `open()` and PROT_READ, PROT_WRITE, PROT_AUTOGROW with `mmap()`.
-w | Wait for keyboard input before exiting, allowing you to run other copies of the program while this one has the segment mapped.

To create a segment named `/var/tmp/test.seg`, use a command such as

    shm_open -c -x -p 0644 -s 0x80000 /var/tmp/test.seg

To attach that segment read-only and then wait, use the command

    shm_open -r -w /var/tmp/test.seg

From a different terminal window, enter the command

    shm_open /var/tmp/test.seg

In the original window, press <Enter> and observe that the value of the first word of the shared segment changed during the wait.

Example 3-1. POSIX Program to Demonstrate shm_open()

    /*
    ||  Program to test shm_open(3).
    ||      shm_open    [-p <perms>] [-s <bytes>] [-c] [-x] [-r] [-t] [-w] <path>
    ||      -p <perms>  access mode to use when creating, default 0600
    ||      -s <bytes>  size of segment to map, default 64K
    ||      -c          use O_CREAT
    ||      -x          use O_EXCL
    ||      -r          use O_RDONLY, default is O_RDWR
    ||      -t          use O_TRUNC
    ||      -w          wait for keyboard input before exiting
    ||      <path>      the pathname of the queue, required
    */
    #include <sys/mman.h>   /* shared memory and mmap() */
    #include <unistd.h>     /* for getopt() */
    #include <errno.h>      /* errno and perror */
    #include <fcntl.h>      /* O_flags */
    #include <stdio.h>
    int main(int argc, char **argv)
    {
        int perms = 0600;           /* permissions */
        size_t size = 65536;        /* segment size */
        int oflags = 0;             /* open flags receives -c, -x, -t */
        int ropt = 0;               /* -r option seen */
        int wopt = 0;               /* -w option seen */
        int shm_fd;                 /* file descriptor */
        int mprot = PROT_READ;      /* protection flags to mmap */
        int mflags = MAP_SHARED;    /* mmap flags */
        void *attach;               /* assigned memory adddress */
        char *path;                 /* ->first non-option argument */
        int c;
        while ( -1 != (c = getopt(argc,argv,"p:s:cxrtw")) )
        {
            switch (c)
            {
            case 'p': /* permissions */
                perms = (int) strtoul(optarg, NULL, 0);
                break;
            case 's': /* segment size */
                size = (size_t) strtoul(optarg, NULL, 0);
                break;
            case 'c': /* use O_CREAT */
                oflags |= O_CREAT;
                break;
            case 'x': /* use O_EXCL */
                oflags |= O_EXCL;
                break;
            case 't': /* use O_TRUNC */
                oflags |= O_TRUNC;
                break;
            case 'r': /* use O_RDONLY */
                ropt = 1;
                break;
            case 'w': /* wait after attaching */
                wopt = 1;
                break;
            default: /* unknown or missing argument */
                return -1;
            } /* switch */
        } /* while */
        if (optind < argc)
            path = argv[optind]; /* first non-option argument */
        else
            { printf("Segment pathname required\n"); return -1; }
        if (0==ropt)
        { /* read-write access, reflect in mprot and mflags */
            oflags |= O_RDWR;
            mprot |= PROT_WRITE;
            mflags |= MAP_AUTOGROW + MAP_AUTORESRV;
        }
        else
        { /* read-only access, mprot and mflags defaults ok */
            oflags |= O_RDONLY;
        }
        shm_fd = shm_open(path,oflags,perms);
        if (-1 != shm_fd)
        {
            attach = mmap(NULL,size,mprot,mflags,shm_fd,(off_t)0);
            if (attach != MAP_FAILED) /* mmap worked */
            {
                printf("Attached at 0x%lx, first word = 0x%lx\n",
                                    attach,      *((pid_t*)attach));
                if (mprot & PROT_WRITE)
                {
                    *((pid_t *)attach) = getpid();
                    printf("Set first word to 0x%lx\n",*((pid_t*)attach));
                }
                if (wopt) /* wait a while, report possibly-different value */
                {
                    char inp[80];
                    printf("Waiting for return key before unmapping...");
                    gets(inp);
                    printf("First word is now 0x%lx\n",*((pid_t*)attach));
                }
                if (munmap(attach,size))
                    perror("munmap()");
            }
            else
                perror("mmap()");
        }
        else
            perror("shm_open()");
        return errno;
    }

## IRIX Shared Memory Arenas

The shared memory arena is basic to all IRIX IPC mechanisms. IRIX semaphores, locks, and barriers are all represented as objects within a shared arena.

### Overview of Shared Arenas

A shared arena is a segment of memory that can be made part of the address space of more than one process. Each shared arena is associated with a disk file that acts as a backing store for the file (see “Page Validation”). Each process that wants to share access to the arena does so by specifying the file pathname of the file. The file pathname acts as the public name of the memory segment. The file access permissions determine which user IDs and group IDs can share the file.

The functions you use to manage a shared arena are discussed in the following topics and are summarized in Table 3-2.

Table 3-2. IRIX Shared Arena Management Functions

Function Name | Purpose and Operation
--- | ---
usconfig(3) | Establish the default size of an arena, the number of concurrent processes that can use it, and the features of IPC objects in it.
usinit(3) | Create an arena or join an existing arena.
usadd(3) | Join an existing arena.

### Initializing Arena Attributes

A program creates a shared memory arena with the `usinit()` function. However, many attributes of a new arena are set by preceding calls to `usconfig()`. The normal sequence of operations is to make several calls to `usconfig()` to establish arena attributes, then to make one call to `usinit()` to create the arena.

You call `usconfig()` to establish the features summarized in Table 3-3.

Table 3-3. Arena Features Set Using usconfig()

usconfig() Flag Name | Meaning
--- | ---
CONF_INITSIZE | The initial size of the arena segment. The default is 64 KB. Often you know that more is needed.
CONF_AUTOGROW | Whether or not the arena can grow automatically as more IPC objects or data objects are allocated (default: yes).
CONF_INITUSERS | The largest number of concurrent processes that can use the arena. The default is 8; if more processes than this will use IPC, the limit must be set higher.
CONF_CHMOD | The effective file permissions on arena access. The default is 600, allowing only processes with the effective UID of the creating process to attach the arena.
CONF_ARENATYPE | Establish whether the arena can be attached by general processes or only by members of one program (a share group).
CONF_LOCKTYPE | Whether or not lock objects allocated in the arena collect metering statistics as they are used.
CONF_ATTACHADDR | An explicit memory base address for the next arena to be created (see “Choosing a Segment Address”).
CONF_HISTON CONF_HISTOFF | Start and stop collecting usage history (more bulky than metering information) for semaphores in a specified arena.
CONF_HISTSIZE | Set the maximum size of semaphore history records.

See the `usconfig(3)` reference page for a complete list of attributes. The use of metering and history information for locks and semaphores is covered in Chapter 4, “Mutual Exclusion.”

| Tip: In programs that use an arena and start a varying number of child processes, it is a common mistake to find that the eighth child process cannot join the arena. This occurs simply because `usconfig()` has not been called with CONF_INITUSERS to set the number of users higher than the default 8 before the arena was created.

### Creating an Arena

After setting the arena attributes with `usconfig()`, the program calls `usinit()`, specifying a file pathname string.

| Tip: The `mktemp()` library function can be used to create a unique temporary filename (see the `mktemp(3C)` reference page).

If the specified file doesn't exist, `usinit()` creates it (and gives it the access permissions specified to `usinit()` with CONF_CHMOD). If a shared arena already exists based on that name, `usinit()` joins that shared arena. If the file exists but is not yet a shared arena, `usinit()` overwrites it. In any case, `usinit()` is subject to normal filesystem permission tests, and it returns an error if the process doesn't have read and write permission on the file (if it already exists) or permission to create the file (if it doesn't exist).

Code to prepare an arena is shown in Example 3-2.

Example 3-2. Initializing a Shared Memory Arena

    usptr_t
    makeArena(size_t initSize, int nProcs)
    {
       int ret;
       char * tmpname = "/var/tmp/arenaXXXXXX";
       if (ret = usconfig(CONF_INITUSERS, nProcs))
       { perror("usconfig(#users)"); return 0; }
       if (ret = usconfig(CONF_INITSIZE, initSize))
       { perror("usconfig(size)"); return 0; }
       return usinit(mktemp(tmpname));
    }

### Joining an Arena

Only one process creates a shared arena. Other processes “join” or “attach” the arena. There are three ways of doing this. When the arena is not restricted to a single process family (either by file permissions or by CONF_ARENATYPE setting), any process that calls `usinit()` and passes the same pathname string gains access to the same arena at the same virtual base address. This process need not be related in any way to the process that created the arena.

#### Restricting Access to an Arena

You can restrict arena access to a single process and the children it creates with `sproc()` (a share group; see “Process Creation and Share Groups”) by calling `usconfig()` to set CONF_ARENATYPE to US_SHAREDONLY before creating the arena. When this is done, the file is unlinked immediately after the arena is created. Then a call to `usinit()` with the same pathname from a different process creates a different arena, one that is not shared with the first one. This has several side-effects that are detailed in `usconfig(3)`.

#### Arena Access From Processes in a Share Group

An arena is a segment in the address space of a process. When that process creates a new process using `sproc()`, the child process usually shares the same address space (see the `sproc(2)` reference page and Chapter 12, “Process-Level Parallelism”). The child process has access to the arena segment on the same basis as the parent process. However, the child process needs to join the arena formally.

The child process should join the arena by calling `usadd()`, passing the address of the arena. The child should test the return code of this function, since it can reflect an error in either of two cases:

- The arena has not been created, or an incorrect arena address was passed.

- The arena was not configured to allow enough using processes, and no more users can be allowed.

A child process can join an arena automatically, simply by using a semaphore, lock, or barrier that was allocated within that arena. These function calls perform an automatic call to `usadd()`. However, they can also encounter the error that too many processes are already using the arena. It is best for the child process to check for this condition with an explicit call to `usadd()`.

### Allocating in an Arena

Allocating shared memory from a shared arena is much like the regular process of allocating memory using the `malloc()` and `free()` library routines. The functions related to allocation within an arena are summarized in Table 3-4.

Table 3-4. IRIX Shared Memory Arena Allocation Functions

Function Name | Purpose and Operation
--- | ---
usmalloc(3) | Allocate an object of specified size in an arena.
uscalloc(3) | Allocate an array of zero-filled units in an arena.
usmemalign(3) | Allocate an object of specified size on a specified alignment boundary in an arena.
usrealloc(3) | Change the allocated size of an object in an arena.
usrealloc(3) | Change the allocated size of an array created with `uscalloc()`.
usmallocblksize(3) | Query the actual size of an object as allocated.
usfree(3) | Release an object allocated in an arena.
usmallopt(3) | Tune the allocation algorithm using constants described in `amallopt(3)`.
usmallinfo(3) | Query allocation statistics (see `amallinfo(3)` for structure fields).

The address of an object allocated using `usmalloc()` or a related function is a valid address in any process that is attached to the shared arena. If the address is passed to a process that has not attached the arena, the address is not valid for that process and its use will cause a SIGSEGV.

The `usmalloc()` family of functions is based on the arena-allocation function family described in the `amalloc(3)` reference page. The `usmallopt()` function is the same as the `amallopt()` function, and both provide several options for modifying the memory allocation methods in a particular arena. In a similar way, `usmallinfo()` is the same as `amallinfo()`, and both return detailed statistics on usage of memory allocation in one arena.

### Exchanging the First Datum

The processes using a shared arena typically need to locate some fundamental data structure that has been allocated within the arena. For example, the parent process creates a foundation data structure in the arena, and initializes it with pointers to other objects within the arena. Any process starting to use the arena needs the address of the foundation structure in order to find all the other objects used by the application.

The shared arena has a special one-pointer field for storing such a basic address. This area is accessed using the functions summarized in Table 3-5.

Table 3-5. IRIX Shared Memory First-Datum Functions

Function Name | Purpose and Operation
--- | ---
usputinfo(3) | Set the shared-pointer field of an arena to a value.
usgetinfo(3) | Retrieve the value of the shared-pointer field of an arena.
uscasinfo(3) | Change the shared-pointer field using a compare-and-swap.

| Note: The precision of the `usgetinfo()` field in an arena, 32 or 64 bits, depends on the execution model of the program that creates the arena. This is one reason that processes compiled to different models cannot share one arena (see “Sharing Memory Between 32-Bit and 64-Bit Processes”).

Often, the parent process creates and initializes the arena before it creates any of the child processes that will share the arena. In this case, you expect no race conditions. The parent can set the shared pointer using `usputinfo()` because no other process is using the arena at that time. Each child process can fetch the value with `usgetinfo()`.

The purpose of `uscasinfo()` is to change the contents of the field in an atomic fashion, avoiding any race condition between concurrent processes in a multiprocessor. All three functions are discussed in detail in the `usputinfo(3P)` reference page.

| Tip: The data type of the shared pointer field is void*, a 64-bit value when the program is compiled to the 64-bit model. If you need to cast the value to an integer, use the type __psint_t, a pointer-sized integer in any model.

In the less-common case when an arena is shared by unrelated processes, each process that calls `usinit()` might be the first one to create the arena—or might not. If the calling process is the first, it should initialize the basic contents and set the shared pointer. If it is not the first, it should use the initialized contents that another process has already prepared. This problem is resolved with `uscasinfo()`, as sketched by the code in Example 3-3.

Example 3-3. Setting Up an Arena With uscasinfo()

    typedef struct arenaStuff {
       ulock_t   updateLock; /* exclusive use of this structure */
       short     joinedProcs; /* number of processes joined */
       ...pointers to other things allocated by setUpArena()...
    } arenaStuff_t;
    /*
    || The following function performs the one-time setup of the
    || arenaStuff contents. It assumes that updateLock is held.
    */
    extern void
    setUpArena(usptr_t *arena, arenaStuff_t *stuff);
    /*
    || The following function joins a specified arena, creating it
    || and initializing it if necessary.  It could be extended with
    || values to pass to usconfig(3) before the arena is created.
    */
    usptr_t*
    joinArena(char *arenaPath)
    {
       usptr_t *arena;
       arenaStuff_t *stuff;
       int ret;
       /*
       || Join the arena, creating it if necessary. Exit on error.
       */
       if (!arena = usinit(arenaPath))
       {
          perror("usinit");
          return arena;
       }
       /*
       || Do the following as many times as necessary until the arena
       || has been initialized.
       */
       for(ret=0; !ret; )
       {
          if (stuff = (arenaStuff_t *)usgetinfo(arena))
          {
             /*
             || Another process has created the arena, and either has
             || initialized it or is initializing it right now. Acquire
             || the lock, which will block us until initializing is done.
             */
             ussetlock(stuff->updateLock);
             /* here do anything needing exclusive use of arena */
             ++stuff->joinedProcs; /* another process has joined */
             usunsetlock(stuff->updateLock); /* release arena */
             ret = 1; /* end the loop */
          }
          else
          {
             /*
             || This process appears to be first to call usinit().
             || Allocate an arenaStuff structure with its updateLock
             || already held and 1 process joined, and try to swap it
             || into place as the active one. We expect no errors
             || in setting up arenaStuff. If one occurs, the arena is
             || simply unusable, and we return a NULL to the caller.
             */
             if (! (stuff = usmalloc(sizeof(arenaStuff_t),arena) ) )
                return stuff; /* should never occur */
             if (! (stuff->updateLock = usnewlock(arena) ) );
                return (usptr_t*)0; /* should never occur */
             if (! uscsetlock(stuff->updateLock, 1) )
                return (usptr_t*)0; /* should never occur */
             stuff->joinedProcs = 1;
             if (ret = uscasinfo(arena,0,stuff))
             {
                /*
                || Our arenaStuff is now installed. Initialize it.
                || We hold the lock in arenaStuff as setUpArena expects.
                || The loop ends because ret is now nonzero.
                */
                setUpArena(arena,stuff);
                usunsetlock(stuff->updateLock);
             }
             else
             {
                /*
                || uscasinfo() either did not find a current value of 0
                || (indicates a race with another process executing this
                || code) or it failed for some other reason. In any case,
                || release allocated stuff and repeat the loop (ret==0).
                */
                usfreelock(stuff->updatelock,arena);
                usfree(stuff,arena);
             }
          } /* usgetinfo returned 0 */
       } /* while uscasinfo swap fails */
       /* arena->initialized arena, updateLock not held */
       return arena;
    }

Example 3-3 assumes that everything allocated in the arena is accessed through a collection of pointers, arenaStuff. The two problems to be solved are these:

- Which asynchronous process is the first to call `usinit()`, and therefore should allocate arenaStuff and initialize it with pointers to other objects?

- How can the second and subsequent processes know when the initialization of arenaStuff is complete (it might take some time) and the arena is completely ready for use?

The solution in Example 3-3 is based on the discussion in the `uscasinfo(3P)` reference page. Each process calls function `joinArena()`. If a call to `usgetinfo()` returns nonzero, it is the address of an arenaStuff_t that has been allocated by some other process. Possibly that process is concurrently executing, initializing the arena. The current process waits until the lock in the arenaStuff_t is released. On return from the `ussetlock()` call, the process has exclusive use of arenaStuff until it releases the lock. It uses this exclusive control to increment the count of processes using the arena.

When `usgetinfo()` returns 0, the calling process is probably the first to create the arena, so it allocates an arenaStuff structure, and also allocates the essential lock and puts it in a locked state. Then it calls `uscasinfo()` to swap the arenaStuff address for the expected value of 0. When the swap succeeds, the process completes initializing the arena and releases the lock.

The call to `uscasinfo()` could fail if, between the time the process receives a 0 from `usgetinfo()` and the time it calls `uscasinfo()`, another process executes this same code and installs its own arenaStuff. The process handles this unusual event by releasing the items it allocated and repeating the whole process.

When unrelated processes join an arena with code like that shown in Example 3-3, they should terminate their use of the arena with code similar to Example 3-4.

Example 3-4. Resigning From an Arena

    /*
    || The following function reverses the operation of joinArena.
    || Even if the calling process is the last one to hold the arena,
    || nothing drastic is done. This is because it is impossible to
    || perform {usinit(); usgetinfo(); ussetlock();} as an atomic
    || sequence.  Once an arena comes into being it must remain
    || usable until the entire application shuts down. Unlinking the
    || arena file can be the last thing that main() does.
    */
    void
    resignArena(usptr_t *arena)
    {
       arenaStuff_t *stuff = (arenaStuff_t *)usgetinfo(arena);
       ussetlock(stuff->updateLock);
       -- stuff->joinedProcs;
       usunsetlock(stuff->updateLock);
    }

It might seem that, when the function `resignArena()` in Example 3-4 finds that it has reduced the joinedProcs count to 0, it could deinitialize the arena, for example unlinking the file on which the arena is based. This is not a good idea because of the remote chance of the following sequence of events:

1. Process A executes `joinArena()`, initializing the arena.

2. Unrelated process B executes `joinArena()` through the `usinit()` call, but is suspended for a higher-priority process before executing `usgetinfo()`.

3. Process A detects some error unrelated to arena use, and as part of termination, calls `resignArena()`.

4. Process B resumes execution with the call to `usgetinfo()`.

If the `resignArena()` function did something irrevocable, such as unlinking or truncating the arena file, it would leave process B in an unexpected state.

## System V Shared Memory Functions

The System V shared memory functions allow two or more processes to share memory. Unlike the IRIX method, in which the external name of a shared arena is also the name of a file, the external name of an SVR4 shared segment is an integer held in an IPC name table (see “SVR4 IPC Name Space”).

The functions and commands used with SVR4 shared memory are discussed in the following topics and summarized in Table 3-6.

Table 3-6. SVR4 Shared Memory Functions

Function Name | Purpose and Operation
--- | ---
shmget(2) | Create a shared memory IPC object or return the ID of one.
shmctl(2) | Get the status of a shared memory segment, change permissions or user IDs, or lock or unlock a segment in memory.
shmat(2) | Attach a shared memory segment to the address space.
shmdt(2) | Detach a shared memory segment from the address space.

### Creating or Finding a Shared Memory Segment

A process creates a shared memory segment, or locates an existing segment, using the `shmget()` system function. When it creates a segment, the arguments to this function establish:

- The numeric key of the segment.

- The size of the segment.

- The user ID and group ID of the segment creator and owner.

- The access permissions to the segment.

When the function locates an existing segment, access to the segment is controlled by the access permissions and by the user ID and group ID of the calling process.

Unlike an IRIX shared arena, a shared segment does not grow automatically as it is used. The size specified when it is created is fixed. The shared segment is initialized to binary zero. (As implemented in IRIX, the pages of the segment are created as they are first referenced; see “Mapping a Segment of Zeros”.)

The value returned by `shmget()` is the ID number of the segment. It is used to identify the segment to other functions.

#### Attaching a Shared Segment

The `shmget()` function creates the segment, or verifies that it exists, but does not actually make it a part of the process address space. That remains to be done with a call to `shmat()` (“attach”), passing the identifier returned by `shmget()`.

You can pass a desired base address to `shmat()`, or you can pass NULL to have the system select the base address. It is best to let the system choose the base; this ensures that all processes have the same base address for the segment.

A process can detach a segment from its address space by calling `shmdt()`.

### Managing a Shared Segment

The `shmctl()` function gives you the ability to get information about a segment, or to modify its attributes. These operations are summarized in Table 3-7.

Table 3-7. SVR4 Shared Segment Management Operations

Keyword | Operation | Can Be Used By
--- | --- | ---
IPC_STAT | Get information about the segment. | Any process having read access.
IPC_SET | Set owner UID, owner GID, or access permissions. | Creator UID, owner UID, or superuser.
IPC_RMID | Remove the segment from the IPC name space. | Creator UID, owner UID, or superuser.
SHM_LOCK | Lock the segment pages in memory. | Superuser process only.
SHM_UNLOCK | Unlock a locked segment. | Superuser process only.

#### Information About Shared Memory

The information structure returned by `shmctl(`IPC_STAT`)` is declared in the `sys/shm.h` header file. The first field, shm_perm, is an ipc_perm structure. This structure is declared in the `sys/ipc.h` header file.

### Shared Memory Examples

The example programs in this section illustrate the use of some of the SVR4 shared memory system functions.

#### Example of Creating a Shared Segment

The program in Example 3-5 illustrates the use of `shmget()`. You can specify command parameters to exercise any combination of `shmget()` function arguments.

Example 3-5. ``shmget() System Call Example

    /*
    ||  Program to test shmget(2) for creating a segment.
    ||  shmget [-k <key>] [-s <size>] [-p <perms>] [-c] [-x]
    ||      -k <key>            the key to use, default == 0 == IPC_PRIVATE
    ||      -s <size>       size of segment, default is 64KB
    ||      -p <perms>      permissions to use, default is 0600
    ||      -x                  use IPC_EXCL
    ||      -c                  use IPC_CREAT
    */
    #include <unistd.h> /* for getopt() */
    #include <sys/shm.h>    /* for shmget etc */
    #include <errno.h>  /* errno and perror */
    #include <stdio.h>
    int main(int argc, char **argv)
    {
        key_t key = IPC_PRIVATE;    /* key */
        size_t size = 65536;            /* size */
        int perms = 0600;               /* permissions */
        int shmflg = 0;             /* flag values */
        struct shmid_ds ds;         /* info struct */
        int c, shmid;
        while ( -1 != (c = getopt(argc,argv,"k:s:p:cx")) )
        {
            switch (c)
            {
            case 'k': /* key */
                key = (key_t) strtoul(optarg, NULL, 0);
                break;
            case 's': /* size */
                size = (size_t) strtoul(optarg, NULL, 0);
                break;
            case 'p': /* permissions */
                perms = (int) strtoul(optarg, NULL, 0);
                break;
            case 'c':
                shmflg |= IPC_CREAT;
                break;
            case 'x':
                shmflg |= IPC_EXCL;
                break;
            default: /* unknown or missing argument */
                return -1;
            }
        }
        shmid = shmget(key,size,shmflg|perms);
        if (-1 != shmid)
        {
            printf("shmid = %d (0x%x)\n",shmid,shmid);
            if (-1 != shmctl(shmid,IPC_STAT,&ds))
            {
                printf("owner uid/gid: %d/%d\n",
                                ds.shm_perm.uid,ds.shm_perm.gid);
                printf("creator uid/gid: %d/%d\n",
                                ds.shm_perm.cuid,ds.shm_perm.cgid);
            }
            else
                perror("shmctl(IPC_STAT)");
        }
        else
            perror("shmget");
        return errno;
    }

#### Example of Attaching a Shared Segment

The program in Example 3-6 illustrates the process of actually attaching to and using a shared memory segment. The segment must exist, and is specified by its ID or by its key. You can use the program in Example 3-5 to create a segment for this program to use.

The attachment is either read-write or read-only, depending on the presence of the `-r` command parameter. When the program attaches the segment read-write, it stores its own PID in the first word of the segment. Run the program several times; each time it reports the previous PID value and sets a new PID value. This illustrates that the contents of the segment persist between uses of the segment.

You can use the `-w` parameter to have the program wait after attaching. This allows you to start more copies of the program so that multiple processes have attached the segment.

Example 3-6. shmat() System Call Example

    /*
    ||  Program to test shmat().
    ||      shmat {-k <key> | -i <id>} [-a <addr>] [-r] [-w]
    ||          -k <key>        the key to use to get an ID..
    ||          -i <id>     ..or the ID to use
    ||          -a <addr>   address to attach, default=0
    ||          -r              attach read-only, default read/write
    ||          -w              wait on keyboard input before detaching
    */
    #include <unistd.h> /* for getopt() */
    #include <sys/shm.h> /* for shmget etc */
    #include <errno.h> /* errno and perror */
    #include <stdio.h>
    int main(int argc, char **argv)
    {
        key_t key = -1; /* key */
        int shmid = -1; /* ..or ID */
        void *addr = 0; /* address to request */
        void *attach;       /* address gotten */
        int rwflag = 0; /* read or r/w */
        int wait = 0;       /* wait before detach */
        int c, ret;
        while ( -1 != (c = getopt(argc,argv,"k:i:a:rw")) )
        {
            switch (c)
            {
            case 'k': /* key */
                key = (key_t) strtoul(optarg, NULL, 0);
                break;
            case 'i': /* id */
                shmid = (int) strtoul(optarg, NULL, 0);
                break;
            case 'a': /* addr */
                addr = (void *) strtoul(optarg, NULL, 0);
                break;
            case 'r': /* read/write */
                rwflag = SHM_RDONLY;
                break;
            case 'w': /* wait */
                wait = 1;
                break;
            default:
                return -1;
            }
        }
        if (-1 == shmid) /* key must be given */
            shmid = shmget(key,0,0);
        if (-1 != shmid) /* we have an ID */
        {
            attach = shmat(shmid,addr,rwflag);
            if (attach != (void*)-1)
            {
                printf("Attached at 0x%lx, first word = 0x%lx\n",
                                    attach,      *((pid_t*)attach));
                if (rwflag != SHM_RDONLY)
                {
                    *((pid_t *)attach) = getpid();
                    printf("Set first word to 0x%lx\n",*((pid_t*)attach));
                }
                if (wait)
                {
                    char inp[80];
                    printf("Press return to detach...");
                    gets(inp);
                    printf("First word is now 0x%lx\n",*((pid_t*)attach));
                }
                if (shmdt(attach))
                    perror("shmdt()");
            }
            else
                perror("shmat()");
        }
        else
            perror("shmget()");
        return errno;
    }

Prev | Table of Contents | Next
Chapter 2. Interprocess Communication |  | Chapter 4. Mutual Exclusion