【问题标题】:fork(): way for a child process to check if parent is dead?fork():子进程检查父进程是否死亡的方法?
【发布时间】:2012-09-14 10:16:56
【问题描述】:

背景

我正在用 C 编写一个共享库,与 LD_PRELOAD 动态链接,用于拦截和覆盖来自预加载它的应用程序的网络调用,例如 socket()connect()recv()、@987654326 @等

在库的初始化中,我使用pthread_create() 启动一个线程。该线程轮询一些映射到用户空间的内核内存。

该库具有足够的通用性,可以处理(但不限于)网络基准应用程序,例如 netperfiperfsockperf.

我的问题

一切正常,生活在大多数情况下都是甜蜜的,除了一个。去魔化的应用程序。例如,如果我启动 netservernetperf 基准测试应用程序的服务器端)作为守护进程,即没有-D 参数,这是应用程序的第一件事确实是调用fork()。在 fork 上,父级使用 exit(EXIT_SUCCESS) 关闭,子级监听连接。父母退出的事实杀死了我的轮询线程。

所以我想要实现的是让孩子在父母离开时产生一个新的轮询线程。我可以拦截并覆盖fork() 电话,但从根本上说,我怎样才能让孩子知道父母是否离开了?我不能做任何假设,因为库必须是通用的。

谢谢。

【问题讨论】:

  • 您需要正确地守护您的应用程序,以便它在自己的进程组中,因此当父进程死亡时它不会得到 SIGHUP。或者你需要忽略 SIGHUP。
  • @JonathanLeffler 所说的 - 在这种情况下 fork() fork() 是你的朋友
  • 您可能会在 What's the difference between nohup and a daemon 找到一些帮助。
  • 我真的希望他不是设备驱动程序开发人员 - 它可以解释为什么我的打印机不能一直工作;-)
  • @JonathanLeffler,我不认为你明白。首先,我的库必须对应用程序透明。我不能盲目地开始更改进程组和 umasks 和 chdirs。这会使调用应用程序处于奇怪的状态并导致错误。其次,当我希望子进程检测父进程是死了还是还活着时,您建议我应该将子进程与其父进程隔离。

标签: c linux


【解决方案1】:

您可以定期轮询 getppid() 函数。一旦它开始返回 '1'(init 进程的 id) - 你的父母已经死了。

更新

摘自“man pthread_create”:

新线程以下列方式之一终止:...

  • 进程中的任何线程调用exit(3),或主线程 从 main() 执行返回。这会导致所有的终止 进程中的线程。

因此,如果您的线程是由调用 exit 的 netserver 进程创建的 - 是的,该线程将被终止

【讨论】:

  • 它是如何工作的?如果您的原始父母去世,您将被系统进程(通常是 PID 1)继承。所以,如果你的父母 PID 改变了,你的父母已经死了。但是如果系统进程已经死了,你的程序可能也没有运行。
  • 是的,但是,例如,netserver 在获得新的传入连接时也会分叉。因此,如果它同时获得 100 个连接,我最终将有 100 个线程轮询 getppid()
  • ...这100个线程也会轮询你的共享内存,对吧?
  • @JonathanLeffler 对不起,乔纳森,主题问题已经足够精确了:“fork(): way for a child process to check if parent is dead?”
  • @Serge 我试过你的例子。但对我来说getppid() 如果父母去世,不要返回 1。它返回另一个 id,但总是大于 1。这应该意味着有些人不能假设您描述的这种行为?
【解决方案2】:

您可以使用ptread_atfork(3) 在孩子中启动您的线程。

【讨论】:

  • 如果我不必确保我没有在父母未死的孩子中启动投票线程,那就可以了。
【解决方案3】:

大约一年后,我有了自己问题的答案,即“子进程如何知道其父进程已死?”

好吧,孩子可以使用getppid() 询问其父母的PID 是多少。如果父 PID 为 1,则意味着子进程已重新成为init 进程的父进程。因此,父母已经走了。

例如:

void child_fork(void)
{
    int parent_pid;
    usleep(1);
    parent_pid = (int)getppid();

    if (parent_pid == 1)
        run_my_polling_thread();
}

注意:当一个进程分叉时,父进程会进入睡眠状态。子进程先运行,再唤醒父进程(对于Copy-on-Write优化,这里不再赘述)。 usleep(1)call 是为了让孩子立即入睡,以便父母有时间醒来并终止。在孩子醒来之前,它将被重新设置为init

现在,在 lib 初始化时,我只需要调用:

__register_atfork(NULL, NULL, child_fork, NULL)

但是,此解决方案并未涵盖所有情况。例如,如果父母有多个孩子怎么办?我真的希望他们都启动一个投票线程吗?

目前这对我来说是一个可以接受的解决方案,尽管它并不理想。

【讨论】:

  • 你真的不应该假设任何关于执行顺序的事情。另外,这与 Serge 的答案的前半部分有什么不同?
  • @VladLazarenko 只需重新阅读 Serge 的答案并接受它。为什么你觉得需要刻薄?
  • @Hasturkun 我可以放心地假设孩子将在分叉后首先运行。请参阅 Robert Love 的 Linux Kernel Development 第 3 版。