Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using execl with multiple child processes

I'm trying to create a process that creates several child processes, each calling the file() function.

Here is what I have so far:

  • The parent writes a list of files into a pipe
  • The child processes redirect the pipe to stdin and redirect stdout to another pipe and each exec file.
  • The parent waits for the termination of the child processes using select and reads from the relevant pipe (containing the output of the file function).

When I use only one child process, everything is working great: The child terminates with all output in the pipe, and the parent reads it. However, when I use 2 child processes, they don't terminate; The file function gets into "sleeping" mode, waiting for more input. The parent is then also blocked, waiting for the children to terminate.

I've created a minimal contained example of the two pipe version:

#define NUM_CHILDREN (2)
//pipes from parent to children
int pipeP2C[NUM_CHILDREN][2];
//pipes from children to parent
int pipeC2P[NUM_CHILDREN][2];

int children_ids[NUM_CHILDREN];

int main()
{
//create pipes from parent to children and vice versa
for (int i = 0; i < NUM_CHILDREN; ++i){
    pipe(pipeP2C[i]);
    pipe(pipeC2P[i]);
}

//create first child
//Create initial g_parLevel child processes
pid_t pid;
int numForks = 0;
do
{
    pid = fork();
    // Parent process should count how many children it has
    if (pid > 0) {
        children_ids[numForks] = pid;
        ++numForks;
    }
    //child process also writes its' own pid to children_ids.
    else {
        children_ids[numForks] = (int)getpid();
    }
}
//Call fork again from parent process, if it doesn't have g_parLevel children already.
while (pid > 0 && numForks < NUM_CHILDREN);

//Now we have NUM_CHILDREN child processes, their ids are kept in the parent process, and each
//of them has (at least) it's own id kept in the suitable index in children_ids.

//parent - write to the children
if (pid > 0){
    //Iterate over all children
    for (int i = 0; i < NUM_CHILDREN; ++i)
    {
        std::string str = "/bin/ls";
        //close reading end
        close(pipeP2C[i][0]);
        //write to child
        write(pipeP2C[i][1], str.c_str(), (int)str.length());
        close(pipeP2C[i][1]);
    }

    //wait for the children to terminate
    int terminatedChildren = 0;
    while (terminatedChildren < NUM_CHILDREN)
    {
        int status;
        int terminatedChild = wait(&status);
        ++terminatedChildren;

        //read from the terminated child
        int childIndex = children_ids[0] == terminatedChild ? 0 : 1;

        //close writing end
        close(pipeC2P[childIndex][1]);
        char buf[2048];
        read(pipeC2P[childIndex][0], buf, sizeof(buf));
        close(pipeC2P[childIndex][0]);
        std::cerr<<"output from child "<<childIndex<<" is:\n"<<buf<<std::endl;
    }
}

//child process
if (pid == 0)
{
    //find the pipe to read from.
    int childPid = getpid();
    int childIndex = children_ids[0] == childPid ? 0 : 1;
    std::cerr<<"in child "<<childPid<<" index "<<childIndex<<std::endl;
    //wait until the parent has written data
    fd_set rfds;
    int ready;
    while(true)
    {
        FD_ZERO(&rfds);
        //we are interested in the reading end
        FD_SET(pipeP2C[childIndex][0], &rfds);
        ready = select(pipeP2C[childIndex][0] + 1, &rfds, NULL, NULL, NULL);
        if (ready > 0){
            std::cerr<<"ready"<<std::endl;

            //close the relevant writing end of the pipe from parent to child
            close(pipeP2C[childIndex][1]);
            //redirect input to stdin
            dup2(pipeP2C[childIndex][0], 0);
            close(pipeP2C[childIndex][0]);

            //close relevant reading end of the pipe from child to parent
            close(pipeC2P[childIndex][0]);
            //redirect output from stdout
            dup2(pipeC2P[childIndex][1], 1);
            close(pipeC2P[childIndex][1]);

            execl("/usr/bin/file","file", "-n", "-f", "-", (char *)NULL);

            //should never get here
            std::cerr<<"file failed"<<std::endl;
            exit(1);
        }
    }
}
}

Why doesn't it wait

like image 474
shakeAndBake Avatar asked Nov 10 '22 11:11

shakeAndBake


1 Answers

You're not closing file descriptors from other processes. When you fork your children still have references to the file descriptors from the other children.

Close them before you do processing. Something like:

for(int k=0;k<NUM_CHILDREN;k++){
    if(k!=childIndex){
        close(pipeP2C[k][0]);
        close(pipeP2C[k][1]);
        close(pipeC2P[k][0]);
        close(pipeC2P[k][1]);
    }
}

Above the while(true) bit in the child section should do the trick. So:

//child process
if (pid == 0)
{
    //find the pipe to read from.
    int childPid = getpid();
    int childIndex = children_ids[0] == childPid ? 0 : 1;
    std::cerr<<"in child "<<childPid<<" index "<<childIndex<<std::endl;
    //wait until the parent has written data
    fd_set rfds;
    int ready;
    for(int k=0;k<NUM_CHILDREN;k++){
        if(k!=childIndex){
            close(pipeP2C[k][0]);
            close(pipeP2C[k][1]);
            close(pipeC2P[k][0]);
            close(pipeC2P[k][1]);
        }
    }
    while(true)
    {
        FD_ZERO(&rfds);
        //we are interested in the reading end
        FD_SET(pipeP2C[childIndex][0], &rfds);
        ready = select(pipeP2C[childIndex][0] + 1, &rfds, NULL, NULL, NULL);
        if (ready > 0){
            std::cerr<<"ready"<<std::endl;

            //close the relevant writing end of the pipe from parent to child
            close(pipeP2C[childIndex][1]);
            //redirect input to stdin
            dup2(pipeP2C[childIndex][0], 0);
            close(pipeP2C[childIndex][0]);

            //close relevant reading end of the pipe from child to parent
            close(pipeC2P[childIndex][0]);
            //redirect output from stdout
            dup2(pipeC2P[childIndex][1], 1);
            close(pipeC2P[childIndex][1]);

            execl("/usr/bin/file","file", "-n", "-f", "-", (char *)NULL);

            //should never get here
            std::cerr<<"file failed"<<std::endl;
            exit(1);
        }
    }
}
like image 156
Benjamin Gruenbaum Avatar answered Nov 14 '22 22:11

Benjamin Gruenbaum