我有一个系统,其中运行两个相同的进程(让我们称它们为副本)。发出信号后,副本将使用fork()
调用复制自身。第三个进程选择一个进程随机杀死,然后发信号通知另一个进程以创建替换进程。在功能上,该系统运作良好;除性能问题外,它可以整天杀死/重生副本。
fork()
来电越来越长。以下是仍然显示问题的最简单设置。时间be显示在下图中:
副本的代码如下:
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内核标记),但我不知道从哪里开始查找。
我尝试过:
/proc/<pid>/maps
没有增长。任何提示?我可以提供哪些帮助?谢谢!
答案 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中。只需用
调用valgrindvalgrind --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中对同一物理页面重复一些?一些数据结构被拆分了?)。