0
votes

Nowadays, I'm learning signals by own efforts with concurrency. However, I have some problems while using them in action. In this program, I expect that there are two processes (one child - one parent), parent sends SIGHUP, SIGINT, SIGTSTP signals to its child respectively. I want the child to handle them in same order. I'm trying to apply synchronization with signals here. I have two major problems that

  • When parent runs at first prior its child, it works but it misses SIGINT, not go into its handler.
  • When child runs at first prior its parent, it blocks. I expect in either way the program runs well.

I really appreciate as well if you let me inform the other issues and their solutions along with the major ones'.

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


char const * hupMsg = "CHILD: I have received a SIGHUP\n";
int hupMsgLen;
void sighup(int x) {
    write(STDERR_FILENO, hupMsg, hupMsgLen);

}

char const * intMsg = "CHILD: I have received a SIGINT\n";
int intMsgLen;
void sigint(int x) {
    write(STDERR_FILENO, intMsg, intMsgLen);
}

char const * die = "CHILD: My DADDY has Killed me!!!\n";
int dieLen;
void sigtstp(int x) {
    write(STDERR_FILENO, die, dieLen);
    exit(111);     // <-- child dies with 111 exit code
}


char const * sigusr1Msg = "SIGUSR1 is being caught\n";
int sigusr1MsgLen;
void sigusr1(int x) {
    write(STDERR_FILENO, sigusr1Msg, sigusr1MsgLen);
}




int main()
{
    dieLen = strlen(die);
    sigusr1MsgLen = strlen(sigusr1Msg);
    hupMsgLen = strlen(hupMsg);
    intMsgLen = strlen(intMsg);

    int         pid;
    sigset_t    allBlockedMask, prevMask, parentUSR1Mask,
                sighupSet, sigintSet, sigtstpSet;

    sigfillset(&sighupSet);
    sigdelset(&sighupSet, SIGHUP);
    sigfillset(&sigintSet);
    sigdelset(&sigintSet, SIGINT);
    sigfillset(&sigtstpSet);
    sigdelset(&sigtstpSet, SIGTSTP);


    sigfillset(&allBlockedMask);

    sigfillset(&parentUSR1Mask);
    sigdelset(&parentUSR1Mask, SIGUSR1);


    struct sigaction saINT, saHUP, saTSTP;
    memset(&saINT, 0, sizeof(saINT));
    memset(&saHUP, 0, sizeof(saHUP));
    memset(&saTSTP, 0, sizeof(saTSTP));
    saINT.sa_flags = 0;
    saINT.sa_handler = sigint;
    saHUP.sa_flags = 0;
    saHUP.sa_handler = sighup;
    saTSTP.sa_flags = 0;
    saTSTP.sa_handler = sigtstp;



    struct sigaction saUSR1;
    memset(&saUSR1, 0, sizeof(saUSR1));
    saUSR1.sa_flags = 0;
    saUSR1.sa_handler = sigusr1;
    sigaction(SIGUSR1, &saUSR1, 0);



    /* get child process */

    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {   /* child */

        fprintf(stderr, "child pid %i\n", getpid());

        /* critical section to set signal handlers */
        sigprocmask(SIG_BLOCK, &allBlockedMask, &prevMask);
        fprintf(stderr, "child in blocking\n");

        /* set function calls */
        sigaction(SIGHUP, &saHUP, 0);
        sigaction(SIGINT, &saINT, 0);
        sigaction(SIGTSTP, &saTSTP, 0);

        /* let parent know, you can continue your process */
        kill(getppid(), SIGUSR1);

        fprintf(stderr, "child now UNblocked \n");
        sigprocmask(SIG_SETMASK, &prevMask, 0);

        /* here, I want to receive signals in turn */
        sigsuspend(&sighupSet);
        sigsuspend(&sigintSet);
        sigsuspend(&sigtstpSet);


    }
    else {
        /* parent */
        //sleep(2);
        fprintf(stderr, "parent pid %i\n", getpid());


        fprintf(stderr, "parent is waiting\n");
        sigsuspend(&parentUSR1Mask);
        fprintf(stderr, "parent is running\n");


        fprintf(stderr, "\nPARENT: sending SIGHUP\n");
        kill(pid, SIGHUP);

        fprintf(stderr, "\nPARENT: sending SIGINT\n");
        kill(pid, SIGINT);

        fprintf(stderr, "\nPARENT: sending SIGTSTP\n\n");
        kill(pid, SIGTSTP);

        wait(0);
    }

    return 0;
}

Sample output when parent runs at first:

parent pid 4628
parent is waiting
child pid 4629
child in blocking
child now UNblocked 
SIGUSR1 is being caught
parent is running

PARENT: sending SIGHUP

PARENT: sending SIGINT

PARENT: sending SIGTSTP

CHILD: I have received a SIGHUP
CHILD: My DADDY has Killed me!!!

Process finished with exit code 0

Sample output when child runs at first:

child pid 4657
child in blocking
child now UNblocked 
SIGUSR1 is being caught
parent pid 4656
parent is waiting
.
.
.
1
exit() is not async-signal-safe. You must not call it from a signal handler. You may use _exit() instead, but sure to understand the difference between the two. - John Bollinger
@JohnBollinger < Thank you. AFAIK, exit() is like garbage collector, closes file descriptors, deallocate memory blocks before exiting whilst _exit() doesn't. Is there other point I missed? - snr
Actually, closing files and releasing memory are two things that you can expect always to happen when a process terminates. Among the things exit() does that _exit() does not are to flush open streams and to call registered atexit handlers. - John Bollinger

1 Answers

2
votes

Issues:

  • As mentioned in comments, signal handlers must not call exit(), but they may call _exit().

  • Your signal handlers do not block signals. This may in fact be responsible for some of your problems, as your signal handlers can themselves be interrupted by signals. In the "parent runs first" case, for example, the write() calls in the child's signal handlers may be interrupted by new signals. Solve this by using the sa_mask members of your struct sigaction objects to specify masks of signals to block during execution of your handlers.

  • As you have observed, you have a race condition between delivery to the parent of the child's SIGUSR1 and the parent's call to sigsuspend(). Solve this by blocking all signals in the parent before the fork instead of in the child afterward. The parent will then not receive the child's SIGUSR1 until it calls sigsuspend() (with a mask that allows it). The parent can restore the original mask after its sigsuspend() returns; the child can continue to restore its signal mask where it now does.