如何跟踪 Linux 中的所有后代进程

How to track all descendant processes in Linux

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

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

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

目前我的做法是:

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

这种方法的缺点是:

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

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

我考虑过的备选方案:

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

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

对于 subprocess.Popen,可调用参数有 preexec_fn 个参数——您可以自己破解它。

或者,看看 cgroups(控制组)——我相信他们可以处理棘手的情况,例如守护程序创建等等。

考虑到我原来 post 的限制,我使用了以下方法:

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

初始实现可用here 用法如下:

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())