What could be causing my child processes to not exit properly?

  • Thread starter Thread starter freshcoast
  • Start date Start date
Click For Summary

Discussion Overview

The discussion revolves around issues related to child processes in a C program, specifically focusing on the proper termination of these processes and the use of pipes for inter-process communication. Participants explore the implementation of a parent process that creates two child processes, one functioning as a wall clock and the other as a countdown timer, and the challenges faced when the child processes do not exit as expected.

Discussion Character

  • Technical explanation
  • Debate/contested

Main Points Raised

  • One participant describes their implementation of a parent process that creates two child processes and expresses frustration over the child processes not exiting properly, which leads to the parent being stuck in a loop.
  • Another participant points out a potential issue with the switch statement in the child process creation logic, suggesting that without break statements, both child functions could be called sequentially.
  • A different participant mentions a solution they found online, indicating that the problem may have been related to how the parent was waiting for the child processes to finish, suggesting that the child processes might have exited before the parent could wait on them.
  • One participant corrects a misunderstanding about the behavior of the wait function, clarifying that it does not return zero when there are no unwaited-for child processes, but instead sets errno to ECHILD and returns -1.
  • Another participant discusses the implications of using exit versus _exit in the context of forked processes, emphasizing the importance of using _exit to avoid flushing standard output unnecessarily.

Areas of Agreement / Disagreement

Participants express differing views on the cause of the issue with child processes not exiting properly, with some suggesting code logic errors while others propose that the timing of process termination and waiting may be the root cause. The discussion remains unresolved regarding the exact nature of the problem.

Contextual Notes

There are unresolved issues regarding the handling of process termination and the use of pipes, as well as the implications of using exit versus _exit in child processes. The discussion highlights the complexity of inter-process communication and process management in C programming.

freshcoast
Messages
185
Reaction score
1
Hey everybody, I am having trouble with working with child processes. I am to implement a parent process creating 2 child processes to each run different code. e.g child 1 is a wallclock and child 2 is a countdown program. I also have to implement a pipe for when child 2, the countdown program, upon reaching 0 it will use the pipe to tell the other child process to terminate.

So far I seem to have written everything correct but for some reason, my child processes are not exiting like they are supposed to and it is causing my parent process to be in a forever loop waiting until the kids end. I swear before a recent modification the code was working fine, until I added another child process then I started having this trouble. I tried back tracking as best as I can to where I remember it was working correctly, but for some reason I am still getting this error. So now I am asking if someone can take a look at my code and see where the problem lies because I for one have been looking at my source code for hours but can not notice anything that would make it behave this way.

Don't mind the runFile() function as I will be implementing that in the future.

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

#define DEFAULT 3
#define pipeSize 2
#define NUMBEROFCHILD 2
#define STOP 1
#define RUN 0

int fd[pipeSize];

void wallClock(){
   int status = RUN;        /*signals*/
   int newStatus = RUN;
   time_t now;
   struct tm *lcltime;
  
   if(close(fd[1]) == -1){
      printf("Error closing writing end of pipe\n");
      _exit(1);
   }
  
   while(status != STOP){                                /*loop until signal occurs*/
      now = time(NULL);
      lcltime = localtime(&now);
      printf("the time is %d:%d:%d\n", lcltime->tm_hour,lcltime->tm_min,lcltime->tm_sec);
      sleep(1);
      read(fd[0], &newStatus, sizeof(newStatus));                    /*check for status*/
      status = newStatus;
   }
  
   if(close(fd[0]) == -1){
      printf("Error closing reading end of pipe\n");
      _exit(1);
   }
   printf("Message has been received--Terminating now\n");
   exit(1);
}

void runFile(){
   int status = RUN;
   int newStatus = RUN;
  
   if(close(fd[1]) == -1){
      printf("Error closing writing end of pipe\n");
      _exit(1);
   }
  
   while(status != STOP){
                   /*run file in here*/
   }
}

void countDown(int start){
   int stopNow = STOP;         /*signals*/
   int cont = RUN;
  
   if(close(fd[0]) == -1){
      printf("Error closing reading end of pipe\n");
      _exit(1);
   }
  
   while(start > 0){                /*while loop until start reaches 0*/
      if(start >= 10){
      printf("Count Down: 00:%2d\n", start);
      }
      else{
      printf("Count Down: 00:0%d\n", start);
      }
      write(fd[1], &cont, sizeof(cont));    /*write in pipe to continue still*/
      start--;
      sleep(1);
   }
  
   printf("Count Down: 00:00 -- sending message to terminate other processess\n");
   write(fd[1], &stopNow, sizeof(stopNow));     /*Tell others to terminate*/
  
   if(close(fd[1]) == -1){
      printf("Error closing writing end of pipe\n");
      _exit(1);
   }
  
   exit(1);
}

int main(int argc, char *argv[]){
  
   int time , i, pid = 0;
   pid_t child[NUMBEROFCHILD];
  
   if(argc <= 2){            /*check arguments*/
      if(argv[1] != '\0'){
         time = atoi(argv[1]);
      }
      else
         time = DEFAULT;
   }
   else{
      printf("Too many parameters -- exiting program\n");
      return -1;
   }
  
   if(pipe(fd) == -1){            /*open pipes*/
      printf("Error opening pipe\n");
      exit(1);
   }
  
   for(i = 0; i < NUMBEROFCHILD; i++){       /* create children */   
      if((child[i] = fork())  == -1){
         printf("Error in creating child\n");
     exit(1);
      }
      if(child[i] == 0){
         switch(i){
        case 0:   wallClock();
        case 1:   countDown(time);
     }
      }
   }
  
   if(close(fd[0]) == -1){           
      printf("Error closing parent reading pipe\n");
      exit(1);
   }
  
   if(close(fd[1]) == -1){           
      printf("Error closing parent writing pipe\n");
      exit(1);
   }
  
   while(pid = wait(NULL)){            /*Wait for all children to finish*/
      if(pid == -1){
         printf("Error exiting child\n");
     exit(EXIT_FAILURE);
      }
   }
  
   printf("All processes has ended, exiting program --\n");
   return 0;
  
}
I have been racking my brain and am frustrated because I feel that my logic is correct and it literally worked hours ago but I must've changed something and now I can't find what is causing it to behave this way.
So thanks for any/all input or advice
 
Technology news on Phys.org
freshcoast said:
C:
if(child[i] == 0){
  switch(i){
  case 0:  wallClock();
  case 1:  countDown(time);
  }
  }
Above is something I noticed that may or may not be the problem. If i == 0, you call wallClock(), and then it "crashes" through and also calls countDownTime(time).

If you're new to the switch statement in C you might not be aware of this behavior of switch.

If that's not what you intended, here's a fix for it.
C:
if(child[i] == 0)
{
   switch(i)
   {
      case 0:  wallClock();
      break;
      case 1:  countDown(time);
      break;
   }
}
 
Thanks for your reply, but it did not solve the solution. However, I did find a solution on google but I am not understanding why it is working all of a sudden, maybe you can explain. So I think the problem was with how I was waiting for the children, I guess it seemed like the child processes were exiting before the parent can wait on it? anyways I added this snippet of code and now everything is working fine.

Code:
int status;
pid_t pid;
n = NUMBEROFCHILD;
   while(n > 0){            /*Wait for all children to finish*/
   pid = wait(&status);
      --n;
   }
 
freshcoast said:
I have been racking my brain and am frustrated because I feel that my logic is correct and it literally worked hours ago but I must've changed something and now I can't find what is causing it to behave this way.
Your logic was incorrect. Your loop is written as if wait returns zero when there are no unwaited-for child processes. That's not what wait does. If there are no unwaited-for child processes, wait sets errno to ECHILD and returns -1.

Your program waited for child processes three times. On the first two calls to wait resulted in your parent process being suspended until one of the child processes exited, at which point wait cleaned things up and returned the process ID of the child processes that had been reaped. On the third call to wait, your parent process no longer had any unwaited-for child processes, so wait returned -1.
 
Mark44 said:
Above is something I noticed that may or may not be the problem. If i == 0, you call wallClock(), and then it "crashes" through and also calls countDownTime(time).
The functions wallClock() and countDown() never return. They instead exit.

@freshcoat: In most cases, don't repeat yourself (DRY) is a good rule to follow. But when your code does something unexpected, it's best to repeat oneself a bit. To avoid confusing others reading your code (e.g., Mark44), it's best to say that the function doesn't return in a comment at the point where it is called and in the commentary that describes the function.

I noticed that your functions wallClock and countDown call _exit(1) on error conditions, exit(1) on success. You should be calling _exit(0) instead of exit(1) to indicate success in those functions. The reason for calling _exit rather than exit is because you are in a forked process where you haven't called exec. A call to exit will flush standard output; you might get a double flush (and confused output). The reason for using _exit(0) instead of _exit(1) at the end is to indicate success.

Finally, you're calling exit(1) in a few places in main, exit(EXIT_FAILURE) in another. On most Unix systems, EXIT_FAILURE is #defined to be 1.
 
I have another question, and since it is related to this program I'll just ask it here.

My problem now is with implementing the 3rd child to run an execl command continuously until it reads the signal to terminate from the pipe. From what I understand is when it runs an execl command, it runs the file and then quits the process immediately, so I am assuming in order to keep it running I need some type of handler in the parent process to catch that process and somehow re-run it again. I was thinking of using fork() from the same child id but I don't think that works since it creates a new process with a new pid and such. Is there another command I don't know about to re-run the same child process or is there another way?
 
freshcoast said:
My problem now is with implementing the 3rd child to run an execl command continuously until it reads the signal to terminate from the pipe. From what I understand is when it runs an execl command, it runs the file and then quits the process immediately, so I am assuming in order to keep it running I need some type of handler in the parent process to catch that process and somehow re-run it again. I was thinking of using fork() from the same child id but I don't think that works since it creates a new process with a new pid and such. Is there another command I don't know about to re-run the same child process or is there another way?
This is starting to sound a lot like homework. We have a homework help section, https://www.physicsforums.com/forums/engineering-and-computer-science-homework.158, where you should post homework-like computer science questions.Regarding your comment "From what I understand is when it runs an execl command, it runs the file and then quits the process immediately ...": This is incorrect. Consider the following:

C:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main ()
{
   printf ("In parent: PID=%d\n", getpid());
   int pid = fork();
   switch (pid)
   {
   case -1: // Oh oh.
      fprintf (stderr, "Fork failure\n");
      exit(1);

   case 0: // Child
      sleep (2);
      printf ("In child:  PID=%d\n", getpid());
      sleep(2);
      // Execute the shell command
      // sh -c 'echo "In shell:  PID="$$; sleep 2; echo "In shell:  Exiting"'
      execl ("/bin/sh",
             "/bin/sh",
             "-c",
             "echo 'In shell:  PID='$$;"
             "sleep 2;"
             "echo 'In shell:  Exiting shell'",
             0);
      fprintf (stderr, "Exec failure\n");  // This should never happen
      _exit(1);

   default: // Parent
      printf ("In parent: Forked PID %d\n", pid);
      break;
   }

   // Also parent
   int waited_pid = wait(0);
   printf ("In parent: Reaped PID %d\n", waited_pid);
   sleep(1);
   printf ("In parent: Exiting parent\n");

   return 0;
}

If you compile and run the above, you'll see something like
Code:
In parent: PID=7987
In parent: Forked PID 7988
In child:  PID=7988
In shell:  PID=7988
In shell:  Exiting shell
In parent: Reaped PID 7988
In parent: Exiting parent
Note how the "In child: PID=xxxx" and "In shell: PID=xxxx" show that the child (before the execl call) and the shell (after the exec call) have the exact same process ID.

In unix, when a process calls a member of the execl family, the process doesn't start the execution of another executable and exit. It instead becomes that other executable. Key resources (e.g., file descriptors) owned by the original process become resources of the execed process.

In particular, this means that when you execl a program that invokes your wallClock, the file descriptors created in the parent process remain usable after a fork and exec.

There's a problem, however: How is the forked/execed process to know which file descriptors to use? If you know with a certainty that the read end of the pipeline is file descriptor 3, you can read from file descriptor 3 because that fd is still open and readable in the forked/execed process. But maybe it's fd 42. Who knows?

There is one way to know, which is to redirect standard input to the read end of the pipeline before calling execl. This is what let's you do things such as find . - type f | xargs grep -l fork. You need to use dup or dup2. This is where your manly ability to read the man pages comes to a test.
 
Last edited by a moderator:
For crying out loud. Sometimes I am not thrilled with our migration to XenForo. The above post is exemplary. I can't fix the silly font problems.
 
D H said:
For crying out loud. Sometimes I am not thrilled with our migration to XenForo. The above post is exemplary. I can't fix the silly font problems.
I fixed it. You closed off one of your font tags with a /function tag instead of a /font tag.
 

Similar threads

Replies
6
Views
2K
  • · Replies 6 ·
Replies
6
Views
3K
  • · Replies 4 ·
Replies
4
Views
2K
  • · Replies 5 ·
Replies
5
Views
2K
Replies
1
Views
4K
  • · Replies 7 ·
Replies
7
Views
5K
  • · Replies 9 ·
Replies
9
Views
3K
  • · Replies 22 ·
Replies
22
Views
6K
  • · Replies 2 ·
Replies
2
Views
3K
Replies
2
Views
2K