Neil Shah
Neil Shah

Reputation: 33

Processes in C for Linux(Ubuntu)

Here is what I am trying to do:

Write a C program that takes an integer command line argument n, spawns n processes that will each generate a random numbers between -100 and 100, and then computes and prints out the sum of these random numbers. Each process needs to print out the random number it generates.

This is what I have so far:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>


int main(int argc, char *argv[]){
    int command,processCheck; // processCheck: to check if fork was successful or not and to 
    char * strNumProcess = NULL;// check the status of child process
    while((command = getopt(argc, argv, "n:"))!=-1){
        if(command == 'n'){
            strNumProcess = optarg;
            break;
        }
    }

    int numProcess = atoi(strNumProcess);

    int pipes[numProcess][2];


    int randomNum; // Variable to store the random number
    int randomNumSum=0; // Initialized variable to store the sum of random number

    /** A loop that creates specified number of processes**/
    for(int i=0; i<numProcess; i++){
        processCheck = fork(); // creates a child process. Usually fork() = 2^n processes
        if(processCheck < 0){ // Checks for the error in fork()
            printf("Error");
            exit(1); // Terminates with error
        }
        else if(processCheck == 0){
            close(pipes[i][0]);
        /** Child process**/
            srand(time(NULL)+getpid()); // sets the randomness of the number associted with process id
            randomNum = rand()% 201 + (-100); // sets the range of random number from -100 to 100 and stores the random number in randomNum
            printf("%d\n" , randomNum); // Prints out the random number
            write(pipes[i][1], &randomNum, sizeof randomNum);
            close(pipes[i][1]);
            exit(0);// Terminates successfully
        }
        else{
            if(wait(NULL)){ // Waits for the child process to end and directs to parent process
                int v;
                if(read(pipes[i][0], &v, sizeof v)==sizeof(v)){
                    randomNumSum+=v;
                    close(pipes[i][0]);
                }
            }
        }
        close(pipes[i][1]);
    }

    printf("%d\n", randomNumSum); // Prints the sum of the random number

    return 0;
}

The program goes in infinite loop after second process.

Upvotes: 0

Views: 196

Answers (1)

Pablo
Pablo

Reputation: 13570

edit

The OP has made significant changes to the question, it's not the same question as it was yesterday. This answer might henceforth make no sense any more.

end edit

The reason for this is that fork() creates a new independent process with its own virtual memory. It only inherits the values from the parent, the forked process do not share variables with the parents. So randomNumSum is for every child a unique variable and changing it does not affect the randomNumSum of the parent.

You need to use for example pipes for communication between parents and children, the children write the results in the pipe, the parent reads from the children.

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char **argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "usage: %s num_of_children\n", argv[0]);
        return 0;
    }

    int noc = atoi(argv[1]);

    if(noc <= 0)
    {
        fprintf(stderr, "Invalid number of children\n");
        return 1;
    }

    int pipes[noc][2];
    pid_t pids[noc];

    for(size_t i = 0; i < noc; ++i)
    {
        if(pipe(pipes[i]) == -1)
        {
            perror("pipe");
            pids[i] = -2; // used later for error checking
            continue;
        }

        pids[i] = fork();

        if(pids[i] == -1)
        {
            perror("fork");
            continue;
        }

        if(pids[i] == 0)
        {
            // CHILD

            // closing reading end
            close(pipes[i][0]);
            srand(time(NULL)+getpid());
            int r = rand()% 201 + (-100);
            printf("Child %zu: r = %d\n", i, r);

            // sending value to parent
            write(pipes[i][1], &r, sizeof r);
            close(pipes[i][1]);
            return 0;
        }

        // closing writing end
        close(pipes[i][1]);
    }

    int sum = 0;
    for(size_t i = 0; i < noc; ++i)
    {
        if(pids[i] == -2)
        {
            fprintf(stderr, "Pipe could not be created for child %zu\n", i);
            continue;
        }

        if(pids[i] == -1)
        {
            fprintf(stderr, "Child %zu was not started\n", i);
            close(pipes[i][0]);
            continue;
        }

        int status;
        if(waitpid(pids[i], &status, 0) == -1)
        {
            fprintf(stderr, "Could not wait for child %zu\n", i);
            close(pipes[i][0]);
            continue;
        }

        if(WIFEXITED(status) && WEXITSTATUS(status) == 0)
        {
            int v;
            if(read(pipes[i][0], &v, sizeof v) != sizeof(v))
            {
                fprintf(stderr, "Could not read from child %zu\n", i);
                close(pipes[i][0]);
                continue;
            }

            sum += v;
            close(pipes[i][0]);
        } else
            printf("Child %zu did not exit normally\n", i);
    }

    printf("The sum is: %d\n", sum);

    return 0;
}

Gives me the output:

Child 0: r = -6
Child 1: r = 63
Child 3: r = 78
Child 2: r = 77
Child 4: r = -47
The sum is: 165

So the technique here is the creation of the pipes with the pipe. A pipe is a unidirectional data channel that can be used for interprocess communicationcite. With a pipe two processes can communicate with each other, but the pipe has only one direction. In this example the child process will write into the pipe and the parent will read from the pipe.

That's why before doing the fork, the parent creates the pipe, does the fork and then closes the it's writing end of the pipe. The child closes it's reading end of the pipe. Then the child calculates the value and writes into the pipe the value it calculated and exists with the status 0.

After creating the children the parent waits for the children to terminate. If the children terminate normally and with exit status 0, the parent reads from the pipe and gets the calculated value of the child.

Btw, as David C. Rankin points out in the comments, your method of getting a random value in the range [-100, 100] is incorrect. rand()% 201 + (-100) would give you values between -100 and 100, because rand()%201 gives you a value between 0 and 200.


edit2

OP asked in the comments

based on my understanding can I just return randonNum instead of exit(0) and do the computation where I calling wait(NULL) and call wait(randomNum)?

Yes, you can use the exit status of a process to send information back to the parent without the need of creating a pipe. But I think this is not a particular good solution for these reasons:

  • the exit status in Unix/POSIX is a unsigned 8-bit value, meaning the exit codes are in the range [0, 255]. So if your random value is let's say -1, the parent process will see 255. In your case that wouldn't be such a problem, because you for values greater than 127, you can subtract 256 to get the negative value.

  • You can only return an (unsigned) 8-bit value. If your child process has to send something more "complex" like a 16-bit value, a float, double, or a struct, you cannot use the exit status, so you are limiting what you can return to the parent. When you want to return something more "complex" than a 8-bit value, then a pipe is perfect tool for that.

  • I consider it as a hack to use the exit status to send other information that is not an error value. The purpose of the exit status is that a process can tell it's parent that it exited without an error by returning 0, or that it exited with an error and the exit status has the error code. That's why I consider it a hack, for me it's like using a screwdriver instead of a hammer for nailing nails.

  • Your wait call would be invalid though, because wait expects a pointer to int and you would need to use the macros WIFEXITED and WEXITSTATUS to get the exit status. But the problem of using wait in this case is that wait returns -1 on error and you wouldn't be able to tell for which child it returned -1 and how many waits you have to call to wait for the rest of the children. The children don't end in the same order as you forked them, so you would need to keep track which child has been wait()ed. It's much more simpler to use waitpid. With waitpid you can wait for a particular child. I personally prefer waitpid here.

So, changing the code to do the same without pipes and using the exit status:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char **argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "usage: %s num_of_children\n", argv[0]);
        return 0;
    }

    int noc = atoi(argv[1]);

    if(noc <= 0)
    {
        fprintf(stderr, "Invalid number of children\n");
        return 1;
    }

    pid_t pids[noc];

    for(size_t i = 0; i < noc; ++i)
    {

        pids[i] = fork();

        if(pids[i] == -1)
        {
            perror("fork");
            continue;
        }

        if(pids[i] == 0)
        {
            // CHILD

            srand(time(NULL)+getpid());
            int r = rand()% 201 + (-100);
            printf("Child %zu: r = %d\n", i, r);

            exit(r);
        }
    }

    int sum = 0;
    for(size_t i = 0; i < noc; ++i)
    {
        if(pids[i] == -1)
        {
            fprintf(stderr, "Child %zu was not started\n", i);
            continue;
        }

        int status;
        if(waitpid(pids[i], &status, 0) == -1)
        {
            fprintf(stderr, "Could not wait for child %zu\n", i);
            continue;
        }

        if(WIFEXITED(status))
        {
            int v = WEXITSTATUS(status);

            // checking if the child wrote a 8-bit negative value
            // in 2-complement format
            if(v > 127)
                v -= 256;

            printf("Parent: child %zu returned %d\n", i, v);
            sum += v;
        } else
            fprintf(stderr, "Child %zu did exit abnormally, ignoring\n", i);
    }

    printf("The sum is: %d\n", sum);

    return 0;
}

Gives me the output for 10 children:

Child 0: r = -59
Child 1: r = 73
Child 2: r = 61
Child 3: r = 98
Child 4: r = 18
Child 6: r = 31
Child 5: r = -88
Parent: child 0 returned -59
Parent: child 1 returned 73
Parent: child 2 returned 61
Child 8: r = 58
Parent: child 3 returned 98
Parent: child 4 returned 18
Parent: child 5 returned -88
Child 7: r = 53
Parent: child 6 returned 31
Child 9: r = -43
Parent: child 7 returned 53
Parent: child 8 returned 58
Parent: child 9 returned -43
The sum is: 202

Upvotes: 3

Related Questions