从 parent 分离 child 进程

Detach child process from parent

当从 parent 派生的 child 进程死亡时,它在某种程度上仍然存在并处于僵尸状态,直到它从 wait() 调用中回收。

是否可以分离这种 parent-child 关系并让 children 自动收割?也许将 child 孤立起来进行初始化,或者什么?

可能的用例:在 long-lived 程序中创建大量 fire-and-forget 进程,而不 "holding" 增加无法被 [= 回收的僵尸 PID 列表24=].

根据 the POSIX _exit() documentation:

If the parent process of the calling process has set its SA_NOCLDWAIT flag or has set the action for the SIGCHLD signal to SIG_IGN:

  • The process' status information (see Status Information), if any, shall be discarded.

  • The lifetime of the calling process shall end immediately. If SA_NOCLDWAIT is set, it is implementation-defined whether a SIGCHLD signal is sent to the parent process.

  • If a thread in the parent process of the calling process is blocked in wait(), waitpid(), or waitid(), and the parent process has no remaining child processes in the set of waited-for children, the wait(), waitid(), or waitpid() function shall fail and set errno to [ECHILD].

Otherwise:

  • Status information (see Status Information) shall be generated.

  • The calling process shall be transformed into a zombie process. Its status information shall be made available to the parent process until the process' lifetime ends.

  • ...

总之,要防止子进程成为僵尸进程,最简单的方法就是调用sigignore( SIGCHLD );

更新

这确实会影响等待 any 子进程的能力,这可能是不希望的。 setsid() library function 允许进程将自身与其父进程分离:

pid_t child = fork();
if ( ( pid_t ) 0 == child )
{
    int rc = setsid();

    rc = execv(...);
}
else ...

在我安装的 Solaris 11.2 实例上,取消关联的子进程不会创建僵尸,也不会发送 SIGCHLD 到父进程。

这是 "fire-and-forget" 子进程的缩写守护进程,仅执行必要的操作以防止创建僵尸或向父进程发送 SIGCHLD。有关更完整的守护进程,请参阅 Linux daemonize

从 Python 3.2 开始,您可以使用 subprocess.Popen() 并传递 start_new_session=True 来完成此操作。

文档状态:

If start_new_session is true the setsid() system call will be made in the child process prior to the execution of the subprocess. (POSIX only)

https://docs.python.org/3/library/subprocess.html#subprocess.Popen

请注意,这不允许 "to do this from the parent at an arbitrary stage in the child's lifetime" 如后续评论中所述。

忽略SIGCHLD或为SIGCHLD安装一个带有SA_NOCLDWAIT标志的sighandler可以防止所有children变成僵尸(/成为wait-能)。

要为单个 child 执行此操作,您可以使用 Linux 的 clone() 而不使用 SIGCHLD 标志,或者您可以加倍 fork (或者 vfork 如果你知道如何安全地做到这一点)在启动 child 时。然后 child 可以立即 _exit(0) 并被其 parent 编辑 wait 并且 grandchild 可以继续进行实际工作(重新 parent按你说的改成了init)。

(双叉的轻微扩展将完全妖魔化 child 或 grandchild also 调用 setsid 来创建一个新会话, 一个新的进程组并从控制终端分离。Setsid 单独不会阻止 child 成为僵尸。它只会在其默认设置中隐藏 ps 的僵尸,其中 ps 仅显示当前 terminal-controlling 会话中的进程。)