/* norace.c Started by Jeff Ondich on 4/12/96 Last modified on 2/25/22 This program is nearly identical to "race_with_lock.c", but uses a binary semaphore rather than a lock variable to guarantee mutually exclusive access to the shared memory. race_with_lock.c fails because lock variables cannot be tested and modified in a single atomic action, while norace.c succeeds because of the atomicity of the UP and DOWN operations. As usual, the comments below are overdone for tutorial purposes. Please don't emulate this. */ #include #include #include #include #include #include #include #include #define MUTEX_KEY ((key_t)1234) #define SHARED_INT_KEY ((key_t)5678) // This variable is global so interrupt_handler can detach it int *shared_integer; // prototypes void *create_shared_memory(key_t, int); int create_semaphore(key_t, int); void up(key_t); void down(key_t); void interrupt_handler(int); int main() { int pid, temp; // The infinite loops below will require us to hit Ctrl-C // to end the program. We need to clean up the semaphores // and shared memory before we go. signal(SIGINT, interrupt_handler); // Get and attach shared memory block for the shared integer. shared_integer = (int *)create_shared_memory(SHARED_INT_KEY, sizeof(int)); // Create a semaphore and initialize it to 1. create_semaphore(MUTEX_KEY, 1); // Fork a child process. The child and the parent are // going to have a fight. The child wants the shared // integer to be 3, and the parent wants it to be 2. pid = fork(); if (pid == -1) { perror("fork() failure"); exit(1); } if (pid != 0) { // Parent loops forever, putting 3's into the shared // integer using the semaphore to enforce mutual exclusion. printf("Parent [%d] starting\n", getpid()); while (1) { down(MUTEX_KEY); *shared_integer = 3; up(MUTEX_KEY); } } else { // Child loops forever, putting 2's into the shared // integer using the semaphore to enforce mutual exclusion. printf("Child [%d] starting\n", getpid()); while (1) { down(MUTEX_KEY); *shared_integer = 2; temp = *shared_integer; up(MUTEX_KEY); if (temp != 2) { printf("Race\n"); } } } return 0; } // Allocates a block of shared memory with the specified key and // of the specified size. Aslo attaches the shared memory to the // current process' address space, and returns the attached address. void *create_shared_memory(key_t key, int size) { // Create the shared memory. int id = shmget(key, size, 0664 | IPC_CREAT | IPC_EXCL); if (id == -1) { perror("Trouble allocating shared memory"); interrupt_handler(0); } // Attach the shared memory to the current process' address space. void *address = (char *)shmat(id, (char *)0, 0); if (address == (char *)-1) { perror("Trouble attaching shared memory"); interrupt_handler(0); } return address; } // For use in create_semaphore union semun { int val; // value for SETVAL struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET unsigned short int *array; // array for GETALL, SETALL struct seminfo *__buf; // buffer for IPC_INFO }; // Creates a single semaphore with the specified key, // initializes it to the specified value, and returns the // semaphore id. // // Note that System V (and thus linux) only give you tools // for manipulating *arrays* of semaphores, so what we're // doing here is allocating an array of one semaphore. int create_semaphore(key_t key, int value) { int id = semget(key, 1, 0664 | IPC_CREAT | IPC_EXCL); if (id == -1) { perror("Couldn't create semaphore"); interrupt_handler(0); } union semun arg; arg.val = value; if (semctl(id, 0, SETVAL, arg) == -1) { perror("Can't set semaphore value"); interrupt_handler(0); } return id; } // Performs an UP operation on the semaphore with the specified key void up(key_t key) { int id; struct sembuf sops[1]; // Operate on semaphore number 0 in the array of semaphores sops[0].sem_num = 0; // Add 1 to the semaphore's value--this is what makes this an "up" sops[0].sem_op = 1; // The flags IPC_NOWAIT and SEM_UNDO are not relevant // for our purposes, so we make the flags 0 sops[0].sem_flg = 0; // The 1 in the semop() call tells semop() how many operations are // in the array "sops". We're doing one UP, and that's all. id = semget(key, 0, 0); if (id == -1 || semop(id, sops, 1) == -1) { fprintf(stderr, "[%d] ", getpid()); perror("Trouble doing UP"); } } // Performs a DOWN operation on the semaphore with the specified key. void down(key_t key) { int id; struct sembuf sops[1]; // Operate on semaphore number 0 in the array of semaphores sops[0].sem_num = 0; // Subtract 1 from the semaphore's value--this is what // makes this a "down" sops[0].sem_op = -1; // The flags IPC_NOWAIT and SEM_UNDO are not relevant // for our purposes, so we make the flags 0 sops[0].sem_flg = 0; // The 1 in the semop() call tells semop() how many operations are // in the array "sops". We're doing one DOWN, and that's all. id = semget(key, 0, 0); if (id == -1 || semop(id, sops, 1) == -1) { fprintf(stderr, "[%d] ", getpid()); perror("Trouble with DOWN"); } } void clean_up_shared_memory() { int id = shmget(SHARED_INT_KEY, 0, 0); shmctl(id, IPC_RMID, NULL); shmdt(shared_integer); union semun arg; id = semget(MUTEX_KEY, 0, 0); semctl(id, IPC_RMID, 0, arg); } void interrupt_handler(int sig) { fprintf(stderr, "%d quitting\n", getpid()); clean_up_shared_memory(); exit(1); }