`
trophy
  • 浏览: 178602 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

system() 阻塞SIGCHLD信号的原因

 
阅读更多

代码1:APUE10.18节的system函数源代码 
int system(const char *cmdstring) /* with appropriate signal handling */
{
    pid_t               pid;
    int                 status;
    struct sigaction    ignore, saveintr, savequit;
    sigset_t            chldmask, savemask;
    ……
    sigemptyset(&chldmask);         /* now block SIGCHLD */
    sigaddset(&chldmask, SIGCHLD);
    if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) 
        return(-1);

    if ((pid = fork()) 
        status = -1;    /* probably out of processes */
    } else if (pid == 0) {          /* child */
        ……
        sigprocmask(SIG_SETMASK, &savemask, NULL);

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);     /* exec error */
    } else {                        /* parent */
       while (waitpid(pid, &status, 0) 
           if (errno != EINTR) {
               status = -1; /* error other than EINTR from waitpid() */
               break;
           }
    }
    ……
    if (sigprocmask(SIG_SETMASK, &savemask, NULL) 
        return(-1);

    return(status);}
如果该函数的调用者在其SIGCHLD信号处理函数中调用了waitpid函数来获得任意子进程的终止状态,且在fork之前没有阻塞SIGCHLD信号,则子进程结束,父进程执行SIGCHLD信号处理函数且得到了子进程的终止状态。而该函数中的waitpid函数由于没有得到其创建的子进程的终止状态而出错。

定义了一个函数,函数中fork了一个子进程,并且定义的函数要得到关于子进程的一些信息,例如子进程ID、子进程终止状态等,而该函数的调用者所注册的SIGCHLD信号处理函数会影响定义的这个函数获取这些信息,因此为了避免该函数在获取这些信息之前,由于子进程终止触发SIGCHLD信号处理函数先执行,在fork之前都将SIGCHLD信号阻塞了,在函数获取了相关信息后,才对SIGCHLD信号接触阻塞。 


接下来我们以代码1为例完整的解释一下阻塞SIGCHLD信号的原因:

在一个进程终止或停止时,SIGCHLD信号被送给其父进程。按系统默认,将忽略此信号。如果父进程希望了解其子进程的这种状态改变,则应捕捉此信号。信号捕捉函数中通常要调用wait函数以取得子进程ID和其终止状态。

一般的,父进程在生成子进程之后会有两种情况:一是父进程继续去做别的事情;另一是父进程什么都不做,一直在wait子进程退出。

SIGCHLD信号就是为第一种情况准备的,它让父进程去做别的事情,而只要父进程注册了处理该信号的函数,在子进程退出时就会调用该函数,在函数中wait子进程得到终止状态之后再继续做父进程的事情。


我们先来写一个function_model函数模型,如下所示:
int function_model(…) {    pid_t               pid;    int                 status;    ……    if ((pid = fork())         status = -1;        } else if (pid == 0) {          /* child */       ……    } else {                        /* parent */       while (waitpid(pid, &status, 0)            if (errno != EINTR) {               status = -1;                break;           }    }        ……    return(status);}


当我们定义了一个具有function_model函数模型结构的函数时,应该在fork之前将SIGCHLD信号阻塞,fork之后,父子进程的SIGCHLD信号都是被阻塞的。子进程在进行任何的操作之前,应该将SIGCHLD恢复至原来的设置。父进程则应该在waitpid函数之后,将SIGCHLD恢复至原来的设置。


即这种类型的函数的正确的格式为:

int function_model(…) 
{
    pid_t               pid;
    int                 status;
    sigset_t            chldmask, savemask;
    ……
    sigemptyset(&chldmask);         /* now block SIGCHLD */
    sigaddset(&chldmask, SIGCHLD);
    if(sigprocmask(SIG_BLOCK, &chldmask, &savemask) 
        return(-1);

    if ((pid = fork()) 
        status = -1;    
} else if (pid == 0) {          /* child */

sigprocmask(SIG_SETMASK, &savemask, NULL);
       ……
    } else {                        /* parent */
       while (waitpid(pid, &status, 0) 
           if (errno != EINTR) {
               status = -1; 
               break;
           }
    }
    ……
if(sigprocmask(SIG_SETMASK, &savemask, NULL) 
    return(-1);
    return(status);
}

父进程阻塞SIGCHLD信号的原因在于:如果function_model函数的调用者caller在调用function_model之前,注册了SIGCHLD信号的处理函数sigchld_handler,且sigchld_handler函数中调用了waitpid函数等待任意子进程结束,如下所示:

void sigchld_handler(int signo)
{
    pid_t pid;
    int status;
    while((pid=waitpid(-1,&status,WNOHANG))>0)
    {
        ……
}
return ;
}

    若我们没有阻塞SIGCHLD信号,则当function_model函数中fork的子进程结束时,会向父进程发送SIGCHLD信号,则其信号处理函数sigchld_handler被调用,并在sigchld_handler函数中调用了waitpid得到了这个子进程的退出状态,然后释放掉其占用的进程表等其它资源。返回到function_model函数中继续执行,由于其创建的子进程的退出状态已经被获取,内核不再保存其退出状态,所以其中调用的waitpid函数由于无法得到其退出状态而出错,并将status设置为-1。
    若我们阻塞了SIGCHLD信号,则当function_model函数中fork的子进程结束时,内核会向父进程发送SIGCHLD信号,但SIGCHLD信号被阻塞而不递送给父进程,即处于未决状态,因此function_model函数中的waitpid就能正确得到这个子进程的退出状态。当解除对SIGCHLD信号的阻塞时,SIGCHLD信号会被递交给父进程,引发父进程调用sigchld_handler函数,由于在function_model函数中以调用waitpid获取了function_model函数创建的子进程的退出状态,内核不再保存其终止信息,所以sigchld_handler中的waitpid也不会的到其退出状态。

    下面是Advanced Programming in the UNIX® Environment: Second Edition(APUE中的解释:
POSIX.1 states that if wait or waitpid returns the status of a child process while SIGCHLD is pending, then SIGCHLD should not be delivered to the process unless the status of another child process is also available. None of the four implementations discussed in this book implements this semantic. Instead, SIGCHLD remains pending after the system function calls waitpid; when the signal is unblocked, it is delivered to the caller. If we called wait in the sig_chld function in 
Figure 10.26
, it would return 1 with errno set to ECHILD, since the system function already retrieved the termination status of the child.


http://blog.chinaunix.net/u1/59740/showart_486730.html

分享到:
评论

相关推荐

    linux下system函数的简单分析

    2. **信号掩码**:通过 `__sigprocmask` 函数设置了一个新的信号掩码,用于阻塞 `SIGCHLD` 信号,防止在父进程等待子进程结束时接收到这个信号导致行为异常。 3. **进程创建**:使用 `FORK()` 或 `__fork()` 创建...

    深刻理解进程间通信

    Linux支持多种类型的信号灯,包括System V信号灯和POSIX信号灯。 **4.3 信号灯与内核** 信号灯的实现依赖于内核的支持,通过内核提供的系统调用来实现互斥和同步。 **4.4 操作信号灯** 操作信号灯的主要函数包括...

    unix高级编程网络编程面试题

    26. **子进程收尸的三种方法**:通常是父进程调用`wait()`、`waitpid()`或者`SIGCHLD`信号处理函数中调用`wait`。 27. **信号和信号量的区别**:信号是用于进程间通信或操作系统的通知机制,而信号量是一种用于进程...

    我的APUE2读书笔记

    SIGCHLD与SIGCLD信号** 这些信号用于通知父进程子进程已经终止。 **8. 产生信号:kill和raise函数** `kill`和`raise`函数可以发送信号给进程。 **9. alarm和pause** `alarm`用于设定定时器,`pause`用于暂停进程...

    PHP swoole的process模块创建和使用子进程操作示例

    SIGCHLD 信号会在子进程结束时被发送,父进程可以在这个信号的处理函数中调用 swoole_process::wait 方法来回收子进程资源。 以上就是 PHP Swoole 的 process 模块创建和使用子进程操作的相关知识点。需要注意的是...

    APUE读书笔记《UNIX环境高级编程第二版》

    实例:在abort、system及sleep函数的实现中使用信号机制** - 内置函数可能利用信号实现某些功能。 **14. 作业控制信号** - 作业控制信号用于控制作业状态。 **15. 信号值与信号名字之间的转换** - 使用`strsignal...

    解析如何在C语言中调用shell命令的实现方法

    该函数在执行过程中会忽略SIGINT(中断)和SIGQUIT(退出)信号,并且会暂时阻塞SIGCHLD信号。如果命令执行成功,`system()`返回命令的退出状态,否则返回-1。注意,如果`string`参数为NULL,`system()`会返回非零值...

    C++教程网视频:linux网络编程

    - **僵进程与SIGCHLD信号**:子进程退出后父进程未回收会导致僵进程;SIGCHLD用于通知父进程子进程已退出。 ##### socket编程(七) - **TCP 11种状态**:包括LISTEN、SYN_SENT、SYN_RCVD、ESTABLISHED、CLOSE_...

    APUE读书笔记(Unix高级环境编程)

    - **SIGCHLD与SIGCLD信号**:子进程终止时向父进程发送的信号。 - **产生信号**:使用`kill`和`raise`函数发送信号。 - **信号集及其处理函数**:使用`sigset_t`类型管理信号集合。 - **sigaction函数**:更灵活地...

Global site tag (gtag.js) - Google Analytics