Lab 4: Semaphores & Shared Memory

  1. Semaphores
    /*
     * % g++ semaphore.cc -o semaphore -lpthread
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <sys/sem.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    
    #define N      10
    #define LOOP   5
    
    /* A global variable protected by the mutex above */
    int counter = 0;
    
    /* A semaphore set id */
    int semid;
    
    /*
     * Simulate mutex init: allocate a semaphore
     */
    void my_mutex_init(void) {
        struct sembuf op;
    
        /*
         * Quoted from the man page: The name choice IPC_PRIVATE was perhaps 
         * unfortunate, IPC_NEW would more clearly show its function.
         * Alternatively, instead of IPC_PRIVATE, you can pick one yourselves
         * as long as it's unique system-wise.
         *
         * 2nd paramter could be > 1, in that case, you could get a set/group
         * of semaphores.
         *
         * 0600 means rw permission from its own process.
         */
        if ((semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)) < 0) {
    	fprintf(stderr, "Failed to allocate a semop: %s\n", strerror(errno));
    	exit(1);
        }
    
        // Up the semaphore by 1
        op.sem_num = 0;    // Could be 1, 2, ... if you have more semaphores
        op.sem_op = +1;
        op.sem_flg = 0;
    
        if (semop(semid, &op, 1) < 0) {
    	fprintf(stderr, "Failed to set initial value to a semop %d: %s\n",
    		semid, strerror(errno));
    	exit(1);
        }
    }
    
    /*
     * Simulate mutex lock: decrement the semaphore by 1
     */
    void my_mutex_lock(void) {
        struct sembuf op;
        op.sem_num = 0;
        op.sem_op = -1;
        op.sem_flg = 0;
    
        if (semop(semid, &op, 1) < 0) {
    	fprintf(stderr, "Failed to decrement semop %d: %s\n",
    		semid, strerror(errno));
    	exit(1);
        }
    }
    
    /*
     * Simulate mutex unlock: increment the semaphore by 1
     */
    void my_mutex_unlock(void) {
        struct sembuf op;
        op.sem_num = 0;
        op.sem_op = +1;
        op.sem_flg = 0;
    
        if (semop(semid, &op, 1) < 0) {
    	fprintf(stderr, "Failed to increment semop %d: %s\n",
    		semid, strerror(errno));
    	exit(1);
        }
    }
    
    /*
     * Simulate mutex destroy: destroy the semaphore
     * VERY IMPORTANT: without that, semaphore resource is not freed
     */
    void my_mutex_destroy() {
        if (semctl(semid, 0, IPC_RMID) < 0) {
    	fprintf(stderr, "Failed to destroy semop %d: %s\n",
    		semid, strerror(errno));
    	exit(1);
        }
    }
    
    /* 
     * A callback function assigned to a thread via pthread_create()
     */
    void *client(void *p) {
        int i, old_i, new_i;
    
        for (i = 0; i < LOOP; i++) {
    	usleep(rand() % 1000);
    	my_mutex_lock();
    	old_i = counter;
    	new_i = ++counter;
    	my_mutex_unlock();
    
    	printf("Thread %d (%x): changed counter from %d to %d\n",
    	       (int)p, pthread_self(), old_i, new_i);
    	fflush(stdout);
        }
    
        return (NULL);
    }
    
    int main(void) {
        int i;
        pthread_t tid[N];
    
        my_mutex_init();
    
        for (i = 0; i < N; i++) {
    	/* Create threads and assign client as callback functions */
    	if (pthread_create(&tid[i], NULL, client, (void *)i)) {
    	    fprintf(stderr, "Failed to create thread: %s\n", strerror(errno));
    	    // Continue
    	}
        }
    
        /* Wait for threads to exit */
        for (i = 0; i < N; i++) {
    	pthread_join(tid[i], NULL);
        }
    
        my_mutex_destroy();
    
        return (0);
    }
    
  2. Shared Memory
    #include <stdio.h>
    #include <sys/shm.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/wait.h>
    
    int main(void) {
        int shmid;    // A shared memory id
        int *ptr;
    
        // Allocate a chunk/segment of shared memory
        if ((shmid = shmget(IPC_PRIVATE, sizeof(int), 0600 | IPC_CREAT)) < 0) {
    	fprintf(stderr, "Failed to get a chunk of shared memory: %s\n",
    		strerror(errno));
    	return (1);
        }
    
        // Map the shared memory to a variable
        ptr = (int *)shmat(shmid, NULL, 0);
        if ((int)ptr == -1) {
    	fprintf(stderr, "Failed to map shared memory: %s\n", strerror(errno));
    	return (1);
        }
    
        switch (fork()) {
        case -1:
    	fprintf(stderr, "Failed to call fork(): %s\n", strerror(errno));
    	break;
        case 0:
    	// Child
    
    	// Change memory in child process should also change memory in parent's
    	// since they shared the same memory.
    	*ptr = 1234;
    	break;
        default:
    	// Parent
    	if (wait(NULL) < 0) {
    	    fprintf(stderr, "Failed to call wait(): %s\n", strerror(errno));
    	}
    
    	// Should be 1234, same as child process!
    	printf("ptr = %d\n", *ptr);
    
    	// Make sure only one process destroy the shared memory after use.
    	// VERY INPORTANT to destroy the shared memory you are no longer used
    	if (shmctl(shmid, IPC_RMID, NULL) < 0) {
    	    fprintf(stderr, "Failed to destroy shared memory: %s\n",
    		    strerror(errno));
    	}
    	break;
        }
    
        return (0);
    }