fork(): 子进程如何检查父进程是否死亡?

7

背景

我正在用C语言编写一个共享库,并使用LD_PRELOAD进行动态链接,旨在拦截和覆盖应用程序预加载的网络调用,例如socket()connect()recv()send()等。

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

该库旨在足够通用以处理 - 但不限于 - 网络基准测试应用程序,例如netperfiperfsockperf

我的问题

除了一种情况外,一切正常,大多数情况下都很顺利。那就是守护进程应用程序。例如,如果我将netservernetperf基准测试应用程序的服务器端)作为守护进程启动,即没有使用-D参数,那么应用程序做的第一件事就是调用fork()。在fork时,父进程使用exit(EXIT_SUCCESS)关闭,而子进程则监听连接。父进程退出会导致我的轮询线程终止。

因此,我想要实现的是:如果父进程已经退出,则子进程生成一个新的轮询线程。我可以拦截和覆盖fork()调用,但从根本上说,我如何让子进程知道父进程是否已经退出呢?我不能做任何假设,因为这个库必须足够通用。

谢谢。


3
你需要正确地将应用程序守护化,使其位于自己的进程组中,并且在父进程终止时不会收到 SIGHUP 信号。或者你需要忽略 SIGHUP 信号。 - Jonathan Leffler
@JonathanLeffler所说的 - 在这种情况下,fork()是你的朋友。 - Adrian Cornish
你可能会在nohup和守护进程有什么区别找到一些帮助。 - Jonathan Leffler
我真的希望他不是设备驱动程序开发人员 - 这可能解释了为什么我的打印机并不总是工作;-) - Adrian Cornish
@JonathanLeffler,我认为你没有理解。首先,我的库必须对应用程序透明。我不能盲目更改进程组、umask和chdir。那会让调用应用程序处于奇怪的状态并导致错误。其次,你建议我应该隔离子进程与父进程,但我想让子进程检测父进程是否已死亡或仍然存活。 - Asblarf
显示剩余2条评论
3个回答

5
您可以定期轮询getppid()函数。一旦它开始返回'1'(init进程的ID)- 您的父进程就死了。 更新 来自'man pthread_create'的摘录:
新线程以以下方式之一终止:......
  • 进程中任何一个线程调用exit(3),或主线程从main()返回。这将导致进程中所有线程的终止。
因此,如果您的线程是由调用exit的netserver进程创建的-是的,这个线程将被终止。

那是怎么回事?如果你的原始父进程死了,你就会被系统进程(通常是PID 1)继承。所以,如果你的父进程PID改变了,你的父进程就已经死亡了。但如果系统进程已经死亡,你的程序可能也没有运行。 - Jonathan Leffler
是的,但是netserver例如在收到新的传入连接时也会进行分叉。因此,如果它同时获得100个连接,我最终将得到100个线程在getppid()上轮询却毫无意义。 - Asblarf
这100个线程也会轮询您的共享内存,对吧? - Serge
@JonathanLeffler 我很抱歉,Jonathan,这个话题的问题已经足够明确了:“fork(): 子进程如何检查父进程是否死亡?” - Serge
1
@Serge 我试了你的例子。但是对于我来说,如果父进程死了,getppid()不会返回1。它会返回另一个进程ID,但始终大于1。这意味着有些人不能假设你描述的这种行为? - timmornYE
显示剩余2条评论

1
大约一年后,我找到了自己问题的答案,即“子进程如何知道其父进程已经死亡?” 子进程可以使用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)调用在此处是为了立即将子进程置于睡眠状态,以便父进程有时间唤醒并终止。在子进程唤醒之前,它将被重新分配给init

现在,在lib init时我只需要调用:

__register_atfork(NULL, NULL, child_fork, NULL)

然而,这个解决方案并不适用于所有情况。例如,如果父级有多个子元素,我真的希望它们都启动一个轮询线程吗?
目前对我来说,这是一个可以接受的解决方案,尽管不是理想的。

你真的不应该假设执行顺序。另外,这和Serge答案的前半部分有什么不同吗? - Hasturkun
@VladLazarenko 刚刚重新阅读了Serge的回答并接受了它。你为什么觉得有必要说话刻薄呢? - Asblarf
@Hasturkun 我可以安全地假设,在 fork 之后,子进程将首先运行。请参阅 Robert Love 的《Linux 内核开发》第三版。 - Asblarf

1

如果我不必确保在一个父进程没有死亡的子进程中启动轮询线程,那就可以这样做。 - Asblarf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接