【问题标题】:Linux system() after setting SA_NOCLDWAIT to SIGCHLD将 SA_NOCLDWAIT 设置为 SIGCHLD 后的 Linux system()
【发布时间】:2013-07-06 03:20:31
【问题描述】:

system()states 的手册页“在命令执行期间,SIGCHLD 将被阻塞”
在我的代码中,我正在为SIGCHLD 安装一个处理程序,它将一些内容写入日志并忽略信号。我这样做是为了了解在我的日志中退出的子进程并避免僵尸进程。

当我使用system() 调用时出现问题。有了这个处理程序system() 总是返回-1 而不是孩子的退出代码。但根据手册页的上述引用,system() 应该处理来自孩子的信号。

我做错了什么?

我的代码如下:

static void handleSignal(int signum, siginfo_t* inf, void* ctx) {
    cout << "in signal " << signum << endl;
}

int main() {
    struct sigaction chldsa, prevchld;
    chldsa.sa_sigaction = &handleSignal;
    sigemptyset(&chldsa.sa_mask);
    chldsa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
    if (sigaction(SIGCHLD, &chldsa, &prevchld) == -1) {
        cout << "Failed setting sigaction SIGCHLD " << errno;
    }

    int x = system("exit 1");
    cout << "RET=" << x << endl;
}

【问题讨论】:

  • 首先,那是 C++,而不是 C。它们是不同的语言,即使它们有相似之处。其次,Linux 中的system(string) 相当于在子进程中执行execl("/bin/sh", "sh", "-c", string, NULL);(根据POSIX.1 兼容性的要求)。为什么不自己写呢?只要不忽略 SIGCHLD (SIG_IGN),它是一个非常简单的函数来实现。需要一个例子吗?虽然它确实解决了您的问题,但它并不完全是您问题的答案——除非您认为 “不要使用 system(),而是使用您自己的版本” 是一个有效的答案。
  • 不使用system() 不是一种选择。这不是我的代码做的。
  • 另外,execl()AFAIK 不等同于 system(),因为它不做 fork()
  • 当我使用system()时你确实写了。另外,“execl() in a child process”的哪一部分你没看懂?
  • 阅读时错过的部分 :)

标签: c linux process signals


【解决方案1】:

如果您想摆脱SA_NOCLDWAIT 及其影响,只需在SIGCHLD 处理程序的end 处获取所有子代即可。 p>

编辑添加:刚刚验证了以下内容:

    pid_t p;

    /* Reap all pending child processes */
    do {
        p = waitpid(-1, NULL, WNOHANG);
    } while (p != (pid_t)0 && p != (pid_t)-1);

SIGCHLD 处理程序(安装SA_NOCLDSTOP |SA_RESTART | SA_SIGINFO)中最后运行时工作正常。没有僵尸,system() 返回正确的退出状态。

请注意,waitpid() 是 Linux 中的 async-signal safe 函数,根据 POSIX.1-2004,WNOHANG 表示调用永远不会阻塞,因此上述循环是安全的。

但是,由于标准信号没有排队,如果两个或多个进程以正确的时间退出,其中一个在父进程中执行子回收循环之后但在信号处理程序返回之前退出,这是可能的,一个或多个孩子不会立即收获。但是,当下一个子进程退出时,它们会。为了缓解这种情况,您应该定期运行上述循环,或者作为程序正常操作的一部分,或者通过定时器中断。循环永远不会阻塞,并且消耗的 CPU 时间非常少。

请记住,waitpid(-1, NULL, WNOHANG) 将返回零,如果有活着的孩子,-1errno == ECHILD 如果没有活着的孩子,或者刚刚收获的进程的进程 ID。 (如果您愿意,您可以在日志记录中使用循环,使用非 NULL 状态指针来捕获已退出的每个子节点的退出状态,并将其记录下来。您甚至可以将定期计时器挂钩到同一信号处理程序,例如。)

【讨论】:

  • 这就是我最终所做的,它适用于单线程应用程序。在多线程应用程序中,这仍然会被破坏,因为信号是由未阻塞的不同线程接收的。
  • 在多线程应用程序中,除了不适用于SIGCHLD 的极少数例外,您希望阻止所有线程中的所有异步信号,除了用于处理它们的线程。做到这一点的可靠方法是将所有对pthread_create 的调用包装在对pthread_sigmask 的适当调用中,这样子进程就会继承正确的掩码。 (如果您尝试在pthread_create 的子方调用pthread_sigmask,则会出现一个竞争窗口,可以将不适当的信号传递给子。)
  • @Zack:我通常不会为我的线程阻塞异步信号。首先,我知道EINTR,并在适当的时候处理它;其次,我经常使用传递给特定线程的(实时)信号来中断阻塞系统调用,例如超时。我敢说你在这里做出过分的断言。依赖SIGCHLD 传递给特定线程——记住它没有排队——而不是在收到 a SIGCHLD 时收割所有子线程,这是自找麻烦。您只是不会为每个退出的孩子获得SIGCHLD,即使使用专用的收割线程也是如此。
  • @NominalAnimal 我想你误解了——确保所有异步信号在所有不期望它们的线程中被阻塞的建议是为了回应 shoosh 关于信号被错误线程处理的抱怨,在他们的多线程应用程序中。它与您关于循环通过waitpid 的观点正交。我承认我不明白 EINTR 与此有什么关系,或者你为什么不使用 SA_RESTART 并忘记它。
  • @Zack:哦!我在太宽泛的背景下解释了你的评论。至于EINTR:信号传递导致当前阻塞的非异步信号安全函数返回-1 和errno==EINTR。我经常利用这一点:超时、通过实时信号上的pthread_sigqueue() 进行异步事件通知等。这确实意味着 I/O 函数需要重试循环。虽然SA_RESTART 标志阻止了大多数(但不是全部)功能发生这种情况,但我通常不依赖它。我从信号传递事件中受益使用,因此在我看来,默认阻止它们会浪费一个有用的工具而没有收获。
【解决方案2】:

Errno 应该显示 SA_NOCLDWAIT 是问题所在。通过提供此标志,您表示您对孩子的状态不感兴趣。这包括由 system() 创建和管理的孩子。系统内部调用 waitpid 将遵循:

POSIX.1-2001 指定如果 SIGCHLD 的处置设置为 SIG_IGN 或为 SIGCHLD 设置 SA_NOCLDWAIT 标志(请参阅 sigaction(2)),则终止的子进程不会成为僵尸和调用 wait()或 waitpid() 将阻塞,直到所有子进程都终止,然后失败,将 errno 设置为 ECHILD。

很明显,您对孩子的地位很感兴趣。您正在检查从 system() 返回的状态。因此,要么删除标志,要么管理所有孩子而无需等待。

如果您处于必须使用 system() 的情况,并且您的代码需要自动收割子代,您可以使用您的处理程序。这不会收集 system() 孩子,因为在通话期间信号被阻止传递。类似的东西:

while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}

【讨论】:

  • 正确,安迪加文。然而,由于在 Linux 中生成了SIGCHLD 信号,因此可以使用它来代替wait()waitpid()。例如,可以使用sigwaitinfo() 等待所需的SIGCHLD 信号,直接为所有其他可能被捕获的SIGCHLD 调用处理程序。这就是为什么我问@shoosh 他们是否需要一个例子。实现非常简单,但特定于 Linux(由于需要 SIGCHLD 信号行为;我没有检查 *BSD 或其他系统是否提供类似行为)。
  • 问题的标题明确指出是 SA_NOCLDWAIT 导致了问题。我的问题是 - 我如何绕过它并让它发挥作用。
  • 该标志与 system() 不兼容。我试图表明如果你想使用 system(),你应该放弃这个标志。你不能选择管理一些孩子的状态,让系统来收割其余的。
猜你喜欢
  • 2021-09-27
  • 2016-10-25
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
相关资源
最近更新 更多