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

  • Thread starter freshcoast
  • Start date
In summary: Error creating child processes\n"); _exit(1); } for(i = 0; i < NUMBEROFCHILD; i++){ child[pid] = wallClock(); } } return 0;In summary,The author is having trouble working with child processes. He is to
  • #1
freshcoast
185
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
  • #2
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;
   }
}
 
  • #3
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;
   }
 
  • #4
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.
 
  • #5
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.
 
  • #6
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?
 
  • #7
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:
  • #8
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.
 
  • #9
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.
 

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

What is a child process?

A child process is a process that is created by another process, known as the parent process. Child processes are typically used to perform tasks in parallel with the parent process, allowing for efficient use of system resources.

Why would I need help with child processes?

Child processes can be complex to manage and can cause issues if not handled properly. Therefore, it is common for individuals working with child processes to need help with understanding how to create, manage, and communicate with child processes.

How do I create a child process?

Creating a child process depends on the operating system and programming language being used. In general, child processes can be created by calling the appropriate system functions or using libraries that handle child process creation. It is important to properly handle any errors that may occur during the creation process.

What are some common mistakes when working with child processes?

Some common mistakes when working with child processes include not properly handling errors, not properly communicating with the child process, and not managing resources efficiently. It is important to carefully plan and test the use of child processes to avoid these mistakes and ensure smooth execution.

How can I troubleshoot issues with child processes?

If you are experiencing issues with child processes, it is important to first check for any errors or exceptions that may be occurring. Debugging tools and techniques can also be helpful in identifying and resolving issues. It may also be beneficial to seek assistance from experienced individuals or documentation specific to the operating system and programming language being used.

Back
Top