Подтвердить что ты не робот

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
  • Я впервые заметил это замедление в системе, в которой измерения времени находятся за пределами перезапускаемого процесса; такое же поведение.

Любые подсказки? Все, что я могу предоставить, чтобы помочь? Спасибо!

4b9b3361

Ответ 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 для одной и той же физической страницы? Какая-то структура данных разделена?).