父子进程信号处理问题

As we know , we can use :

void (*signal(int sig, void (*func)(int)))(int);

to set handler when our process have receive a signal. 

signal table : we can get them by  man 7 signal

First the signals described in the original POSIX.1-1990 standard.

Signal Value Action Comment
──────────────────────────────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at tty
SIGTTIN 21,21,26 Stop tty input for background process
SIGTTOU 22,22,27 Stop tty output for background process

 

There are some notes we should pay attention to when programming:

  • avoid zomie process
  • avoid signal was ignore when concurrent server run

 

so let's take a look at some local program:

#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void unix_error(const char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(0);
}
//this will generate a zombie process 
void handler2(int sig)
{
    pid_t pid;
    if((pid=waitpid(-1, NULL, 0))>0)
        printf("Handler reped child %d\n", (int)pid);
    else
        unix_error("waitpid error");
    sleep(2);
    return;
}
//this will process all signal when process the same signal
void handler2(int sig)
{
    pid_t pid;
    while((pid=waitpid(-1, NULL, 0))>0)
        printf("Handler reped child %d\n", (int)pid);
    if(errno!=ECHILD)
        unix_error("waitpid error");
    sleep(2);
    return;
}
#define MAXBUF 256
int main(void)
{
    int i, n;
    char buf[MAXBUF];
    if(signal(SIGCHLD, handler2)==SIG_ERR)
        unix_error("signal error");
    
    // parent creates children
    for(i = 0; i<3; i++)
    {
        if(fork()==0)
        {
            printf("Hello from child %d\n", (int)getpid());
            sleep(1);
            exit(0);
        }
    }
    while((n=read(STDIN_FILENO, buf, sizeof(buf)))<0)
        if(errno!=EINTR)
            unix_error("read error");
    
    printf("parent processing input\n");
    //when running in this loop , check ps to see zombie process <defunct>
    while(1)
        ;
    
    printf("bye\n");//will never run
    exit(0);

}

there are two handler2 function , one  will generate a zombie process ,while the other will not. 

At the first case , we can see zombie process when we run in while(1);

[dengwei@localhost signal]$ ps -a
PID TTY TIME CMD
5913 pts/5 00:00:39 python
11774 pts/8 00:00:08 sc
11777 pts/8 00:00:00 sc <defunct>

That's because 

when we handling SIGCHLD signal in handler2 function for the first time, we sleep for 2 sec. At the same time , another SIGCHLD was sent , but as it was the same SIGCHLD signal , then the second signal was discarded.

 

this is ref from <Computer System: A Programmer's Perspective>: 

  • Pending signals can be blocked. Unix signal handlers typically block pending signals of the type currently being processed by the handler. For example, suppose a process has caught a SIGINT signal and is currently running its SIGINT handler. If another SIGINT signal is sent to the process, then the SIGINT will become pending, but will not be received until after the handler returns.
  • Pending signals are not queued. There can be at most one pending signal of any particular type. Thus, if two signals of type are sent to a destination process while signal is blocked because the destination process is currently executing a handler for signal , then the second signal is simply discarded; it is not queued. The key idea is that the existence of a pending signal merely indicates that at least one signal has arrived.
  • System calls can be interrupted. System calls such as read, wait, and accept that can potentially block the process for a long period of time are called slow system calls. On some systems, slow system calls that are interrupted when a handler catches a signal do not resume when the signal handler returns, but instead return immediately to the user with an error condition and errno set to EINTR.

so , the same theory:

Let's see our concurrent server program with feature that accept each connection and fork a child to handler the client's requiration:

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                    listenfd, connfd;
    pid_t                childpid;
    socklen_t            clilen;
    struct sockaddr_in    cliaddr, servaddr;
    void                sig_chld(int);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);
    int on = 1;
    Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    Signal(SIGCHLD, sig_chld);    /* must call waitpid() */

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue;        /* back to for() */
            else
                err_sys("accept error");
        }

        if ( (childpid = Fork()) == 0) {    /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);    /* process the request */
            exit(0);
        }
        Close(connfd);            /* parent closes connected socket */
    }
}
#include    "unp.h"

void
sig_chld(int signo)
{
    pid_t    pid;
    int        stat;

    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}

Pay attention to the following:

if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
//we handler slow system call

 Signal(SIGCHLD, sig_chld);    /* must call waitpid() */

// got all children end signal
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);

The waitpid() system call suspends execution of the calling process
until a child specified by pid argument has changed state. By default,
waitpid() waits only for terminated children, but this behavior is mod‐
ifiable via the options argument, as described below.

The value of pid can be:

< -1 meaning wait for any child process whose process group ID is
equal to the absolute value of pid.

-1 meaning wait for any child process.

0 meaning wait for any child process whose process group ID is
equal to that of the calling process.

> 0 meaning wait for the child whose process ID is equal to the
value of pid.

The value of options is an OR of zero or more of the following con‐
stants:

WNOHANG return immediately if no child has exited.

posted @ 2013-01-29 12:09  邓维  阅读(1131)  评论(0编辑  收藏  举报