Могу ли я форкать процессы и выполнять внутренние функции? - PullRequest
0 голосов
/ 27 августа 2018

Я действительно искал это, и все, что я могу найти, - это то, что вы можете использовать команды оболочки execvp ().

Мне интересно, могу ли я разветвлять процессы, а затем заставить их запускать функцию, которая является внутренней для программы?(Например, функция, которую я сам написал в коде)

Ответы [ 4 ]

0 голосов
/ 27 августа 2018

Я подозреваю, что самый простой ответ здесь - пример:

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

static int child_foo(void)
{
    /* This child process just counts to three. */

    int i;

    for (i = 1; i <= 3; i++) {
        printf("Foo: %d\n", i);
        fflush(stdout);
        sleep(1);
    }

    printf("Foo is done.\n");
    return EXIT_SUCCESS;
}

static int child_bar(unsigned long  n)
{
    /* This child process checks if n is prime or not. */ 
    const unsigned long  imax = (n + 1) / 2;
    unsigned long        i;

    for (i = 2; i <= imax; i++)
        if (!(n % i)) {
            printf("Bar: Because %lu is divisible by %lu, %lu is not a prime.\n", n, i, n);
            return EXIT_FAILURE;
        }

    printf("Bar: %lu is prime.\n", n);
    return EXIT_SUCCESS;
}

int main(void)
{
    pid_t  p, foo, bar;
    int    status;

    printf("Forking child processes.\n");
    fflush(stdout);

    foo = fork();
    if (foo == -1) {
        fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    } else
    if (!foo)
        return child_foo();

    bar = fork();
    if (bar == -1) {
        fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
        /* Wait until all child processes (here, foo only) have completed. */
        do {
            p = wait(NULL);
        } while (p != -1 || errno == EINTR);
        return EXIT_FAILURE;
    } else
    if (!bar)
        return child_bar(227869319);

    /* Wait until all child processes have completed. */
    do {
        p = wait(&status);
        if (p == foo || p == bar) {
            /* Report exit status. */
            if (p == foo)
                printf("child_foo()");
            else
                printf("child_bar()");
            if (WIFEXITED(status)) {
                if (WEXITSTATUS(status) == EXIT_SUCCESS)
                    printf(" exited successfully (EXIT_SUCCESS).\n");
                else
                if (WEXITSTATUS(status) == EXIT_FAILURE)
                    printf(" exited with failure (EXIT_FAILURE).\n");
                else
                    printf(" exited with status %d.\n", WEXITSTATUS(status));
            } else
            if (WIFSIGNALED(status))
                printf(" died from signal %d (%s).\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
            else
                printf(" was lost.\n");
            fflush(stdout);
        }
    } while (p != -1 || errno == EINTR);

    printf("All done.\n");
    return EXIT_SUCCESS;
}

Выше main() разветвляет два дочерних процесса.Один из них запускает child_foo() и выходит, другой запускает child_bar(227869319) и выходит.Родительский процесс пожинает все дочерние процессы и возвращает причину состояния выхода и выхода, если таковой имеется, а затем завершает сам себя.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * fflush(stdout) * * *, чтобы указать внутренние кэши (включаякэширование, выполняемое библиотекой C) должно быть сброшено перед разветвлением, иначе дочерний процесс унаследует содержимое кэша.В случае stdout это будет означать дублирование выходных данных.

На практике для удобства сопровождения (понимание программиста-человека) часто лучше "вернуться" из дочерних функций процесса, используя exit(EXIT_SUCCESS) или exit(EXIT_FAILURE), чем return.Тогда в main() вместо, скажем, return child_foo(), вы бы сказали:

        child_foo(); /* Never returns. */
        exit(EXIT_FAILURE);

, где exit(EXIT_FAILURE) - это просто ловушка для ошибок, если модификация child_foo() заставляет еговозврат, а не выход.

В некоторых случаях шаблон возврата полезен, если есть работа по очистке, которая всегда должна выполняться до завершения дочернего процесса.В этом случае вы обычно помещаете эту очистку в отдельную функцию, а затем заменяете return child_foo() на

        int  exitstatus;

        exitstatus = child_foo();
        cleanup_function();
        exit(exitstatus);

Обратите внимание, что в main(), return exitstatus; и exit(exitstatus); в точности эквивалентны (C892.1.2.2, C99 5.1.2.2.3p1, C11 5.1.2.2.3p1).Единственная разница для нас, людей; может нам, людям, легче правильно интерпретировать намерение, стоящее за exit(exitstatus) в середине main(), по сравнению с return exitstatus;.Сам я "разбираю" их так же, но я, кажется, использую return more.

Функция strsignal() определена в POSIX-1.2008 и вызывается только если выудается убить один из дочерних процессов внешним сигналом, не убивая также и родительский процесс.Если ваша библиотека C не поддерживает ее, просто удалите ее (также удалите также спецификатор %s printf).

Как обычно, вторая по важности вещь состоит в том, чтобы гарантировать, что будущие разработчики понимают намерениеправильно.Я включил довольно минимальные комментарии, но и имена функций, и начальный комментарий в дочерних функциях процесса должны прояснить цель.Я уверен, что вы можете сделать еще лучше.(Я считаю комментирование самой сложной частью программирования для меня. Очень легко писать бесполезные комментарии, которые описывают, что делает код, но опускают intent . Когда вы смотрите даже на собственные месяцы кодапозже вы забываете о своих мыслях и должны полагаться на комментарии (и косвенно делать выводы из кода), чтобы получить правильное понимание намерений. Хорошие комментарии сложно написать!)

По моему мнению,самое главное, чтобы программа работала надежно и не испортила данные.Это означает достаточную проверку ошибок (в моем случае, параноик - я действительно не хочу молча повреждать данные) и минимальные предположения о том, что "должно" получиться.Иногда это приводит к «гнездам» кода, например проверкам status в цикле wait() в main().Я намеренно оставил там комментарии, потому что считаю, что вы должны пройти через них, оставив открытым окно браузера или терминала в man 2 wait, и добавить необходимые комментарии, чтобы вы точно понимали, что делает это гнездо кода,Это определенно поможет вам понять, как процессы могут завершаться или прерываться, и как родительский процесс может это обнаружить.

0 голосов
/ 27 августа 2018

Вы fork a процесс ( работает , активен, экземпляр программы) и вы execve an исполняемый файл (пассивный файл, обычно в формате ELF ) - не некоторые функции.Прочитайте еще раз fork (2) и execve (2) и учетные данные (7) .См. Также Операционные системы: три простых компонента , чтобы лучше понять роль ОС.

, с которой вы можете выполнять команды оболочки execvp ().

Неверно. execvp вызывает execve и запускает исполняемые файлы (не команды оболочки; например, вы не можете execvp встроенная оболочка cd).Он не использует оболочку (но execvp ищет вашу PATH переменную , как ваша оболочка).

Обратите внимание, что у каждого процесса есть собственное виртуальное адресное пространство fork создает новый процесс со своим собственным новым виртуальным адресным пространством (которое является копией виртуального адресного пространства родителя. Эта копия выполняется лениво, читайте о copy-on-пишите методов).execve - это замена процесса виртуального адреса на новый (описанный в исполняемом файле).

С этого нового виртуального адресного пространства (после успешного fork, в дочернем элементеprocess) является копией родительской, она также содержит код каждой внутренней функции, о которой вы мечтали из вашей программы.Поэтому после fork вы можете вызывать эти внутренние функции.

Используйте pmap (1) и proc (5) , чтобы понять виртуальное адресное пространство процессов.Сначала запустите cat /proc/$$/maps, затем pmap $$ в терминале и попытайтесь понять его вывод (он описывает виртуальное адресное пространство вашего процесса оболочки).

Когда процесс запущен, вы можете расширить еговиртуальное адресное пространство с использованием mmap (2) .Это используется malloc, а также dlopen (3) (что позволяет загружать плагинов в ваш процесс).

PS.Я предполагаю, что вы используете Linux.

0 голосов
/ 27 августа 2018

Когда вы вызываете fork, создается новый процесс, наследующий текущий контекст родительского процесса.Дочерний и родительский процессы могут выполняться независимо, вызывая любую функцию в вашей программе.Однако, если им нужно общаться / синхронизироваться друг с другом, им необходимо использовать один из механизмов IPC, например, общую память, каналы, семафоры и т. Д.

0 голосов
/ 27 августа 2018

Конечно, вы можете сделать так, чтобы дочерний элемент выполнял одну функцию, а родительский выполнял другую (или даже одну и ту же) функцию в том же исполняемом файле.

pid_t pid = fork();
if (pid < 0)
    err_syserr("failed to fork: ");
else if (pid == 0)
    be_childish();
else
    be_parental();

Вы можете добавить аргументы к be_childish() и be_parental() по мере необходимости.Перед выполнением кода fork() вы можете создать каналы или сокеты для связи между ними - или семафорами, и общей памятью, или любым другим IPC, который вы хотите.

...