I'm trying to create a process that creates several child processes, each calling the file()
function.
Here is what I have so far:
exec
file
. 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
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With