201 lines
4.6 KiB
C
201 lines
4.6 KiB
C
/* This file is part of the sample code and exercises
|
|
* used by the class "Advanced Programming in the UNIX
|
|
* Environment" taught by Jan Schaumann
|
|
* <jschauma@netmeister.org> at Stevens Institute of
|
|
* Technology.
|
|
*
|
|
* This file is in the public domain.
|
|
*
|
|
* You don't have to, but if you feel like
|
|
* acknowledging where you got this code, you may
|
|
* reference me by name, email address, or point
|
|
* people to the course website:
|
|
* https://stevens.netmeister.org/631/
|
|
*/
|
|
|
|
/* A simple program to illustrate the use of
|
|
* semaphores. Derived from:
|
|
* https://www.beej.us/guide/bgipc/html/multi/semaphores.html
|
|
*
|
|
* Run in parallel in multiple processes to illustrate
|
|
* the blocking as another process holds a lock.
|
|
*
|
|
* Use ipcs(1) to show that semaphores remain in the
|
|
* kernel until explicitly removed.
|
|
*/
|
|
#include <sys/ipc.h>
|
|
#include <sys/sem.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAX_RETRIES 10
|
|
|
|
/* Code examples often use a char as the ftok(3) id,
|
|
* but really any int will do. */
|
|
#define SEMID 42
|
|
#define SEMLOCK -1
|
|
#define SEMUNLOCK 1
|
|
|
|
#ifndef __DARWIN_UNIX03
|
|
union semun {
|
|
int val;
|
|
struct semid_ds *buf;
|
|
ushort *array;
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* initsem() -- simplified version of W. Richard
|
|
* Stevens' UNIX Network Programming 2nd edition,
|
|
* volume 2, lockvsem.c, page 295;
|
|
*/
|
|
int
|
|
initsem(key_t key) {
|
|
int i;
|
|
union semun arg;
|
|
struct semid_ds buf;
|
|
struct sembuf sb;
|
|
int semid;
|
|
|
|
arg.buf = &buf;
|
|
|
|
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
|
|
if (semid >= 0) { /* we got it first */
|
|
sb.sem_num = 0; /* operate on the first semaphore in the set */
|
|
sb.sem_op = 1; /* "free" the semaphore */
|
|
sb.sem_flg = 0; /* no flags are set */
|
|
|
|
/* "Free" the semaphore. Note: since
|
|
* this semget, semop sequence is
|
|
* non-atomic, it poses a possible
|
|
* race condition whereby this process
|
|
* that created the semaphore might be
|
|
* suspended prior to freeing it. In
|
|
* that case, the second process in
|
|
* the 'EEXIST' block below...
|
|
*/
|
|
if (semop(semid, &sb, 1) == -1) {
|
|
int e = errno;
|
|
if (semctl(semid, 0, IPC_RMID) < 0) {
|
|
perror("semctl rm");
|
|
}
|
|
errno = e;
|
|
return -1; /* error, check errno */
|
|
}
|
|
|
|
} else if (errno == EEXIST) { /* someone else got it first */
|
|
int ready = 0;
|
|
int e;
|
|
|
|
semid = semget(key, 1, 0); /* get the id */
|
|
if (semid < 0)
|
|
return semid; /* error, check errno */
|
|
|
|
/* ...would see sem_otime as '0',
|
|
* sleep for a second, and attempt
|
|
* again up to MAX_TRIES to give the
|
|
* semaphore-creating process above a
|
|
* chance to free it. Without this
|
|
* check, the race condition above
|
|
* could otherwise lead to a deadlock.
|
|
*/
|
|
for (i = 0; i < MAX_RETRIES && !ready; i++) {
|
|
if ((e = semctl(semid, 0, IPC_STAT, arg)) < 0) {
|
|
perror("semctl stat");
|
|
return -1;
|
|
}
|
|
|
|
if (arg.buf->sem_otime != 0) {
|
|
ready = 1;
|
|
} else {
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
/* If the other process didn't free
|
|
* the semaphore after MAX_TRIES
|
|
* seconds, give up. Note: we set
|
|
* errno explicitly ourselves here. */
|
|
if (!ready) {
|
|
errno = ETIME;
|
|
return -1;
|
|
}
|
|
} else {
|
|
return semid; /* error, check errno */
|
|
}
|
|
|
|
return semid;
|
|
}
|
|
|
|
int
|
|
main(void) {
|
|
key_t key;
|
|
int semid;
|
|
struct sembuf sb = {
|
|
.sem_num = 0,
|
|
.sem_op = SEMLOCK,
|
|
.sem_flg = SEM_UNDO,
|
|
};
|
|
|
|
/* What happens if you use e.g., getuid() as
|
|
* the 'id' argument to ftok(3)?
|
|
*
|
|
* What if the user running the command
|
|
* doesn't have read permissions to the file
|
|
* here?
|
|
*
|
|
* Give it a try. */
|
|
if ((key = ftok("./semdemo.c", SEMID)) == -1) {
|
|
err(EXIT_FAILURE, "ftok");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/* What happens if you don't use ftok(3)?
|
|
* Give it a try. */
|
|
//key = IPC_PRIVATE;
|
|
|
|
if ((semid = initsem(key)) == -1) {
|
|
err(EXIT_FAILURE, "initsem");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
(void)printf("Press return to lock: ");
|
|
(void)getchar();
|
|
(void)printf("Trying to lock...\n");
|
|
|
|
/* This call will block if another process
|
|
* already has a lock. */
|
|
if (semop(semid, &sb, 1) == -1) {
|
|
err(EXIT_FAILURE, "semop");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
(void)printf("Locked.\n");
|
|
(void)printf("Press return to unlock: ");
|
|
(void)getchar();
|
|
|
|
/* Since we specified SEM_UNDO above, we don't
|
|
* stricly need to free the resource here; our
|
|
* process exiting would increment the
|
|
* semaphore value. (Try it out: comment out
|
|
* this block and set sem_flg = 0 above.)
|
|
* However, just as with cleaning up file
|
|
* descriptors and other resources, it's a
|
|
* good idea to be explicit and free the
|
|
* resource on exit. */
|
|
sb.sem_op = SEMUNLOCK;
|
|
if (semop(semid, &sb, 1) == -1) {
|
|
err(EXIT_FAILURE, "semop");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
(void)printf("Unlocked.\n");
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|