What happens during this signal handling program?

2019-02-27 21:43发布

问题:

void main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0) 
    {   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received");
    exit (0);
}

I think that the program above asks the system to launch the f function ( which displays "signal received" ) when the SIGUSR1 signal is received by the parent process. but I'm not sure about that, please feel free to correct or to give more details. Thank for the help !

回答1:

There are some mistakes in your code:

  1. Avoid calling printf( ) function in signal handler. SIGNAL(7) manual provides a list of authorized functions calling them is safe inside signal-handlers. Read:

    Async-signal-safe functions

    A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of "safe function". If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined.

  2. Use return type of main() int; read "What should main() return in C?"

  3. x should be pid_t. (Process Identification).

Now lets suppose your program compile and run (not interrupted by any other signal while handler executing): I am just indenting your code and shifting f() function definition before main because function declaration is missing, also adding some comments that you should read:

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void f( )
{
    printf ("signal received\n");
    exit (0);//if child receives signal then child exits from here
}            // ******* mostly happens
int main ( )
{   int x;
    signal (SIGUSR1, f);// First: handler registered
    x = fork ( );       // Second: Child created, now child will execute in
    if (x == -1)        // parallel with parent 
      exit (1);
    if (x != 0) {// parent           
        kill(x, SIGUSR1) ; // Third: sends signal to child
        sleep(2);          // Forth: parent sleep
    }
    return 0; // parent always exits  
    // Child can exits from here **** 
    // if signal sent from parent delayed
}

In main() function, you registers f() function for SIGUSR1 signal and after that calls fork() to create a new process. In runtime as fork() function returns a child-process starts executing in parallel with parent process.
As I can see your code, I think that you understands that child-process is copy of parent-process except values of variables can be different from the point fork() returns and hence x is different in child and parent process. We can use the return value from fork to tell whether the program is running in the parent-process or in child. But note that it is not parent but actually the child-process that receives signal SIGUSR1. Value of self process id is always 0 for any process. You checks the return value x = fork() that is pid of newly created child-process, in child-process value of x is 0 and in parent x != 0. Hence signal is sent from parent process to child process.

Your comments:

I think that the program above asks the system to launch the f( ) function ( which displays "signal received") when the SIGUSR1 signal is received by the parent process.

I have impression that you don't consider that both processes execute concurrently and "it can be happen that soon after fork() create a child-process, child-process start executing and immediately terminate before parent-process can send a signal to child (or child-process can receive the signal)". In that case, function f() will never get a chance to execute and printf in signal handler never prints.

But the possibility of what I have described just above is very low because fork takes time to create a new process. And even if you execute the code again and again most of the times signal sent from the parent process will execute signal-handler.

What is correct way of writing this code?

Code is x.c: Correct way is set a flag that indicates that signal handler executed and then call printf function on the bases of flag value outside signal-handler as I have described in my answer: How to avoid using printf in a signal handler? And reason behind it explained by Jonathan Leffler in his answer.

#define _POSIX_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
volatile sig_atomic_t flag = 0; //initially flag == 0
void f(){
    flag = 1; // flag is one if handler executes
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  x = fork();
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    sleep(1);
  }
  if(flag)//print only if signal caught and flag == 1
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:");
  return EXIT_SUCCESS;
}

Now compile it and execute:

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
@:~$ 

Notice child-process prints because parent sends signal to child(but parent process doesn't prints as no signal catch in parent). So behavior of above code still similar as you was getting in your code. Below I have added one more example in which I am trying to demonstrate that 'concurrent execution of processes results different at different instance of execution'(read comments).

// include header files...
volatile sig_atomic_t flag = 0;
void f(){
    flag = 1;
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  (void) signal (SIGUSR2, f); // added one more signal
  x= fork ( );
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    while(!flag); // in loop until flag == 0
  }
  if (x == 0){//child 
    kill(getppid(), SIGUSR2); // send signal to parent 
    while(!flag); // in loop until flag == 0
  }//  ^^^^ loop terminates just after signal-handler sets `flag` 
  if(flag)
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:"); 
  return EXIT_SUCCESS;
}

In above code, two signals are registered in both parent and child process. Parent process doesn't sleeps but busy in a while loop until a signal sets flag. Similarly child-process has a loop that breaks as flag becomes 1 in signal-handler. Now compile this code and run repeatedly. I frequently tried an got following output in my system.

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received  
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 
./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 

@:~$ 

Notice output, one case is: "till child process created parent sent signal and enter in while-loop and when child-process get chance to execute(depends on CPU scheduling) it send back a signal to parents and before parent process get chance to execute child receives signal and prints message". But it also happens sometimes that before child printf print; parent receives and print message (that is I marked using arrow).

In last example I am trying to show child-process executes in parallel with parent- process and output can be differs if you don't applies concurrency control mechanism.

Some good resource To learn signals (1) The GNU C Library: Signal Handling (2) CERT C Coding Standard 11. Signals (SIG).



回答2:

One problem is that the child process doesn't do anything, but will return immediately from the main function, maybe before the parent process can send the signal.

You might want to call e.g. pause in the child.



回答3:

For the sake of the exercise, here is a corrected version of the original code which will compile and run.

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <bits/signum.h>
void f ( );

int main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0)
{   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received\n");
    exit (0);
}

This does exactly what the original question suggested the program should do. Try it out and see what happens if you don't believe me.

BTW: I'm not very experienced at C. There are a number of comments asserting that using printf() in the child process is unsafe. Why?? The child process is a duplicate of the parent including virtual address space. So why is printf() unsafe?



标签: c linux signals