Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do I need to do anything with a SIGCHLD handler if I am just using wait() to wait for 1 child to finish at a time?

Tags:

c

posix

wait

I've got a program that is forking to a child to do some work, but I am only doing one child at a time at this time. I am using wait() to wait for the child to finish, do I need to do anything with SIGCHLD as well (such as disable the handler)?

In my situation I am getting a value of EINTR in errno which leads me to think that I need to mask SIGCHLD.

In broad strokes, this is the program:

  • read arguments
  • for(list of work to do)
  • fork()
  • if child, execlp() to work program
  • if parent, wait() for child to finish
  • when child finishes, parent loops to next work item
like image 575
CyberSkull Avatar asked Dec 21 '22 01:12

CyberSkull


1 Answers

Theory

POSIX says about SIG_IGN (strictly under the XSI extension note):

If the action for the SIGCHLD signal is set to SIG_IGN, child processes of the calling processes shall not be transformed into zombie processes when they terminate. If the calling process subsequently waits for its children, and the process has no unwaited-for children that were transformed into zombie processes, it shall block until all of its children terminate, and wait(), waitid(), and waitpid() shall fail and set errno to [ECHILD].

And in the description of <signal.h> says that the default signal disposition for SIGCHLD is SIG_IGN 'ignore' (code 'I'). However, it is the SIG_DFL behaviour is to 'ignore' the signal; the signal is never generated. This is different from the SIG_IGN behaviour.

So, you don't have to set a signal handler, but you shouldn't get information about the process back — you should just get an error once there are no children left.

Demonstration Code

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

static siginfo_t sig_info;
static volatile sig_atomic_t sig_num;
static void *sig_ctxt;

static void catcher(int signum, siginfo_t *info, void *vp)
{
    sig_num = signum;
    sig_info = *info;
    sig_ctxt = vp;
}

static void set_handler(int signum)
{
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = catcher;
    sigemptyset(&sa.sa_mask);

    if (sigaction(signum, &sa, 0) != 0)
    {
        int errnum = errno;
        fprintf(stderr, "Failed to set signal handler (%d: %s)\n", errnum, strerror(errnum));
        exit(1);
    }
}

static void prt_interrupt(FILE *fp)
{
    if (sig_num != 0)
    {
        fprintf(fp, "Signal %d from PID %d\n", sig_info.si_signo, (int)sig_info.si_pid);
        sig_num = 0;
    }
}

static void five_kids(void)
{
    for (int i = 0; i < 5; i++)
    {
        pid_t pid = fork();
        if (pid < 0)
            break;
        else if (pid == 0)
        {
            printf("PID %d - exiting with status %d\n", (int)getpid(), i);
            exit(i);
        }
        else
        {
            int status = 0;
            pid_t corpse = wait(&status);
            printf("Child: %d; Corpse: %d; Status = 0x%.4X\n", pid, corpse, (status & 0xFFFF));
            prt_interrupt(stdout);
            fflush(0);
        }
    }
}

int main(void)
{
    printf("SIGCHLD set to SIG_IGN\n");
    signal(SIGCHLD, SIG_IGN);
    five_kids();
    printf("SIGCHLD set to catcher()\n");
    set_handler(SIGCHLD);
    five_kids();
    return(0);
}

The fflush(0); call ensures standard output (in particular) is flushed, which matters if the output of the sample program is piped to another program.

Example Output

The output from the example code agrees with the theory — but requires a little interpretation.

SIGCHLD set to SIG_IGN
PID 4186 - exiting with status 0
SIGCHLD set to SIG_IGN
Child: 4186; Corpse: -1; Status = 0x0000
PID 4187 - exiting with status 1
Child: 4187; Corpse: -1; Status = 0x0000
PID 4188 - exiting with status 2
Child: 4188; Corpse: -1; Status = 0x0000
PID 4189 - exiting with status 3
Child: 4189; Corpse: -1; Status = 0x0000
PID 4190 - exiting with status 4
Child: 4190; Corpse: -1; Status = 0x0000
SIGCHLD set to catcher()
PID 4191 - exiting with status 0
SIGCHLD set to catcher()
Child: 4191; Corpse: -1; Status = 0x0000
Signal 20 from PID 4191
Child: 4192; Corpse: 4191; Status = 0x0000
PID 4192 - exiting with status 1
Child: 4193; Corpse: 4192; Status = 0x0100
Signal 20 from PID 4192
PID 4193 - exiting with status 2
Child: 4194; Corpse: 4193; Status = 0x0200
Signal 20 from PID 4193
PID 4194 - exiting with status 3
Child: 4195; Corpse: 4194; Status = 0x0300
Signal 20 from PID 4194
PID 4195 - exiting with status 4

The first section of the output agrees exactly with the theory; the calling code gets no information about the dead children except that there are no dead children (left) to wait for.

The second section of the output agrees with the theory too, but it appears that the children aren't executing before the parent, so the first wait() from the parent has no dead children to wait for, and it returns -1. Subsequent calls get the various children, but one escapes unwaited for (but the corpse will be cleaned up by the system). If the code used waitpid() such as: pid_t corpse = waitpid(pid, &status, 0);, it would wait for each child in turn.

Some of this commentary may need revision in the light of the modified comments about 'ignore' above. TBD — out of time right now.

Mac OS X 10.8.4 on 3 GHz Intel Core 2 Duo, GCC 4.6.0, 64-bit compilation:

gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
    -Wold-style-definition sigchld.c -o sigchld 

This was a quick adaptation of some existing code to test the behaviour of SIGINT and pause(). It may have some superfluous material in it. You do not have to call wait() or one of its relatives in the signal handler; you may do so if you wish, though.

like image 103
Jonathan Leffler Avatar answered Feb 09 '23 00:02

Jonathan Leffler