This document will provide an overview of semaphore functionality, and will end with a program that uses semaphores to control access to a file. (This task, admittedly, could easily be handled with file locking, but it makes a good example since it's easier to wrap your head around than, say, shared memory.)
How do you create the semaphore set? It's done with a call to semget(), which returns the semaphore id (hereafter referred to as the semid):
#include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
What's the key? It's a unique identifier that is used by different processes to identify this semaphore set. (This key will be generated using ftok(), described in the Message Queues document.)
The next argument, nsems, is (you guessed it!) the number of semaphores in this semaphore set. The exact number is system dependent, but it's probably between 500 and 2000. If you're needing more (greedy wretch!), just get another semaphore set.
Finally, there's the semflg argument. This tells semget() what the permissions should be on the new semaphore set, whether you're creating a new set or just want to connect to an existing one, and other things that you can look up. For creating a new set, you can bit-wise or the access permissions with IPC_CREAT.
Here's an example call that generates the key with ftok() and creates a 10 semaphore set, with 666 (rw-rw-rw-) permissions:
#include <sys/ipc.h> #include <sys/sem.h> key_t key; int semid; key = ftok("/home/beej/somefile", 'E'); semid = semget(key, 10, 0666 | IPC_CREAT);
Congrats! You've created a new semaphore set! After running the program you can check it out with the ipcs command. (Don't forget to remove it when you're done with it with ipcrm!)
struct sembuf { ushort sem_num; short sem_op; short sem_flg; };
Of course, sem_num is the number of the semaphore in the set that you want to manipulate. Then, sem_op is what you want to do with that semaphore. This takes on different meanings, depending on whether sem_op is positive, negative, or zero, as shown in the following table:
sem_op | What happens |
---|---|
Positive | The value of sem_op is added to the semaphore's value. This is how a program uses a semaphore to mark a resource as allocated. |
Negative | If the absolute value of sem_op is greater than the value of the semaphore, the calling process will block until the value of the semaphore reaches that of the absolute value of sem_op. Finally, the absolute value of sem_op will be subtracted from the semaphore's value. This is how a process releases a resource guarded by the semaphore. |
Zero | This process will wait until the semaphore in question reaches 0. |
So, basically, what you do is load up a
int semop(int semid ,struct sembuf *sops, unsigned int nsops);
The semid argument is the number obtained from the call
to semget(). Next is sops, which is a pointer
to the
One field in the
One of these flags is IPC_NOWAIT which, as the name suggests, causes the call to semop() to return with error EAGAIN if it encounters a situation where it would normally block. This is good for situations where you might want to "poll" to see if you can allocate a resource.
Another very useful flag is the SEM_UNDO flag. This causes semop() to record, in a way, the change made to the semaphore. When the program exits, the kernel will automatically undo all changes that were marked with the SEM_UNDO flag. Of course, your program should do its best to deallocate any resources it marks using the semaphore, but sometimes this isn't possible when your program gets a SIGKILL or some other awful crash happens.
Now, I'm trying to compile this code under both Linux and HPUX, but I've
found the the system calls differ. Linux passes a
Here is the Linux-style
union semun { int val; /* used for SETVAL only */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ ushort *array; /* used for GETALL and SETALL */ }; int semctl(int semid, int semnum, int cmd, union semun arg);
Notice that
int semctl(int semid, int semnum, int cmd, ... /*arg*/);
In HPUX, instead of passing in a
Where were we? Oh yeah--destroying a semaphore. Basically, you want to set semid to the semaphore ID you want to axe. The cmd should be set to IPC_RMID, which tells semctl() to remove this semaphore set. The two parameters semnum and arg have no meaning in the IPC_RMID context and can be set to anything.
Here's an example call to torch a semaphore set:
union semun dummy; int semid; . . semid = semget(...); . . semctl(semid, 0, IPC_RMID, dummy);
Easy peasy.
I get around this problem in the sample code by having a single process that creates and initializes the semaphore. The main process just accesses it, but never creates or destroys it.
Just be on the lookout. Stevens refers to this as the semaphore's "fatal flaw".
The idea is to run seminit.c to create the semaphore. Try using ipcs from the command line to verify that it exists. Them run semdemo.c in a couple of windows and see how they interact. Finally, use semrm.c to remove the semaphore. You could also try removing the semaphore while running semdemo.c just to see what kinds of errors are generated.
Here's seminit.c (run this first!):
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main(void) { key_t key; int semid; union semun arg; if ((key = ftok("semdemo.c", 'J')) == -1) { perror("ftok"); exit(1); } /* create a semaphore set with 1 semaphore: */ if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) { perror("semget"); exit(1); } /* initialize semaphore #0 to 1: */ arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(1); } return 0; }
Here's semdemo.c:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main(void) { key_t key; int semid; struct sembuf sb = {0, -1, 0}; /* set to allocate resource */ if ((key = ftok("semdemo.c", 'J')) == -1) { perror("ftok"); exit(1); } /* grab the semaphore set created by seminit.c: */ if ((semid = semget(key, 1, 0)) == -1) { perror("semget"); exit(1); } printf("Press return to lock: "); getchar(); printf("Trying to lock...\n"); if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(1); } printf("Locked.\n"); printf("Press return to unlock: "); getchar(); sb.sem_op = 1; /* free resource */ if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(1); } printf("Unlocked\n"); return 0; }
Here's semrm.c:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main(void) { key_t key; int semid; union semun arg; if ((key = ftok("semdemo.c", 'J')) == -1) { perror("ftok"); exit(1); } /* grab the semaphore set created by seminit.c: */ if ((semid = semget(key, 1, 0)) == -1) { perror("semget"); exit(1); } /* remove it: */ if (semctl(semid, 0, IPC_RMID, arg) == -1) { perror("semctl"); exit(1); } return 0; }
Isn't that fun! I'm sure you'll give up Quake just to play with this semaphore stuff all day long!
Whenever you have multiple processes running through a critical section of code, man, you need semaphores. You have zillions of them--you might as well use 'em.
Copyright © 1997 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information.