IPCs part I
Semaphores

ipcs and ipcrm

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.

IPCs: What the heck are they?

IPCs are sets of commands and constructs designed to help programmers write programs that can communicate with each other in various ways. The three types are semaphores (which we will cover this week), shared memory (next week), and message queues (never). Read sections 6.1 and 6.2 for an overview of what they are, then read chapter 7 for semaphores.

Semaphores are an extremely useful construct, which can be used to implement many of the algorithms you have seen in class. They come in two types, binary (which are essentially boolean variables) and counting. The really nice thing about semaphores, that makes them better than, say, ints, is that the operations on them are atomic (un-interruptible), which is incredibly important in distributed programs, and that they are relatively easy to share between processes.

The theory behind them is what you have been covering in lecture, so we'll skip right to how to code with them.

Getting a Semaphore: semget

In order to declare/create a semaphore, you'll use the semget command (pg 169):

int semget(key_t key, int nsems, int semflg);

The return value is the identifier for the semaphore, which you will need to perform operations on it.

nsems is the number of semaphores you want; usually, you get all your semaphores in one big block (basically an array of semaphores, but you don't have to deal with the implementation) instead of lots of individual ones because it makes your code cleaner.
semflag is used for a number of things. The first is permissions for the semaphores, which you set with an octal number that works like the arguments to chmod, like 0666 for read-write all (the 0 preceding it makes it an octal number instead of decimal). Combined with that using bitwise-or (|) you can have IPC_CREAT, to make new semaphores if they don't exist already, or IPC_EXCL, which causes the call to fail if the semaphore set already exists (which lets you make sure you are getting a new set, not using someone else's by accident).
key is used to identify what semaphores you'd like to access, since semget can either create new semaphores, or give you access to semaphores created by another process. The trick here is that you want a key that your other programs can also figure out, but which won't accidentally be used by some other process. The ftok command is designed for that purpose; it takes a file path and a character, and return a unique key composed of the file id plus the character Often you will use "." (the current directory) as the path. That makes it easy for your programs to pick the same thing if they are run from the same directory, but unlikely that other programs will. Here's how a call to get 3 semaphores might look:

key_t mykey;
int semid;
mykey=ftok(".", 'n');
semid=semget(mykey, 3, IPC_CREAT | IPC_EXCL | 0666);
//error-check semid against -1

Alternately, the key parameter to ftok can be IPC_PRIVATE, if you want to make a semaphore that no other process can access, so you don't care what the key is.

Now some other process can get an id referring to the same semaphore, by using the same key parameter (but without using the IPC_CREAT or IPC_EXCL).

Controlling Semaphores: semctl

Once you have a semaphore, you will need to initialize it using semctl. This command does a lot of things, most of which I won't cover (section 7.3 does a pretty good job). No matter what you use it for, you'll need to put this declaration in the beginning of your code (pg 174):

union semun {
   int val;
   struct semid_ds *buf;
   ushort *array;
};

This should be in the <sys/sem.h> file, but for some reason it's not on Solaris machines, so you have to include it yourself.

The prototype for semctl is (pg 173):

int semctl(int semid, int semnum, int cmd, union semun arg);

semid and semnum are easy; semid is what semget returns, and semnum is the number of semaphores in the set (i.e. the second argument to semget). cmd is one of many things (see pgs. 174-175), but you will probably only ever need SETALL (to initialize all the semaphores to a starting value) and IPC_RMID (to remove the semaphores when you finish). The last parameter is where things get kind of weird; a union is a type which depends on runtime factors. Here it's sometimes an int, and sometimes a pointer to an array of unsigned shorts (the values of semaphores), and sometimes another type which you wont have to deal with. While that's a somewhat confusing concept, it makes some sense considering that there are so many different actions semctl can take.

Luckily, we'll only use semctl for a few purposes, so there's only a couple of ways we'll use arg. For IPC_RMID the fourth argument is ignored, so you can just pass 0. For SETALL it's an array pointer, where the array has the values you want the semaphores to have. Since the union will be used as an array, you have to initialize the array field of the union you pass in. For example, if we have used the semget call above and want to initialize all three semaphores to 1, we'll need these declarations at the top of our function:

union semun arg; //to declare an instance of the semun to pass
ushort initVals[3]={1,1,1}; //the initial values we want

Then when we are ready in our code to initialize the semaphores, we set arg to the array we made, so we can pass it to semctl:

arg.array=initVals; //no [], to make it a pointer
semctl(sem_id, 3, SETALL, arg); //there should be error checking here

The semnum argument to semctl is ignored in the case of SETALL and IPC_RMID, so you can just pass it 0 for those commands.

Using Semaphores: semop

While you could use semctl to do all of your acquire/release operations, you really really don't want to: it's harder, and it probably won't end up working right in a distributed system. Instead, you use semop (pg 179):

int semop(int semid, struct sembuf *sops, size_t nsops);

where sembuf is declared as (pg 180):

struct sembuf {
   ushort sem_num;
   short sem_op;
   short sem_flg;
};

(This declaration is in the header files, so you won't need to put it in your code.) sem_num is simply the index of the semaphore that you want to operate on. I won't get too much into the details of how sem_op and sem_flg interact, but the Monkey book has a thorough explanation of what they do (pg 181). The short version, which lets you do basic operations, is the following (where semval is the current value of the semaphore being operated on):

  1. If sem_op is negative, and smaller in magnitude than the semval, then semval+=sem_op
    This corresponds to decrementing the semaphore by the magnitude of sem_op.
  2. If sem_op is negative, and larger in magnitude than the semval, the call blocks until the semval is at least as large, then acts as above.
    This corresponds to attempting to decrement the semaphore, but having to wait to do so. A semval of 0 basically means that the resource protected by the semaphore is in use, which means that semval should never be negative.
  3. If sem_op is positive, semval+=sem_op
    This corresponds to incrementing the semaphore by the magnitude of sem_op.

So in the case of a binary semaphore, you initialize it to 1, case 1 with sem_op=-1 is "wait" when the resource is free, case 2 with sem_op=-1 is "wait" when the resource is not free, and case 3 with sem_op=1 is "signal".

Be sure to read all the details on pgs 179-182, and especially understand the charts. To see how to use these commands to implement signal and wait, look at Tekin's examples. I recommend you use these routines, or something like them, when dealing with semaphores. It saves you typing all the struct settings each time you want to wait or signal, and makes your code much easier to understand. When you see code with declarations initializing a sembuf struct with an array, it's just shorthand for initializing all of the elements one at a time. So these initializations are equivalent:

struct sembuf signal_buf = {0, 1, 0};

struct sembuf signal_buf;
signal_buf.sem_num=0;
signal_buf.sem_op=1;
signal_buf.sem_flg=0;

Stuart Morgan, 2003