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

我有一个系统运行两个相同的进程(让我们称之为副本)。 当发信号时,副本将通过使用fork()调用来自我复制。 第三个进程select其中一个进程随机杀死,然后发信号通知另一个进程。 在function上,该系统运作良好; 它可以整天杀死/重生副本,除了性能问题。

fork()调用需要的时间越来越长。 以下是仍然显示问题的最简单的设置。 时间是显示在下面的图表中: 叉时机

副本的代码如下:

 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-kernel标签),但我不知道从哪里开始寻找。

我曾经尝试过:

  • 试过kmemleak ,什么都没有收到。 这意味着,如果有一些内存“泄漏”,它仍然可以到达。
  • /proc/<pid>/maps没有增长。
  • 目前使用RT补丁运行3.14内核(注意这是在非rt和rt进程中发生的),并且也在3.2上进行了尝试。
  • 僵尸进程不是一个问题。 我已经尝试了一个版本,我使用prctl作为一个subreaper安装另一个进程
  • 我首先注意到一个系统中的时间测量在重新启动的过程之外, 同样的行为。

任何提示? 我可以提供什么帮助? 谢谢!

这种放缓是由匿名vmas的积累引起的,是已知的问题。 当存在大量的fork()调用并且父节点在子节点之前退出时,问题就很明显了。 下面的代码重现了这个问题( 来源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; } 

行为可以通过检查/proc/slabinfo anon_vma来确认。

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

至于如何最终发现问题,我终于开始在整个fork代码中进行printk调用,检查dmesg显示的时间。 最终我看到这是对anon_vma_fork的调用,而这个调用时间越来越长。 那么这是谷歌搜索的一个快速的问题。

这花了相当长的时间,所以我仍然希望有更好的方法来追踪这个问题的任何建议。 对于那些已经花时间试图帮助我的人,谢谢。

也许你可以尝试使用通用的wait()调用,而不是waitpid()? 这只是一个猜测,但我听说从本科教授那里得到了更好的结果。 另外,你有没有尝试使用地址消毒剂

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

 set follow-fork-mode child 

但那只能调试父母。 你可以通过获取子进程的pid来调试,在fork之后调用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> 

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