3

I want to create a simple program that uses fork and creates a child process which with the use of pause is waiting. I want this child process to start after it gets a specific signal from father process. Code I've written:

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

int main() {
    pid_t c = fork();
    if (c == 0) {
        pause();
        printf("signal was given");
    }
    if (c > 0)
        kill(c, SIGINT);

    return 0;
}

I think kill gives a specific signal to a process with pid c(child) and I thought that pause just waits for a signal to unpause that process. However in this case running this program has no results. I have also tried adding a signal catching function to the child using signal(SIGINT, handler) and creating a handler function that prints the desired result but it is still not working. Any ideas?

r3mus n0x
  • 5,954
  • 1
  • 13
  • 34

2 Answers2

3

If you send SIGINT, whose default disposition is to kill the process, to a process that neither blocks it nor handles it, the process will die.

If you want the signal to interrupt blocking calls like pause(), it needs to have a handler.

But simply installing a handler introduces race conditions:

if (c == 0 ){
    //< if the signal arrives here the child dies
    signal(SIGINT, handler);
    //< if the signal arrives here then nothing happens except the handler is run
    pause(); //< if the handler arrives here then pause gets interrupted
    printf("signal was given\n");
    exit(0);
}

To eliminate the race conditions, you need to

  1. block the signal in the parent so that the child starts with the signal blocked
  2. install the handler in the child
  3. unblock the signal and pause() in one atomic step

To achieve 3. in one step, you need sigsuspend() instead of pause().

#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>

void handler(int Sig){}

int main()
{
    sigset_t sigint, oldmask; sigemptyset(&sigint); sigaddset(&sigint, SIGINT);
    sigprocmask(SIG_BLOCK, &sigint, &oldmask);

    pid_t c=fork();
    if(0>c) return perror(0),1;
    if (c==0){
        signal(SIGINT, handler);
        sigdelset(&oldmask,SIGINT); /*in (the unlikely) case the process started with SIGINT blocked*/
        sigsuspend(&oldmask);
        printf("signal was given\n");
        exit(0);
    }
    kill(c,SIGINT);
    wait(0);
    return 0; 
}

Alternatively, you can use sigwait() and drop the need for a handler altogether:

#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>

int main()
{
    sigset_t sigint, oldmask; sigemptyset(&sigint); sigaddset(&sigint, SIGINT);
    sigprocmask(SIG_BLOCK, &sigint, &oldmask);

    pid_t c=fork();
    if(0>c) return perror(0),1;
    if (c==0){
        int sig; sigwait(&sigint,&sig);
        printf("signal was given\n");
        exit(0);
    }
    kill(c,SIGINT);
    wait(0);
    return 0; 
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • In this case, you can just install the signal handler before calling `fork()` to eliminate the race condition. – Andrew Henle Mar 15 '19 at 21:29
  • @AndrewHenle That, by itself, will not eliminate the race condition where the signal might arrive before `pause()` is entered, in which case the child now ends up blocked indefinitely. – Petr Skocik Mar 15 '19 at 21:33
  • Well, yeah, because there are two race conditions in that code - getting a signal handler in place, and the timing of the `pause()`. Put a semaphore in the signal handler, call `sem_post()` in the handler, and `sem_wait()` instead of `pause()`. And down into the parent/child/signals rabbit hole we go... ;-) – Andrew Henle Mar 15 '19 at 21:37
  • 1
    Thank you for your explanation! –  Mar 15 '19 at 21:38
0

You have two issues:

  1. The child process is getting a signal before it calls pause().
  2. SIGINT by default would kill a process so printf will never be executed.

Try this:

void handler(int signum)
{
    //nothing here
}

int main()
{
    pid_t c = fork();
    if (c == 0) {
        signal(SIGINT, handler);
        pause();
        printf("signal was given");
    }
    if (c > 0) {
        sleep(1); // <-- give the child process some time to pause()
        kill(c, SIGINT);
    }

    return 0;
}
r3mus n0x
  • 5,954
  • 1
  • 13
  • 34
  • Thank you very much for your solution sir.Why is the signal given before pause?Could i do this somehow without using the sleep call? –  Mar 15 '19 at 21:23
  • 2
    @Labyrinthian, in the multitasking OS processes are executed concurrently (in parallel or seemingly in parallel) so there is no way to know which "part" of which process will be executed first. In this case it seems that most times the signal is delivered before the child executes `pause()` but there's really no guarantee. As for a better solution than `sleep()`, I believe that PSkocik has already given you two :) – r3mus n0x Mar 15 '19 at 21:36