READ THIS SECTION!!!
ipcs (pg 144) is the most important command you will learn for the next few weeks. Before you even learn what IPCs are, you need no learn to list and remove them. The ipcs shell command is like ps for IPCs; it lists all of the IPCs (messages, semaphores, and shared memory) on the system. It gives a bunch of information, the most important of which are owner, to see which ones are your responsibility to clean up, and type and id, which you will use with:
ipcrm (pg 144), which is used to remove IPCs. Every time you run a program that uses any of the IPC constructs you are going to learn, you must use ipcs and ipcrm -type id to remove any IPCs you have left. You should also double check when you log out that you haven't left any IPCs behind.
The reason I am so adamant is that there is an upper limit on the number of each type of IPC that can exist on any given machine, and it's not very high. So if you are debugging your program and your IPCs never get deleted by your code, and you are sloppy and leave them there, no-one else will be able to use that computer to do their assignments until the sys-admin cleans up your mess.
The good news is that by using ipcs, who, and finger, you can figure out if people have left IPC constructs clogging a computer and send them e-mail reminding them to remove their IPCs. If for some reason you have a semaphore or shared memory you can't delete, e-mail Unix help and ask them to remove it.
Also be aware that some people will invariably forget, since the class is large and no-one's memory is perfect. Plan to try several computers before you find one that works if you are doing any last-minute testing.
Shared memory is pretty much exactly what it sounds like: memory that can be accessed by several processes. They are quite useful, but there is, of course, a catch: the commands provide ways for multiple processes to access the memory, but they don't control that access. Those of you who haven't slept through all of Tekin's lectures know that having unrestricted access to shared data is a bad thing. Luckily, you just learned about semaphores, so all you have to do is apply the theory that you have been learning in lecture.
One important thing to note is that although the commands for shared memory look a lot like those for semaphores, they work very differently in many important respects, so be sure you understand the differences.
In order to allocate shared memory, you'll use the shmget
command, which looks like this:
shmget(key_t key, int size, int shmflg);
Which is, as I said, looks similar to semget. The return value is still
the identifier, and the shmflg has all the same arguments (IPC_CREAT,
IPC_EXCL, permissions (see the semaphore recitation notes for
an explanation)) as for semget
The thing that's different is the second parameter, which is used to specify
the size of the shared memory segment. Like malloc, you need to
make sure you specify the amount of space (in bytes) that you will be using.
This is one of those places where C is uglier than C++; C++ takes care of these
details for you when you use the new command, but in C it's your
problem. Fortunately, you needn't know what the size of each data type is (which
is very good since it's platform dependant), thanks to the sizeof
operator, which takes a parenthesized data-type or a variable name and returns
it's space in bytes. So sizeof(char) returns 1, sizeof(struct
mystruct) returns the number of bytes your struct occupies, and sizeof
a returns the number of bytes for whatever type a is. So
a typical call to semget to create a shared array of ten characters
would be:
key_t mykey;
int shmid;
mykey=ftok(".", 'n');
shmid=shmget(mykey, 10*(sizeof(char)), IPC_CREAT | IPC_EXCL | 0666);
//error-check shmid against -1
Once again, the key parameter to ftok can be IPC_PRIVATE,
if you want to make a shared memory block that no other process can access,
so you don't care what the key is. Also, you only want to use the IPC_CREAT
and IPC_EXCL flags for the process making the shared memory. All
your other processes which use that memory should leave those flags out or the
call will fail.
In the above example, all I wanted were characters, but if you wanted several
different data types, there's no reason you can't get them all in one block,
by using, say, sizeof(char)+6*(sizeof(int)) as the second parameter
to shmget. The only drawback will be the added difficulty of finding
the address of each piece of the block later (I'll explain in the next section).
A quick aside: the error checking done in many of the Monkey Book's example programs in Chapter 8 is quite good, so if you are still having trouble doing error checking, follow their examples.
Unlike semaphores, just creating the shared memory isn't enough; you also have
to attach it to your program. You do this with the appropriately named shmat
command:
shmat(int shmid, void *shmaddr, int shmflg);
The first argument is self explanatory by now. The next one is used to try
to control where in memory the shared segment is placed; you will never care
for the purposes of this class, so just pass it a 0 (if you are
curious what exactly it does, see pg 200). The last one lets you set any flags.
The only one you might want is SHM_RDONLY, to make it read-only
for that process. Usually though, you'll want the default permission of read/write,
so that can be 0 as well.
shmat will return a void*; i.e. a non-typed pointer
to the beginning of the memory segment. If you want to the shared memory for
a single variable, just cast it to the appropriate pointer type. So assuming
I had used shmget already to get memory with id shmid:
int *a;
a = (int *)shmat(shmid, 0, 0);
would let you use a just like a normal integer pointer. Similarly,
if you want an array, the same sort of casting will work. All array indexes
do is add the appropriate amount to a base pointer and dereference, so if in
the example above the shared memory segment had been created with space for
five integers (5*sizeof(int)), then an assignment such as a[2]=3;
would be perfectly valid.
Things get uglier if you want to mix types. Consider the following code to get an array of 3 characters and an integer:
key_t mykey;
int shmid;
int *x;
char *cs;
void *base;
mykey=ftok(".", 'm');
shmid=shmget(mykey, 3*(sizeof(char))+sizeof(int), IPC_CREAT | IPC_EXCL | 0666);
//error checking should be done here
base = shmat(int shmid, 0, 0);
//error checking should be done here too
x = (int *)base;
a = (char *)(base+sizeof(int));
This will work fine, but it gets progressively uglier if you are doing several variables all in one block. I find it easier to just make another shared memory for different types. Remember that there is a system limit on shared memories though, so sometimes it's better to use only one instead of clogging up the machine with a lot of small separate segments.
This is the easy part. Once you've done all that, you can pretend it's a regular
variable (except that you need to control access to it). So you can use *x=8;
and a[1]='b'; and anything else you are used to doing with pointers.
Again, cleaning up your shared memory, as with semaphores, is VERY IMPORTANT. There's
an extra step with shared memory, and that is detaching it. Detach is done
using, you guessed it, shmdt(void *shmaddr); where shmaddr is just
the pointer you assigned shmat to (recast as a void*).
Then you need to actually delete the shared memory, which is done using the
shmctl call, just like semctl for semaphores. You
can also use shmctl for other things (see pg 198) but unlike semaphores
there's no need to initialize shared memory with shmctl; just initialize
it as you would a normal variable. The delete call for the above shared memory
is:
shmctl(shmid, IPC_RMID, (struct shmid_ds *)0);
The last parameter is ignored for removing, just as it was it semctl,
but you should still cast the null pointer appropriately.
Important note: Although every process needs to shmdt, only
one removes the shared memory. The standard way of doing it
is, whichever process actually created the memory (using the IPC_CREAT
flag for shmget) is responsible for deleting it. Obviously in some
applications that won't be the best method, but it's a good rule of thumb.nd
anything else you are used to doing with pointers.
memcpy and Shared Memory with FilesA useful function worth mentioning is memcpy (pg 208). While it's
not restricted to shared memory, this is the first real use for it we've seen.
The call is memcpy(void *s1, void *s2, size_t n) What it does it
take the contents of the memory starting at s2 and copy n
bytes to s1. It does not allocate memory for you,
and it does not check that there is enough space in s1.
So as usual in C, be sure that you have allocated space before calling the function.
The rest of the chapter covers using files as a form of shared memory. While it's interesting, I doubt very much that you'll be doing it in this class, so don't feel like you have to read through it; I will only cover it if we have extra time in the next couple of weeks.
Stuart Morgan, 2003