繁体   English   中英

父进程仅从其子进程接收到SIGCHLD一次或两次,而不管它多少次fork()

[英]The parent-process only received SIGCHLD from its children once or twice, no mater how many times it fork()

我几乎执行与此处相同的操作,以实现一个signalprocmask程序,在该程序中父级将处理其子级发送的所有SIGCHLD(我也测试了链接中的代码,但结果是相同的-仅父级一次或两次没有收到SIGCHLD,它分叉了多少次)

预期结果将是:(加数与del相同)

add job 12987
add job 12988
Wed Dec 19 22:20:59 CST 2018
del from 12987
Wed Dec 19 22:21:00 CST 2018
del from 12988
add job 12989
add job 12990
del from 12989
Wed Dec 19 22:21:01 CST 2018
add job 12991
Wed Dec 19 22:21:02 CST 2018
del from 12990
Wed Dec 19 22:21:03 CST 2018
del from 12991

但是结果是:(并非所有SIGCHLD都会被父进程捕获)

add job 12987
add job 12988
Wed Dec 19 22:20:59 CST 2018
del from 12987
now the list is: 12988
Wed Dec 19 22:21:00 CST 2018
del from 12988
now the list is: 
add job 12989
add job 12990
Wed Dec 19 22:21:01 CST 2018
add job 12991
Wed Dec 19 22:21:02 CST 2018
add job 12992
Wed Dec 19 22:21:03 CST 2018
add job 12993
Wed Dec 19 22:21:04 CST 2018
add job 12994
Wed Dec 19 22:21:05 CST 2018
add job 13091
Wed Dec 19 22:21:06 CST 2018
add job 13092
Wed Dec 19 22:21:07 CST 2018
Wed Dec 19 22:21:08 CST 2018 

这是我的代码:

#include "apue.h"
#include <sys/wait.h>
#include <sys/signal.h>
#include <errno.h>

void printJobs();
void addJob(int);
void delJob();

void handler(int sig)
{
    sigset_t mask_all, pre_all;
    sigfillset(&mask_all); // fill all bits of the mask
    pid_t pid;
    while ((pid = waitpid(-1, NULL, 0)) > 0) {
        sigprocmask(SIG_BLOCK, &mask_all, &pre_all);
        printf("del from %d\n", pid);
        delJob(pid);
        sigprocmask(SIG_UNBLOCK, &pre_all, NULL);
    }
    if (errno != ECHILD)
        printf("waitpid error\n");
}

int main(int argc, char **argv)
{
    pid_t pid;
    sigset_t mask_all, mask_one, pre_one;

    sigfillset(&mask_all);
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD);
    signal(SIGCHLD, handler);
    for (int i = 0; i < 10; ++i) {
        sigprocmask(SIG_BLOCK, &mask_one, &pre_one); // block SIGCHLD
        if ((pid = fork()) == 0) {
            sigprocmask(SIG_SETMASK, &pre_one, NULL);
            sleep(1);
            execve("/bin/date", argv, NULL);
        }
        sigprocmask(SIG_BLOCK, &mask_all, NULL);     // block all sigals
        addJob(pid);
        sigprocmask(SIG_SETMASK, &pre_one, NULL); // unblock SIGCHLD
        sleep(1);
    }
    exit(0);
}

typedef struct Node {
    int val;
    struct Node *next;
} Node, *pNode;

pNode phead = NULL, ptail = NULL;

void printJobs()
{
    pNode pt = phead;
    while (pt) {
        printf("%d", pt->val);
        pt = pt->next;
    }
    printf("\n");
}

void delJob(int pid)
{
    if (ptail) {
        pNode pt = phead, pre = NULL;
        while (pt && pt->val != pid) {
            pre = pt;
            pt = pt->next;
        }
        if (!pt) {
            printf("No job %d\n", pid);
            return;
        }
        if (pt == phead) { // only have one node or empty
            phead = phead->next ? phead->next : NULL;
            free(pt);
            ptail = phead ? ptail : NULL;
        } else { // have more than one nodes
            printf("del %d\n", pt->val);
            free(pt);
            pre->next = NULL;
            ptail = pt == ptail ? pre : ptail;
        }
        printf("now the list is: ");
        printJobs();
    } else {
        printf("No job %d\n", pid);
    }
}

void addJob(int pid)
{
    printf("add job %d\n", pid);
    pNode pt = malloc(sizeof(Node));
    pt->val = pid;
    pt->next = NULL;
    if (!phead) {
        phead = ptail = pt;
    } else {
        ptail->next = pt;
        ptail = pt;
    }
}

您的父进程不会等待其子进程完成(通过wait()waitpid() ),因此无法确保它们在运行时发出的SIGCHLD信号仍在运行时传递给它。 信号处理程序执行等待在这里无济于事,因为这不会阻止进程在孩子还活着时终止。 父级和所有子级可能会在大约同一时间终止,因此即使我们假设从信号处理程序调用printf()行为与您一样,父级仅报告一个或两个子级的信号也就不足为奇了似乎期望。

此外,该程序的所有sleep()确实会使问题更加困惑。 当然,它不是管理进程间同步和定时的正确工具。

考虑以下经过大量修饰和修改的导数以进行比较:

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

volatile sig_atomic_t signal_count;

void handler(int sig) {
    signal_count += 1;
}

int main(int argc, char *argv[]) {
    pid_t pid;

    signal_count = 0;
    signal(SIGCHLD, handler);
    for (int i = 0; i < 10; ++i) {
        if ((pid = fork()) == 0) {
            sleep(1);
            execve("/bin/date", argv, NULL);
        }
        sleep(1);
    }

    // wait for the children to terminate
    while (wait(NULL) != -1) { /* empty */ }

    printf("Process %d handled %d SIGCHLD signals\n", (int) getpid(), (int) signal_count);
    exit(0);
}

我对该程序的测试运行产生了以下输出:

 Wed Dec 19 09:56:08 CST 2018 Wed Dec 19 09:56:09 CST 2018 Wed Dec 19 09:56:09 CST 2018 Wed Dec 19 09:56:10 CST 2018 Wed Dec 19 09:56:10 CST 2018 Wed Dec 19 09:56:11 CST 2018 Wed Dec 19 09:56:11 CST 2018 Wed Dec 19 09:56:11 CST 2018 Wed Dec 19 09:56:12 CST 2018 Wed Dec 19 09:56:12 CST 2018 Process 2169 handled 10 SIGCHLD signals 

特别注意最后一行。 它确认所有10个预期信号均由原始父进程处理。

附录

正如@zwol在评论中观察到的那样,在某种程度上,父级可以依赖于wait()waitpid()阻塞来收集其子级,因此它根本不需要为SIGCHLD注册处理程序。 每次wait()返回一个非错误代码时,它可以执行所需的任何工作。 使用信号处理程序来收集子项的情况恰恰相反,您要避免阻塞父进程,或者想出在哪里尝试以非阻塞方式收集子项。

尽管如此,可能是这样的情况,尽管您通常不希望阻止收集孩子,但父母希望在某个时候(也许正准备终止)确保收集所有剩余的孩子。 在这种情况下,使用两种方法在同一程序中收集孩子可能是有意义的。

附录2

借助@NominalAnimal,我也观察到,信号处理的常规实现方式不会将多个相同类型的非实时信号同时排队到同一线程。 如果将信号传递到已在其上挂起该类型信号的线程,则新信号不会产生任何其他影响。 因此,尽管我为每个孩子展示了一个单独的SIGCHLD ,但是我不能保证看到一个以上的孩子,因为第二个到第十个可以在第一个仍待处理的情况下交付。 保持信号处理程序实施的简短性可以减少以这种方式“丢失”信号的可能性,但不能消除它。

但是请注意,这些特定的信号处理细节不会阻止wait()waitpid()收集进程终止的子级。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM