Semaphore in Unix Using C Programming

🎯 Introduction

In concurrent programming, synchronization is essential to prevent race conditions and ensure orderly access to shared resources. Semaphores are a synchronization mechanism commonly used in Unix-based systems to achieve this purpose. Semaphores are integer variables that can be used for signaling between processes and controlling access to shared resources.

This blog post focuses on implementing a simple producer-consumer scenario using semaphores in C programming on a Unix-based system. The producer process will produce items and store them in a shared buffer, and the consumer process will consume these items from the buffer. The two processes will be synchronized using semaphores to avoid conflicts during read and write operations on the shared buffer.

🎯 Concept

👉 Semaphores

Semaphores are a synchronization technique used to control access to shared resources in concurrent programs. They can take non-negative integer values and support two main operations:

Wait (P) Operation: Decreases the value of the semaphore. If the semaphore's value becomes negative, the process is blocked until the semaphore becomes positive.

Signal (V) Operation: Increases the value of the semaphore. If there are blocked processes waiting for the semaphore, one of them is allowed to proceed.

👉 Producer-Consumer Problem:

The producer-consumer problem is a classic synchronization problem where one or more producer processes produce items and place them into a shared buffer, and one or more consumer processes consume these items from the buffer. The goal is to ensure that the producer doesn't try to produce items when the buffer is full, and the consumer doesn't try to consume items when the buffer is empty.

🎯 Semaphores Detailed Explanation

👉 Header Files and Definitions:

The code includes necessary header files such as <stdio.h>, <sys/types.h>, <sys/sem.h>, <unistd.h>, and <sys/ipc.h>. It also defines a semaphore key KEY and a union semun to manipulate semaphore values.


👉 Semaphores Producer (producer.c):

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/sem.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <fcntl.h>

#include <errno.h>


#define KEY 1010


union semun {

    int val;

    struct semid_ds *buf;

    unsigned short *array;

    struct seminfo *__buf;

};


int create_semaphore(int nsems) {

    int semid = semget(KEY, nsems, IPC_CREAT | IPC_EXCL | 0666);

    if (semid == -1) {

        perror("semget");

        exit(EXIT_FAILURE);

    }

    return semid;

}


void set_semaphore(int semid, unsigned short *values) {

    union semun u;

    u.array = values;

    if (semctl(semid, 0, SETALL, u) == -1) {

        perror("semctl");

        exit(EXIT_FAILURE);

    }

}


void main() {

    int semid, fd;

    struct sembuf put, get;

    union semun u;

    unsigned short init_values[2] = {1, 0};


    semid = create_semaphore(2);

    set_semaphore(semid, init_values);


    fd = open("buff", O_CREAT | O_RDWR, 0666);

    if (fd == -1) {

        perror("open");

        exit(EXIT_FAILURE);

    }


    int count = 0;

    write(fd, &count, sizeof(int));


    while (1) {

        put.sem_num = 0;

        put.sem_op = -1;

        put.sem_flg = 0;

        if (semop(semid, &put, 1) == -1) {

            perror("semop");

            exit(EXIT_FAILURE);

        }


        lseek(fd, 0, 0);

        read(fd, &count, sizeof(int));

        count++;

        lseek(fd, 0, 0);

        write(fd, &count, sizeof(int));

        printf("Produced item: %d\n", count);


        get.sem_num = 1;

        get.sem_op = 1;

        get.sem_flg = 0;

        if (semop(semid, &get, 1) == -1) {

            perror("semop");

            exit(EXIT_FAILURE);

        }


        sleep(5);

    }


    close(fd);

}


👉 Semaphores Consumer (consumer.c):

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/sem.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <fcntl.h>

#include <errno.h>


#define KEY 1010


union semun {

    int val;

    struct semid_ds *buf;

    unsigned short *array;

    struct seminfo *__buf;

};


int get_semaphore() {

    int semid = semget(KEY, 0, 0);

    if (semid == -1) {

        perror("semget");

        exit(EXIT_FAILURE);

    }

    return semid;

}


void main() {

    int semid, fd;

    struct sembuf put, get;


    semid = get_semaphore();


    fd = open("buff", O_RDONLY);

    if (fd == -1) {

        perror("open");

        exit(EXIT_FAILURE);

    }


    int count;


    while (1) {

        get.sem_num = 1;

        get.sem_op = -1;

        get.sem_flg = 0;

        if (semop(semid, &get, 1) == -1) {

            perror("semop");

            exit(EXIT_FAILURE);

        }


        lseek(fd, 0, 0);

        read(fd, &count, sizeof(int));

        printf("Consumed item: %d\n", count);


        put.sem_num = 0;

        put.sem_op = 1;

        put.sem_flg = 0;

        if (semop(semid, &put, 1) == -1) {

            perror("semop");

            exit(EXIT_FAILURE);

        }

        sleep(5);

    }


    close(fd);

}

🎯 Summary

This blog post discussed the implementation of a simple producer-consumer scenario using semaphores in C programming on a Unix-based system. Semaphores were used to synchronize the producer and consumer processes and avoid race conditions while accessing the shared buffer. The producer produced items and placed them in the buffer, while the consumer consumed items from the buffer. The semaphores controlled access to the buffer and ensured proper synchronization between the two processes.

🎯 Key Points