C-программа не возвращается из оператора ожидания

Мне нужно перенести C-программу с OpenVMS на Linux, и теперь у меня проблемы с программой, генерирующей подпроцессы. Генерируется подпроцесс (fork работает нормально), но execve не работает (что правильно, так как указано неправильное имя программы).

Но чтобы сбросить количество активных подпроцессов, я впоследствии вызываю ожидание (), которое не возвращается. Когда я смотрю на процесс через ps, я вижу, что подпроцессов больше нет, но wait() не возвращает ECHILD, как я думал.

while (jobs_to_be_done)
{
   if (running_process_cnt < max_process_cnt)
   {
      if ((pid = vfork()) == 0)
      {
         params[0] = param1 ;
         params[1] = NULL ;
         if ((cstatus = execv(command, params)) == -1)
         {
            perror("Child - Exec failed") ;   // this happens
            exit(EXIT_FAILURE) ;
         }
      }
      else if (pid < 0)
      {
         printf("\nMain - Child process failed") ;
      }
      else
      {
         running_process_cnt++ ;
      }
   }
   else   // no more free process slot, wait
   {
      if ((pid = wait(&cstatus)) == -1)   // does not return from this statement
      {
         if (errno != ECHILD)
         {
            perror("Main: Wait failed") ;
         }
         anz_sub = 0 ;
      }
      else
      {
         ...
      }
   }
}

Нужно ли что-то сделать, чтобы сообщить команде ожидания, что подпроцессов больше нет? С OpenVMS программа работает нормально.

Заранее большое спасибо за вашу помощь


person Jörg Mohren    schedule 21.07.2015    source источник
comment
если (как вы говорите в комментариях к ответам) вы изменили vfork на fork и все еще получаете проблему, я бы сказал, что, скорее всего, вы неверно истолковываете происходящее. Попробуйте сократить его до минимального, полного, компилируемого и проверяемого примера (stackoverflow.com/help/MCVE).   -  person davmac    schedule 21.07.2015


Ответы (2)


В настоящее время я не рекомендую использовать vfork в Linux, так как fork(2) достаточно эффективен благодаря ленивым методам copy-on-write в ядре линукса.

Вы должны проверить результат fork. Если это не сбой, процесс был создан, и wait (или waitpid( 2), возможно, с WNOHANG, если вы не хотите особо ждать, а просто узнать об уже завершившихся дочерних процессах...) не должно давать сбоев (даже если функция exec в дочернем элементе не удалась, форк удалось).

Вы также можете осторожно использовать сигнал SIGCHLD, см. сигнал(7). Защитный способ использования сигналов состоит в том, чтобы установить некоторый флаг volatile sigatomic_t в обработчиках сигналов, а также проверить и очистить эти флаги внутри вашего цикла. Напомним, что внутри обработчика сигнала можно вызывать только функции безопасные для асинхронных сигналов (а их довольно мало). Прочтите также о сигналах POSIX.

Найдите время, чтобы прочитать Продвинутое программирование в Linux, чтобы получить более широкую картину. Не пытайтесь имитировать OpenVMS на POSIX, а думайте в духе POSIX или Linux!

Возможно, вы захотите всегда использовать waitpid в своем цикле, возможно (иногда или всегда) с WNOHANG. Таким образом, waitpid следует вызывать не только в другой части вашего if (running_process_cnt < max_process_cnt), но и, вероятно, в каждой итерации вашего цикла.

Возможно, вы захотите скомпилировать все предупреждения и информацию об отладке (gcc -Wall -Wextra -g), а затем использовать отладчик gdb. Вы также можете strace(1) свою программу (вероятно, с -f )

Вы можете узнать об избыточном выделении памяти. Мне не нравится эта функция, и я обычно отключаю ее (например, запуская echo 0 > /proc/sys/vm/overcommit_memory от имени пользователя root). См. также proc(5) — очень полезно знать о...

person Basile Starynkevitch    schedule 21.07.2015
comment
Здравствуйте Василий Спасибо за ваш ответ. Я заменил vfork() на fork(), но результат остался прежним. Затем я добавил в задание несколько команд sleep и увидел, что fork() работает нормально, execv() тоже работает, но после завершения подпроцессов wait() не возвращается. - person Jörg Mohren; 21.07.2015
comment
Я бы сказал, что posix_spawn здесь лучший вариант, если вы можете его достать. fork/exec, к сожалению, ненадежны в обычных системах, особенно Cygwin и большинстве (не Linux) реализаций POSIX, не допускающих чрезмерного выделения виртуальной памяти. (Не по теме, но меня укусили в прошлом.) - person doynax; 22.07.2015
comment
Но вопрос про линукс.... Когда fork/exec ненадежен? Конечно, может выйти из строя. - person Basile Starynkevitch; 22.07.2015
comment
@Basile Starynkevich: В Linux? Насколько мне известно, fork+exec почти идентичен vfork+exec, если не считать недопустимого поведения vfork. За исключением некоторого времени копирования метаданных виртуальной памяти. Я лично сталкивался с проблемами на Cygwin, который использует несколько неприятных и крайне ненадежных хаков для подделки fork на уровне пользователя, а также на старом сервере Solaris, у которого иногда заканчивалась память при разветвлении (система обещала поддержать разветвленная память с пространством подкачки на случай, если она понадобится, в отличие от Linux рулетки.) - person doynax; 22.07.2015

От 1_:

Ребенок не должен возвращаться из текущей функции или вызывать exit(3), но может вызывать _exit(2)

Вы не должны вызывать exit(), когда вызов execv (после vfork) терпит неудачу - вместо этого вы должны использовать _exit(). Вполне возможно, что это само по себе вызывает проблему, которую вы видите, когда wait не возвращается.

Я предлагаю вам использовать fork вместо vfork. Это намного проще и безопаснее в использовании.

Если это само по себе не решает проблему, вам нужно выполнить некоторую отладку или сократить код до тех пор, пока вы не найдете причину. Например, следующее должно работать без зависаний:

#include <sys/wait.h>

int main(int argc, char ** argv)
{
    pid_t pid;
    int cstatus;
    pid = wait(&cstatus);
    return 0;
}

Если вы можете убедиться, что эта программа не зависает, то причиной зависания должен быть какой-то аспект вашей программы. Я предлагаю вставлять операторы печати непосредственно перед и после вызова wait.

person davmac    schedule 21.07.2015
comment
Здравствуйте, я заменил операторы exit() в подпроцессе на _exit() - тоже не сработало. Затем я заменил wait() циклом вызовов waitpid() для реальных подпроцессов. По крайней мере, эти операторы возвращаются, но все с возвращаемым значением 0 и errno ECHILD. Подпроцессы еще не завершены во время вызова waitpid(). - person Jörg Mohren; 21.07.2015
comment
Итак, моя проблема заключается в том, что waitpid(pid, &cstatus, 0) возвращается, но не доставляет pid завершенного процесса и правильный статус выхода подпроцесса. - person Jörg Mohren; 21.07.2015
comment
@JörgMohren см. пример в комментарии выше. Работает ли это для вас? Я предлагаю напечатать дочерний pid сразу после fork, а также перед вами wait/waitpid. Возможно, в вашей программе есть проблема с повреждением памяти. Или вы дважды ожидаете одного и того же дочернего процесса. - person davmac; 21.07.2015
comment
Здравствуйте, davmac Большое спасибо; твой вариант мне тоже подходит. Теперь мне нужно только выяснить, какая из частей моей более сложной программы вызывает проблему. Но это должно быть возможно! - person Jörg Mohren; 21.07.2015
comment
Здравствуйте, я думаю, что нашел точку, в которой что-то идет не так. Я скопировал код davmac'а в функцию в своем коде, вызывая ее с нескольких позиций. Все идет нормально до того момента, когда я подключаю свою программу к базовой базе данных Oracle. После подключения больше не работает. Я представлю эту проблему Oracle (если никто из вас не знает, в чем может быть причина) - person Jörg Mohren; 21.07.2015
comment
Хм, интересно, клиентская библиотека Oracle (а) запускает постоянный дочерний процесс и (б) устанавливает обработчик сигнала SIGCHLD или запускает поток, который вызывает wait? - person davmac; 21.07.2015
comment
(или, может быть, он устанавливает обработчик SIGCHLD в SIG_IGN?) - person davmac; 21.07.2015
comment
Теперь я нашел способ как возможное решение проблемы. Если я вызываю waitpid(0, &cstatus, 0), программа ожидает только моих сгенерированных подпроцессов (т. е. подпроцесс, сгенерированный DB-connect, находится в другой группе). Но что меня немного удивляет, так это то, что waitpid() ждет, но возвращает ECHILD (хотя я видел, что она действительно ждет до конца подпроцесса). Я мог бы также получить состояние возврата, проанализировав, произошли ли какие-либо ошибки, но это не распространяется на системные проблемы, вызвавшие сбой процесса. Любые идеи по этому поводу? Кроме того, я попробовал сигнал (SIGCHLD, SIG_DFL) перед обработкой подпроцесса. Не помогло. - person Jörg Mohren; 22.07.2015
comment
Извините, моя вина. Команда «сигнал (SIGCHLD, SIG_DFL)» DID помогла (я использовал неправильный исполняемый файл для моего теста). Большое спасибо davmac за вашу прекрасную идею. - person Jörg Mohren; 22.07.2015