Fork() утечка? Принимая все больше и больше, чтобы развить простой процесс
У меня есть система, в которой выполняются два одинаковых процесса (пусть их называют репликами). Когда сигнализируется, реплика дублирует себя, используя вызов fork()
. Третий процесс выбирает один из процессов для случайного уничтожения, а затем сигнализирует другому, чтобы создать замену. Функционально система работает хорошо; он может убивать/респанировать реплики весь день, кроме проблемы с производительностью.
Вызов fork()
длится дольше. Ниже приведена простейшая настройка, которая все еще отображает проблему. Временная диаграмма будет отображена на графике ниже: ![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-kernel), но я не знаю, с чего начать искать.
Что я пробовал:
- Попробовал kmemleak, который ничего не поймал. Это означает, что если есть некоторая память "утечка", что она еще доступна.
-
/proc/<pid>/maps
не растет.
- В настоящее время запущено ядро 3.14 с RT patch (обратите внимание, что это происходит с процессами non-rt и rt), а также попробовал 3.2.
- Процессы зомби не являются проблемой. Я попробовал версию, в которой я настраивал другой процесс как подстроку, используя prctl
- Я впервые заметил это замедление в системе, в которой измерения времени находятся за пределами перезапускаемого процесса; такое же поведение.
Любые подсказки? Все, что я могу предоставить, чтобы помочь? Спасибо!
Ответы
Ответ 1
Замедление вызвано накоплением анонимных vmas и является известной проблемой. Проблема очевидна, когда существует большое количество вызовов fork()
, а родительские выходы перед дочерними элементами. Следующий код воссоздает проблему (источник Даниэль Форрест):
#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
.
Существует патч (источник), который ограничивает длину скопированного anon_vma_chain
до пяти. Я могу подтвердить, что исправление устраняет проблему.
Что касается того, как я в конечном итоге нашел проблему, я, наконец, просто начал размещать вызовы printk
по всему тегу fork
, проверяя время, указанное в dmesg
. В конце концов я увидел, что это вызов anon_vma_fork
, который длился дольше и дольше. Затем это был быстрый поиск google.
Прошло довольно много времени, поэтому я по-прежнему буду благодарен за любые предложения по улучшению способа отслеживания проблемы. И всем тем, кто уже потратил время, пытаясь помочь мне, спасибо.
Ответ 2
Возможно, вы могли бы попробовать использовать общий вызов wait(), а не waitpid()? Это просто догадка, но я слышал, что это было лучше от профессора в университете. Кроме того, вы пробовали использовать адрес дезинфицирующего средства
Кроме того, вы можете использовать GDB для отладки дочернего процесса (если вы еще этого не пробовали). Вы можете использовать режим follow-fork:
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>
Ответ 3
Просто идея: может быть, это связано с MMU или кешем? Насколько я понимаю, при fork() ядро заполняет соответствующие записи в таблице ссылками на одни и те же физические страницы ОЗУ. Вы написали: "Вы делаете фиктивные записи, но делаете ли вы их исполняемым segmens (если да, то как, потому что они должны быть защищены от записи)? На графике кажется, что производительность увеличивается в некоторых точках (512 × 512 * 3? 512 * 4?). Это заставляет меня подозревать, что система (ядро?, Аппаратное обеспечение?) Знает о проблеме и использует некоторое обходное решение (дублирующие антивирусы в MMU для одной и той же физической страницы? Какая-то структура данных разделена?).