【问题标题】:Handling zombies with SIGCHLD使用 SIGCHLD 处理僵尸
【发布时间】:2020-08-02 02:58:30
【问题描述】:

在我的程序中,我正在监听传入的 SIGCHLD 信号以避免僵尸。

代码:

void myhandler(int signo)
{   
    printf("test");
    int status;
    pid_t pid;

    while((pid = waitpid(-1, &status, WNOHANG)) > 0)
        ++count;
}

int main(int argc, char const *argv[])
{

    struct sigaction sigchld_action;
    memset(&sigchld_action,0,sizeof(sigchld_action));
    sigchld_action.sa_handler = &myhandler;
    sigaction(SIGCHLD,&sigchld_action,NULL);

    if(fork() == 0){
        exit(0);
    }
    if(fork()==0){
        exit(0);
    }
    if(fork()==0){
        exit(0);
    }


    while(wait(NULL) > 0)
    ++count;

    return 0;
}

问题是,分叉子的数量和 printf("test") 的输出数量有时不匹配。分叉子的数量大于数量 printf("test")。

这段代码是否保证不会有僵尸?如果是,它是如何做到这一点的?它没有打印正确数量的“测试”。 waitpid() 是否在 while 内多次清除死去的孩子?

当这个信号处理程序被调用时会发生什么,同时另一个孩子可能会死掉。默认情况下,信号将被阻止。(当处理程序运行时,另一个孩子可能会死亡)。 waitpid 是否清除信号处理程序运行时发送的信号的进程?

另外,计数器不相等。 (static volatile int) 或者我尝试了原子整数。

【问题讨论】:

  • 您不能在信号处理程序中安全地使用像 printf() 这样的 stdio 函数,顺便说一句。
  • @Shawn 是的我知道,但是如果使用原子计数器,结果是一样的。
  • 他简单的意思是相同类型的未决信号没有排队,它们被丢弃。此外,信号处理程序在处理程序期间屏蔽信号(除非您明确阻止它这样做)。所以他们到了,但没有及时得到处理。你不会以这种方式收获所有的孩子——僵尸会出现。
  • 其实我最后的说法是错误的,可能所有的孩子还在while循环中被收割。即使处理程序只被调用一次。检查调用了多少次处理程序在这里不是一个有效的指标。毕竟,您的代码以这种方式是安全的,对于某些语句中的混淆感到抱歉
  • @Root2A 这就是我的想法,是的。如果没有更多的孩子要收割,它会退出处理程序,但是当再次发生 SIGCHLD 时,您将再次进入处理程序。

标签: c unix operating-system signals


【解决方案1】:

正在发生两种不同的事情:

  1. printf 不是异步信号安全的,因此您不应从信号处理程序中调用它。将其替换为write
  2. 您只在信号处理程序中增加count 一次。如果您想知道有多少进程死亡,则需要在 while 循环中增加它。

试试这个代码:

void handler(int signo)
{   
    int status;
    pid_t pid;
    while((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        write(1, "test", 4); /* technically this may result in a partial write and you should loop it, but in practice I think this'll be fine for this example */
        ++count;
    }
}

使用该代码,您可能没有僵尸,并且tests 的正确数量和count 中的正确值。但是,还有一个竞争条件:如果在对waitpid 的最终调用和信号处理程序结束之间,另一个子进程死亡,那么将不会收到SIGCHLD,因此它将是一个僵尸直到它之后的那个也死了。这种极端情况的解决方案要复杂得多,并且取决于应用程序其余部分的结构。

【讨论】:

  • 结果相同,在 5 个进程中计数在 2-3-5 之间变化。但是我在父级的末尾添加了一个无限循环以避免终止父级,然后我运行 ps 并且我没有看到任何僵尸。如果计数小于进程数,怎么没有僵尸?
  • @Root2A 你能编辑你的问题以包含一个完整的程序而不仅仅是一个信号处理程序吗?然后可能会更清楚发生了什么。
  • @Root2A 两个有趣的事情:1. handlermyhandler。 2.如果你想让你的信号处理程序做等待,那为什么main中有一个wait
  • @Root2A 从来没有孤儿进程。当一个进程死亡而它仍然有尚未收获的孩子时,孩子会被收养(通常由init)。你没有看到僵尸,因为init 在父级退出后正在收割它们。
  • @Root2A 你的最新版本在main 结尾处带有while(wait(NULL) > 0) 将永远不会在它的所有孩子之前退出。 (当然,也可以是kill -9d 什么的。)
猜你喜欢
  • 2013-05-31
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多