Вызов fork() execvp() _exit() , и процесс не завершается

Итак, краткое резюме: у меня есть программа, которая рекурсивно ищет файл с определенным расширением. Каждый раз, когда он его находит, он создает копию, вносит некоторые изменения в копию, создает патч (используя diff и execvp()) и удаляет исходный файл.

Проблема, с которой я столкнулся, заключается в том, что после нескольких сотен файлов fork() возвращается с сообщением «Ресурс временно недоступен». Я добавил счетчик, чтобы увидеть, сколько процессов все еще выполняется, когда происходит этот сбой, и похоже, что ни один из них не был закрыт - количество открытых процессов всегда совпадает с количеством обработанных файлов.

Теперь у меня сложилось впечатление, что поток должен идти примерно так- fork();//создает дочерний процесс dostuff();//в дочернем процессе _exit(1);//возвращаем управление родителю

но все оказывается не так просто. Возможно, кто-то здесь заметит что-то очевидное, чего мне не хватает в коде.

Я разместил функцию «очистки», которая отвечает за разветвление и исправление — остальное разделено на несколько файлов, так что, надеюсь, этого достаточно.

(аргумент «имя» — исходное имя файла, а «новое имя» — измененная копия.)

void cleanup (char * name, char * newname)
{
    if (pf)
    {
            pid_t patch_pid;

            char * const diffargs[5] = {thisdiff, "-u", newname, name, NULL};

            char * patchname = malloc(strlen(name) + 6);
            strcpy(patchname, name);
            strcat(patchname, ".patch");

            if((patch_pid = fork()) < 0 )
            {
                    printf("fork failed.\n%s\nfilecount: %ld\nopen forks: %d\n", strerror(errno), filecount, pcount);
                    exit(-1);
            }

            pcount++;

            if (patch_pid == 0)
            {
                    FILE *pfp;
                    if ((pfp = fopen(patchname, "w")) == NULL)
                    {
                            printf("Error opening file \"%s\" for writing.\n%s\n", patchname, strerror(errno));
                            exit(-1);
                    }

                    dup2(fileno(pfp), STDOUT_FILENO);
                    fclose(pfp);
                    execvp(diffargs[0], diffargs);
                    free(patchname);

                    if (remove(name) != 0)
                    {
                            printf("Error removing file %s\n%s\n", name, strerror(errno));
                            exit(-1);
                    }

                    if (rename(newname, name) != 0)
                    {
                            printf("Error renaming file %s\n%s\n", newname, strerror(errno));
                            exit(-1);
                    }
                    pcount--;
                    _exit(1);
            }
    }

    else if (!df && !xf)
    {
            if (remove(name) != 0)
            {
                    printf("Error removing file %s\n%s\n", name, strerror(errno));
                    exit(-1);
            }
            if (rename(newname, name) != 0)
            {
                    printf("Error renaming file %s\n%s\n", newname, strerror(errno));
                    exit(-1);
            }
    }
}

person Erik Nyquist    schedule 09.07.2014    source источник
comment
Вы знаете, что если семейство функций exec работает успешно, не возвращаются? Это означает, что любой код, который у вас есть после вашего вызова execvp, не будет работать, если вызов не завершится ошибкой.   -  person Some programmer dude    schedule 09.07.2014
comment
Вы получаете статус выхода завершенных процессов где-то с wait()? Если нет, вы создаете кучу зомби, заполняющих таблицу процессов.   -  person Jens    schedule 09.07.2014
comment
Они не возвращают никакого значения, если нет ошибки... но, конечно же, они в конечном итоге заканчиваются, и выполнение продолжается?   -  person Erik Nyquist    schedule 09.07.2014
comment
Йенс: нет, не знаю. Должен признаться, я не знаю, где было бы подходящее место для wait(). Я думаю, в самом конце подпрограммы cleanup()?   -  person Erik Nyquist    schedule 09.07.2014
comment
Что делает семейство функций exec, так это заменяет текущий процесс процессом из загруженной программы. Когда вы вызовете его, ваш текущий код просто перестанет существовать для процесса.   -  person Some programmer dude    schedule 09.07.2014
comment
После exec дочерний процесс выполняет совершенно другую программу. Когда он завершается, дочерний процесс завершается, он не возвращается к вашему коду.   -  person Barmar    schedule 09.07.2014
comment
Иоахим: правильно, поэтому fork() - это гарантирует, что после завершения execvp родитель продолжит работу. правильный? или нет?   -  person Erik Nyquist    schedule 09.07.2014
comment
Вы должны вызвать wait в блоке else if (patch_pid == 0).   -  person Barmar    schedule 09.07.2014
comment
Да, родительский процесс продолжает работать как обычно, потому что изменился дочерний процесс. Проблема здесь в том, что у вас есть код в дочернем процессе после вызова execvp, и этот код никогда не запустится.   -  person Some programmer dude    schedule 09.07.2014
comment
Что касается того, когда звонить, например. wait или waitpid (или родственные функции), вы можете использовать SIGCHLD signal, чтобы узнать, когда завершился дочерний процесс.   -  person Some programmer dude    schedule 09.07.2014
comment
@ErikNyquist Если вы хотите отправить комментарий определенному пользователю, поставьте @ перед его именем. В противном случае они не будут уведомлены о том, что вы отправили им сообщение.   -  person Barmar    schedule 09.07.2014
comment
Что касается очистки, вы можете сделать это в родительском процессе после выхода из дочернего процесса. Вы можете использовать waitpid для ожидания любого процесса, и он возвращает pid процесса. Сохраните информацию (имена файлов, выделенную память и т. д.) в таблице вместе с pid и рабочим статусом. Как только процесс завершается, обработчик сигнала устанавливает запись в таблице для этого процесса как не работающую, а родительский процесс в своем основном цикле (или аналогичном) очищает ресурсы дочернего процесса.   -  person Some programmer dude    schedule 09.07.2014
comment
Рассмотрите возможность использования библиотечной функции system для запуска команды оболочки (там вы можете использовать перенаправление >).   -  person Per Johansson    schedule 09.07.2014
comment
@Per Johansson - это в основном учебное упражнение (никто не хочет, чтобы программа удаляла комментарии из исходных файлов, а затем создавала патч), поэтому я выберу более сложный вариант. Но спасибо!   -  person Erik Nyquist    schedule 09.07.2014


Ответы (2)


Два предложения:

  1. Поймите, что exec*() заменяет ваш процесс (в случае успеха). Любой следующий за ним код недоступен.
  2. Получите статус завершения завершенных процессов с помощью одной из функций wait*(); возможно, в обработчике сигнала для SIGCHLD.
  3. Дополнительное предложение: прочтите книгу У. Ричарда Стивенса Advanced Programming in the Unix Environment; это Библия для таких задач.
person Jens    schedule 09.07.2014
comment
Любой код, следующий за ним, недоступен -- если exec* не завершается ошибкой ;) - person P.P; 09.07.2014

Вы, вероятно, хотите, чтобы предложение else после fork также обрабатывало родительский процесс. Что-то вроде следующего может работать;

void cleanup (char * name, char * newname)
{
    if (pf)
    {
        /* SNIP - unchanged */

        if (patch_pid == 0) /* child */
        {
            FILE *pfp;
            if ((pfp = fopen(patchname, "w")) == NULL)
            {
                printf("Error opening file \"%s\" for writing.\n%s\n",
                       patchname, strerror(errno));
                exit(EXIT_FAILURE);
            }
            dup2(fileno(pfp), STDOUT_FILENO);
            fclose(pfp);
            execvp(diffargs[0], diffargs);
            perror("execvp");
            exit(EXIT_FAILURE);
        }
        else /* parent */
        {
            pid_t rc;
            int stat;

            free(patchname);

            rc = waitpid(patch_pid, &stat, 0);
            if (rc < 0)
            {
                perror("waitpid");
                /* do something appropriate here */
            }
            else
            {
                /* if you care about your children */
                if (WIFEXITED(stat))
                {
                    printf("%d exited with status %d\n",
                           (int)rc, WEXITSTATUS(stat));
                }
                else if (WIFSIGNALED(stat))
                {
                    printf("%d terminated because of signal %d\n",
                           (int)rc, WTERMSIG(stat));
                }
                else if (WIFSTOPPED(stat))
                {
                    printf("%d was STOPPED with signal %d\n",
                           (int)rc, WSTOPSIG(stat));
                }
            }
            pcount--;

            if (remove(name) != 0)
            {
                printf("Error removing file %s\n%s\n", name, strerror(errno));
                exit(EXIT_FAILURE);
            }

            if (rename(newname, name) != 0)
            {
                printf("Error renaming file %s\n%s\n", newname, strerror(errno));
                exit(EXIT_FAILURE);
            }
        }
    }
}

Я также настоятельно рекомендую прочитать Advanced Programming in the UNIX Environment покойного У. Ричарда Стивенса. На самом деле, вы также должны добавить тома 1 и 2 сетевого программирования UNIX в свой список ;)

Еще я заметил, что у вас есть ошибка управления памятью, связанная с patchname. Вызов malloc не учитывает дополнительный символ NUL в конце строки.

person D.Shawley    schedule 09.07.2014
comment
Спасибо за предложения по чтению, @Jens & D.Shawley. Для всех, кто просматривает это, я также нашел эту страницу действительно полезной (и мне, вероятно, следовало прочитать ее, прежде чем публиковать здесь...) cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_23.html - person Erik Nyquist; 09.07.2014