Search code examples
csignalssignal-handling

Using Signal Handlers to Pause/Resume a Child Process


I'm currently trying to experiment with signals in C by using them to control a child process created with the fork() method. Essentially, I have a child process running the "yes" command from the linux terminal (this command just prints "y" and a newline until it is terminated). I want to be able to pause/resume this process with CTRL-Z. This is what i've got right now:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
pid_t CHILD_PROCESS;
pid_t PARENT_PROCESS;
int isPaused;
void pause_handler(int signo){
  if(!isPaused){  
    printf("Ctrl-Z pressed. Pausing child.\n");
    isPaused = 1;
    kill(CHILD_PROCESS,SIGSTOP);
  }
  else if(isPaused){
   printf("\nCtrl-Z pressed. Resuming child.\n");
   kill(CHILD_PROCESS,SIGCONT);
   isPaused = 0;
  }
}

int main(int argc, char** argv){
  pid_t pid;
  PARENT_PROCESS = getpid();
  pid = fork();
  if(pid == 0){
    system("yes");
  }
  isPaused = 0;
  if(pid > 0){
    signal(SIGTSTP, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    CHILD_PROCESS = pid;
    while(1){
      if(signal(SIGTSTP,pause_handler) == SIG_ERR){
        printf("Signal Failure");
      }
    }
  }
}

When I run this, I can get "Ctrl-Z pressed. Pausing child." to print to console by pressing CTRL-Z, and I can get "Ctrl-Z pressed. Resuming child." to print to the console by pressing CTRL-Z again. However, it doesn't actually resume printing "y" over and over again. Any ideas as to why the child process isn't resuming?


Solution

  • As it turns out, system has an implicit fork call within it, so the PID that gets stored in CHILD_PROCESS ends up not actually being the child process, and instead an intermediate one.

    From man 3 system:

       The  system()  library  function uses fork(2) to create a child process
       that executes the shell command specified in command using execl(3)  as
       follows:
    
           execl("/bin/sh", "sh", "-c", command, (char *) 0);
    
       system() returns after the command has been completed.
    

    So, if we replace the system("yes") call with execl("/bin/sh", "sh", "-c", "yes", NULL), then we avoid this extra fork and the program functions as desired.


    The only other issue is that, by I comment I found on this post, using printf within a signal handler is undefined behavior. Not an issue to worry about here, but something to keep in mind for future code!