You will usually need a data structure to map onto your
shared memory.
For example, suppose that the struct
named Buffer
is defined as follows (this buffer would be useful in the barbershop
project):
struct Buffer { int GEntries; // Number of grunts to enter the shop int GExits; // Number of grunts to exit the shop int NEntries; // Number of noncoms to enter the shop int NExits; // Number of noncoms to exit the shop int OEntries; // Number of officers to enter the shop int OExits; // Number of officers to exit the shop int BarberChair; // 0, empty, 1 occupied }
To ask the OS for shared memory, you need to specify the size of the
shared area you want. This can be determined by using the
sizeof
keyword. The first and third arguments to
shmget
are as for semget
: a key from
ftok
and a set of flags. Here is an example:
int bufferID; // The ID of the shared buffer // Get the shared memory int key = ftok(...); bufferID = shmget(key, sizeof (Buffer), IPC_CREAT | 0644); if (bufferID < 0) { cerr << "cannot create shared memory: " << strerror(errno) << endl; exit(1); }
Note that after a successful shmget, the process has access to
a shared memory area of the requested size. The process only knows that
area by an integer value, bufferID.
Creating shared memory space is not good enough. You asked the OS for it, but you don't actually have access to it. In addition, when you want access to the shared memory, you usually need to make it look like a well-known data structure. In IPCS terminology all of this is called attaching the shared memory. Basically, you are asking the OS to set up a relationship between the created shared memory and a data structure mapped into your address space. Shmget creates shared memory and shmat maps it into a process's address space. Depending on the order of actions, shared memory can be created and attached before any forks, and thus since children inherit, children will automatically have the shared memory mapped to a common data structure. This is usually the most convenient approach.
Here is an example of how to attach shared memory:
Buffer* sharedData; // Pointer to where the OS put the shared stuff sharedData = (Buffer*)shmat(bufferID, (char *)0, 0); if (sharedData == (Buffer*)-1) { cerr << "cannot attach shared memory: " << strerror(errno) << endl; exit(1); }Note failure is indicated by returning a dummy pointer equal to -1; this is the reason for the odd typecast in the
if
test.
When you first create and attach shared memory, it has not been
initialized. In our case the pointer sharedData
now
points to the shared memory, and you can use it like any other memory
of type Buffer
. (In C, you need to call it struct
Buffer
.) So you can initialize things like this:
sharedData->GEntries = 0; sharedData->GExits = 0; for (int i = 0; i < NUM_CHAIRS; i++) sharedData->chair[i] = 0; // etc.
Using the shared memory is similar. Just remember that it's shared: any change you make will be immediately visible in other processes that share the same memory.
Once a process is finished with the shared memory, then the process should detach it. Detaching does not destroy the shared memory area, but rather removes any mapping to that process's address space:
shmdt((void *)sharedData); // sharedData is now an invalid pointer, just as if you had deleted it
When all the processes are finished, then the shared memory should be returned to the OS:
shmctl(bufferID, IPC_RMID, (struct shmid_ds*)NULL);
Often, you will want to share some structure that contains a pointer to data that you also would like to share. For example:
struct Tricky { int count; double* usefulStuff; }; // ... Tricky* sneaky = (Tricky*)shmat(...); sneaky->count = 10; sneaky->usefulStuff = new double[50];The above code won't do what you want because
new
doesn't
know anything about shared memory. So although
sneaky->count
is
shared among all processes, the 50 doubles you allocated are
private.
There are two solutions to the problem. The first is to create a separate shared-memory area to hold your dynamically allocated array. You then write:
Tricky* sneaky = (Tricky*)shmat(...); sneaky->count = 10; int usefulStuffID = shmget(..., sizeof(double) * 50, ...); sneaky->usefulStuff = (double*)shmat(usefulStuffID, ...);Note that you have to multiple
sizeof(double)
by 50 to
get the size of your second shared-memory area, because
sizeof(double)
is the size of only one double,
not 50. If you forget to multiply, your code will work fine for small
array sizes but will mysteriously fail when you get up to some
moderately large boundary.
The second approach, which
is beyond the scope of this FAQ, is to create a single large
shared-memory area that has enough space for both Tricky
and the array of doubles. Then you use some clever pointer
manipulations to set up the usefulStuff
pointer. The
second approach is more efficient (because it doesn't waste precious
shared-memory IDs) but ugly.
Last modified February 24, 2002 by geoff@cs.hmc.edu