Я подозреваю, что самый простой ответ здесь - пример:
#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
, и добавить необходимые комментарии, чтобы вы точно понимали, что делает это гнездо кода,Это определенно поможет вам понять, как процессы могут завершаться или прерываться, и как родительский процесс может это обнаружить.