Home > Back-end >  Print 1 and 2 unsing fork and System V semaphore in c
Print 1 and 2 unsing fork and System V semaphore in c

Time:08-02

I'd like to print 1 and 2 into console consequently. Desired output would be 121212121212.... 1s are printed by the child process. 2s are printed by the parent process. Processes are initiated by fork. The problem is that I get output like 11111222121212122222. Here is my code so far

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>


int main (int argc, char * argv[])
{
pid_t pid;
key_t key;
int semid;
int result;
int counter;
struct sembuf buf[1];
key = ftok("./one_two", 1);
semid = semget(key, 2, 0666|IPC_CREAT);
buf[0].sem_num = 0;
buf[0].sem_flg = SEM_UNDO;
buf[1].sem_num = 1;
buf[1].sem_flg = SEM_UNDO;
semctl(semid, 0, SETVAL, 0);
buf[0].sem_op = -1;
buf[1].sem_op = 1;
semop(semid,(struct sembuf*) &buf[1], 1);
counter = 1;
pid = fork();
if (pid>0)
{
    while(counter<100)
        {
        semop(semid,(struct sembuf*) &buf[0], 1);

        write(2, "1", 2);
              
        buf[0].sem_op = -1;
        buf[1].sem_op = 1;
        semop(semid,(struct sembuf*) &buf[1], 1);
        counter  ;
        if(counter >10 ) return 1;
        }
   
}
else
{
    
        while(counter<10)
        {semop(semid,(struct sembuf*) &buf[0], -1);
        write(2, "2", 2);
        buf[0].sem_op = -1;
        buf[1].sem_op = 1;
        semop(semid,(struct sembuf*) &buf[1], -1);
        counter  ;
        if(counter >10 ) return 1;
        }
   
}
return 0;
}

CodePudding user response:

Your semaphore initialisation and usage is a little messy.

I guess that what you want is

  • Process 0 :

    • must wait for semaphore 0 to be 0
    • print "1"
    • increase semaphore 0
    • decrease semaphore 1
  • Process 1 :

    • must wait for semaphore 1 to be 0
    • print "2"
    • decrease semaphore 0
    • increase semaphore 1

Problems

  • You don't initialize all semaphores semctl(semid, 0, SETVAL, 0); is okay, but where is semctl(semid, 1, SETVAL, xx);

  • You call semop with strange argument, ok for semop(semid,(struct sembuf*) &buf[1], 1); : you want to play on semaphore 1, but what about semop(semid,(struct sembuf*) &buf[0], -1); ??

  • Your call semop(semid, (struct sembuf *) &buf[1], 2) is wrong: you try to play on semaphore 1 and 2, but yours are 0 and 1

  • Your two processes don't wait for the same ending: first wait for counter to be more of 10, the second to be 10 or more)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>


int main(int argc, char *argv[])
{
    pid_t pid;
    key_t key;
    int semid;
    int counter;
    struct sembuf buf[2];
    key = ftok("./one_two", 1);
    semid = semget(key, 2, 0666 | IPC_CREAT);
    buf[0].sem_num = 0;
    buf[0].sem_flg = SEM_UNDO;
    buf[1].sem_num = 1;
    buf[1].sem_flg = SEM_UNDO;
    
    // set two semaphores values, don't use semop now
    semctl(semid, 0, SETVAL, 1);
    semctl(semid, 1, SETVAL, 0);

    counter = 1;
    pid = fork();
    if (pid > 0) {
        while (counter < 10) {
            // wait 0 to be 0
            buf[0].sem_op = 0;
            // only read first semaphore 
            semop(semid, buf, 1);

            write(2, "0", 2);

            // decrease 1, increase 0");
            buf[0].sem_op = 1;
            buf[1].sem_op = -1;

            semop(semid, buf, 2);

            counter  ;

        }

    } else {

        while (counter < 10) {
        
            // wait 1 to be 0
            buf[1].sem_op = 0;
            // only read second semaphore 
            semop(semid, buf 1, 1);
            
            write(2, "1", 2);

            // decrease 0, increase 1            
            buf[0].sem_op = -1;
            buf[1].sem_op = 1;
            semop(semid, buf, 2);

            counter  ;
            if (counter > 10) {
                puts("1: finished");
                return 1;
            }
        }

    }
    return 0;
}

CodePudding user response:

I personally prefer Linux-specific anonymous shared memory map with unnamed POSIX semaphores. That way, I can use a structure representing the shared memory. In this case, it will have two semaphores, and a (decrementing volatile) counter indicating how many turns (digits to print) there are left.

Here's an example implementation:

// SPDX-License-Identifier: CC0-1.0

// We want the standard C library to expose POSIX.1 features,
#define  _POSIX_C_SOURCE  200809L

// and also GNU features, since this is Linux-specific.
#define  _GNU_SOURCE

// Compile this (example.c) using e.g.
//    gcc -Wall -O2 example.c -lpthread -o ex

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <semaphore.h>  // Needs -lpthread
#include <string.h>
#include <stdio.h>
#include <errno.h>

static struct {
    sem_t           sem1;
    sem_t           sem2;
    volatile int    left;
} *shared_data = NULL;

static void do_parent(void)
{
    while (1) {

        // Wait on the first semaphore.
        if (sem_wait(&(shared_data->sem1)) == -1) {
            fprintf(stderr, "Parent: Failed to wait on semaphore 1: %s.\n", strerror(errno));
            return;
        }

        // Check if there are any turns left.
        if (shared_data->left < 1) {

            // No, we're done.  Parent will always print the final newline.
            printf("\n");
            fflush(stdout);

            // Post on the second semaphore to make sure the child knows to exit too.
            if (sem_post(&(shared_data->sem2)) == -1) {
                fprintf(stderr, "Parent: Failed to post on semaphore 2: %s.\n", strerror(errno));
            }

            return;

        } else {

            // Yes, our turn.
            shared_data->left--;

            printf("1");
            fflush(stdout);

            // Post on the second semaphore to let the child do the next turn.
            if (sem_post(&(shared_data->sem2)) == -1) {
                fprintf(stderr, "Parent: Failed to post on semaphore 2: %s.\n", strerror(errno));
                return;
            }
        }
    }
}

static void do_child(void)
{
    while (1) {

        // Wait on the second semaphore for our turn.
        if (sem_wait(&(shared_data->sem2)) == -1) {
            fprintf(stderr, "Child: Failed to wait on semaphore 2: %s.\n", strerror(errno));
            return;
        }


        // Check if there are any turns left.
        if (shared_data->left < 1) {
            // No, we need to exit.

            // Post on the first semaphore to make sure the parent also knows they need to exit.
            if (sem_post(&(shared_data->sem1)) == -1) {
                fprintf(stderr, "Child: Failed to post on semaphore 1: %s.\n", strerror(errno));
            }
            return;

        } else {
            // Yes, our turn.
            shared_data->left--;

            printf("2");
            fflush(stdout);

            // Post on the first semaphore to let the parent do the next turn.
            if (sem_post(&(shared_data->sem1)) == -1) {
                fprintf(stderr, "Child: Failed to post on semaphore 1: %s.\n", strerror(errno));
                return;
            }
        }
    }
}

int main(void)
{
    // Obtain anonymous shared memory, large enough for the structure shared_data points to.
    shared_data = mmap(NULL, sizeof *shared_data, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shared_data == MAP_FAILED) {
        fprintf(stderr, "Cannot map anonymous shared memory: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Initialize first semaphore (process-shared) to 1.
    if (sem_init(&(shared_data->sem1), 1, 1) == -1) {
        fprintf(stderr, "Error initializing first semaphore to 1: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Initialize second semaphore (process-shared) to 0.
    if (sem_init(&(shared_data->sem2), 1, 0) == -1) {
        fprintf(stderr, "Error initializing second semaphore to 0: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Do 100 turns, total.
    shared_data->left = 100;

    // Fork the child process.
    pid_t child = fork();
    if (child == -1) {
        fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    } else
    if (child) {
        /* Parent process. */
        do_parent();
    } else {
        /* Child process. */
        do_child();

        /* Child process will exit immediately after its work is done. */
        return EXIT_SUCCESS;
    }

    /* Parent reaps the child process. */
    int   status;
    while (1) {
        pid_t  p = waitpid(child, &status, 0);
        if (p == child) {
            break;
        } else
        if (p != -1) {
            fprintf(stderr, "Cannot reap child process: Invalid waitpid() return value (%d).\n", (int)p);
            return EXIT_FAILURE;
        } else
        if (errno != EINTR) {
            fprintf(stderr, "Cannot reap child process: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    }
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status)) {
            fprintf(stderr, "Warning: Child process exited with status %d.\n", WEXITSTATUS(status));
        } else {
            // WIFEXITED(status) && !WEXITSTATUS(status): Child exited with 0 AKA EXIT_SUCCESS.
        }
    } else
    if (WIFSIGNALED(status)) {
        fprintf(stderr, "Warning: Child process died from signal %d.\n", WTERMSIG(status));
    } else {
        fprintf(stderr, "Warning: Child process vanished without a trace.\n");
    }

    /* Anonymous shared memory maps don't need to be cleaned up; the Linux kernel
       will do that on its own, when the last process that has it mapped exits.
       Similarly, POSIX unnamed semaphores are also auto-cleaned up the same way.
       For illustration, we'll destroy them here anyway. */
    sem_destroy(&(shared_data->sem2));
    sem_destroy(&(shared_data->sem1));
    munmap(shared_data, sizeof *shared_data);
    return EXIT_SUCCESS;
}

In this scheme, every process has its own semaphore, and they wait on it. On each turn, they check the turn counter, and if positive, they decrement the counter and perform one turn. Then, they post on the next semaphore, to let the next process have their turn. While there are currently just two processes –– parent and child –– you could have the parent fork more children, and have a round-robin ring of processes doing their turns.

It is important to post on the next semaphore even when they exit, because we do not know which of the processes handles the last turn. (We could, technically, but it isn't worth the effort.) This does mean that the semaphore corresponding to the process that first notices that all turns have been done, will be left at 1, and all other semaphores at 0.

Similarly, the turn counter must be volatile, so that the compiler won't make any assumptions about its value (as those assumptions would be wrong, because there is a second process modifying the same variable, since it is in shared memory). In this model, it is safe to access the turn counter (shared_data->left) after the process has waited on its semaphore (successfully/without errors), up until the process posts on another semaphore. At any other time, the other process may modify the value, so access is not safe.

(We could use <stdatomic.h>, or built-in __atomic functions provided by the C compiler (gcc, Intel CC, clang) but the above pattern is sufficient. It makes much more sense to know when the shared variables can be safely accessed, than to try to just paper over such details using atomics.)

  • Related