首页 > 解决方案 > 如何在 Linux 中跟踪所有后代进程

问题描述

我正在制作一个需要生成多个进程的库。

我希望能够知道在测试期间产生的所有后代进程的集合。这对于在通过测试结束时终止表现良好的守护程序或通过获取失败测试后存在的任何进程的堆栈跟踪来调试死锁/挂起进程很有用。

由于其中一些需要生成守护进程(fork,fork,然后让父进程死亡),我们无法通过遍历进程树来找到所有进程。

目前我的方法是:

  1. 使用注册处理程序os.register_at_fork
  2. 在 fork 中,在 child 中,聚集一个文件并附(pid, process start time)加到另一个文件中
  3. 然后在需要时,我们可以通过迭代文件中的条目并保持(pid,进程开始时间)与现有进程匹配的条目来获取子进程集

这种方法的缺点是:

  1. 仅适用于multiprocessingos.fork- 在使用subprocess或非 Python 进程生成新的 Python 进程时不起作用。
  2. 锁定分叉可能会使测试期间的事情比实际情况更具确定性,从而隐藏竞争条件。

我正在寻找一种不同的方法来跟踪避免这两个缺点的子进程。

我考虑过的替代方案:

  1. 使用bcc注册 fork/clone 的探针 - 问题是它需要 root,我认为从贡献者的角度运行测试会有点烦人。是否有类似的事情可以作为非特权用户仅用于当前进程和后代?
  2. 使用与上述类似的 strace(或 ptrace) - 问题在于性能影响。有几个测试专门对启动时间进行基准测试,ptrace 的开销相对较大。如果只跟踪 fork 和 clone 可能会更少,但它仍然与在测试超时时获取堆栈的愿望相冲突。

有人可以建议一种方法来避免上述问题的陷阱和缺点吗?我现在只对 Linux 感兴趣,理想情况下它不需要 4.15 之后的内核。

标签: pythonlinuxpython-multiprocessingptracebpf

解决方案


鉴于我原来的帖子的限制,我使用了以下方法:

  1. putenv("PID_DIR", <some tempdir>)
  2. 对于当前进程,覆盖forkclone使用将跟踪进程开始时间的版本到$PID_DIR/<pid>. 覆盖是使用plthook完成的,并适用于所有加载的共享对象。dlopen还应该重写以重写任何其他动态加载的库上的函数。
  3. 设置一个具有__libc_start_mainforkcloneas实现的库LD_PRELOAD

此处提供了一个初始实现,如下所示:

import process_tracker; process_tracker.install()

import os

pid1 = os.fork()
pid2 = os.fork()
pid3 = os.fork()

if pid1 and pid2 and pid3:
    print(process_tracker.children())

推荐阅读