leonzai
leonzai

Reputation: 53

I have a question about c with send a signal

this is my code

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

void handler1(int sig) {
    int pid;
    if ((pid = waitpid(-1, NULL, 0)) < 0)
        printf("waitpid error");
    printf("Handler reaped child %d\n", pid);
    sleep(1);
}

int main() {
    int i, n;

    if (signal(SIGCHLD, handler1) == SIG_ERR) {
        printf("signal error\n");
    }

    for (i = 0; i < 3; ++i) {
        if (fork() == 0) {
            printf("Hello from child %d\n", getpid());
            exit(0);
        }
    }
//    sleep(1);
    printf("Father love you!\n");
    while (1);
    exit(0);
}

When I run it, it shows this :

Father love you!
Hello from child 7843
Hello from child 7844
Hello from child 7842
Handler reaped child 7842

But I think it should be

Father love you!
Hello from child 7843
Hello from child 7844
Hello from child 7842
Handler reaped child 7842
Handler reaped child 7843      

There a repetition of Handler reaped child xx.

If I uncomment sleep(1);, it will show what i want :

Hello from child 7858
Handler reaped child 7858
Hello from child 7859
Hello from child 7860
Handler reaped child 7859
Father love you!

I dont know why the first one has only one Handler reaped child. Please help me, thank you in advence.

Upvotes: 1

Views: 202

Answers (1)

zwol
zwol

Reputation: 140659

The operating system is allowed to deliver only one SIGCHLD signal to tell you that several child processes have exited. That means you need to loop calling waitpid inside the signal handler, something like this:

void sigchld_handler(int unused)
{
    pid_t pid;
    int status;
    for (;;) {
        pid = waitpid(-1, &status, WNOHANG);
        if (pid == -1) {
            if (errno != ECHILD)
                printf("waitpid failure: %s\n", strerror(errno);
            return;
        }
        printf("child %d exit status %d\n", (int)pid, status);
    }
}

You need to use WNOHANG so that waitpid will fail and set errno to ECHILD when there are no more processes to wait for, instead of blocking (possibly forever).

Also, in order to make it safe to call printf from inside the signal handler, you need to use sigprocmask and sigsuspend in your main function, so that the signal can only be delivered when normal execution is blocked on sigsuspend. Also also, never use signal, only sigaction; the specification of signal doesn't cover several important details that will bite you.

int main(void)
{
   sigset_t sigchld_set;
   sigemptyset(&sigchld_set);
   sigaddset(&sigchild_set, SIGCHLD);

   sigset_t unblock_sigchld_set;
   if (sigprocmask(SIG_BLOCK, &sigchld_set, &unblock_sigchld_set)) {
       perror("sigprocmask");
       return 1;
   }
   sigdelset(SIGCHLD, &unblock_sigchld_set);

   struct sigaction sa;
   sigfillset(&sa.sa_mask);
   sa.sa_handler = sigchld_handler;
   sa.sa_flags = SA_RESTART;
   if (sigaction(SIGCHLD, &sa, 0)) {
       perror("sigaction");
       return 1;
   }

   /* fork loop goes here */

   for (;;)
       sigsuspend(&unblock_sigchld_set);

   return 0;
}

Making main exit when all child processes have been waited for is left as an exercise. (Hint: you need C11 atomic types.)

Upvotes: 3

Related Questions