fork()泄漏了吗?花更长时间来分叉一个简单的过程

时间:2014-12-08 23:08:57

标签: c linux linux-kernel fork

我有一个系统,其中运行两个相同的进程(让我们称它们为副本)。发出信号后,副本将使用fork()调用复制自身。第三个进程选择一个进程随机杀死,然后发信号通知另一个进程以创建替换进程。在功能上,该系统运作良好;除性能问题外,它可以整天杀死/重生副本。

fork()来电越来越长。以下是仍然显示问题的最简单设置。时间be显示在下图中:fork timing

副本的代码如下:

void restartHandler(int signo) {
// fork
  timestamp_t last = generate_timestamp();
  pid_t currentPID = fork();


  if (currentPID >= 0) { // Successful fork
    if (currentPID == 0) { // Child process
      timestamp_t current = generate_timestamp();
      printf("%lld\n", current - last);

      // unblock the signal
      sigset_t signal_set;
      sigemptyset(&signal_set);
      sigaddset(&signal_set, SIGUSR1);
      sigprocmask(SIG_UNBLOCK, &signal_set, NULL);

      return;
    } else {   // Parent just returns
      waitpid(-1, NULL, WNOHANG);
      return;
    }
  } else {
    printf("Fork error!\n");
    return;
  }
}

int main(int argc, const char **argv) {
  if (signal(SIGUSR1, restartHandler) == SIG_ERR) {
    perror("Failed to register the restart handler");
    return -1;
  }

  while(1) {
    sleep(1);
  }

  return 0;
}

系统运行的时间越长,就越糟糕。

很抱歉没有具体问题,但有没有人知道发生了什么?在我看来,内核中存在资源泄漏(因此是linux内核标记),但我不知道从哪里开始查找。

我尝试过:

  • 尝试kmemleak,但没有抓到任何东西。这意味着如果有一些记忆"泄漏"它仍然可以到达。
  • /proc/<pid>/maps没有增长。
  • 目前使用RT补丁运行3.14内核(请注意,非rt和rt进程会发生这种情况),并且还尝试过3.2。
  • 僵尸进程不是问题。我尝试过使用prctl
  • 将另一个进程设置为subreaper的版本
  • 我首先注意到系统中的这种减速,其中定时测量在重新启动的过程之外被降低;同样的行为。

任何提示?我可以提供哪些帮助?谢谢!

3 个答案:

答案 0 :(得分:2)

减速是由匿名vmas的积累引起的,并且是一个已知问题。当有大量fork()次呼叫并且父级在子级之前退出时,问题就很明显了。以下代码重新创建问题(source Daniel Forrest):

#include <unistd.h>

int main(int argc, char *argv[])
{
  pid_t pid;
  while (1) {
    pid = fork();
    if (pid == -1) {
      /* error */
      return 1;
    }
    if (pid) {
      /* parent */
      sleep(2);
      break;
    }
    else {
      /* child */
      sleep(1);
    }
  }
  return 0;
}

可以通过查看anon_vma中的/proc/slabinfo确认行为。

有一个补丁(source)将复制的anon_vma_chain的长度限制为五。我可以确认补丁修复了这个问题。

至于我最终如何发现问题,我终于开始在printk代码中调用fork次调用,检查dmesg中显示的时间。最终我发现这是对anon_vma_fork的召唤,这种召唤越来越长。然后这是谷歌搜索的快速问题。

花了相当长的时间,所以我仍然感谢任何有关更好地追踪问题的建议。对于那些已经花时间试图帮助我的人,谢谢你。

答案 1 :(得分:1)

也许您可以尝试使用通用的wait()调用,而不是waitpid()?这只是一个猜测,但我听说本科教授更好。此外,您是否尝试过使用address sanitizer

此外,您也可以使用GDB调试子进程(如果您还没有尝试过)。您可以使用follow-fork-mode:

set follow-fork-mode child

但这只能调试父级。您可以通过获取子进程的pid来调试它们,然后在分叉后调用sleep():

attach <child process pid>

然后致电:

detach

这很有用,因为您可以将内存泄漏转储到valgrind中。只需用

调用valgrind
valgrind --vgdb-error=0...<executable>

然后设置一些相关的断点,并继续执行您的程序,直到您点击断点然后搜索泄漏:

monitor leak_check full reachable any

然后:

monitor block_list <loss_record_nr>

答案 2 :(得分:0)

只是一个想法:也许它与MMU或缓存有关?据我所知,在fork()时,内核使用对相同物理RAM页面的引用填充适当的表条目。你写道,你正在进行虚拟写操作,但你是否正在执行可执行的segmens(如果是,如何,因为这些应该是写保护的)?从图中看,似乎性能在某些点上增加(512?512 * 3?512 * 4?)。它让我怀疑系统(内核?,硬件?)是否意识到这个问题并使用了一些解决方法(在MMU中对同一物理页面重复一些?一些数据结构被拆分了?)。