Поскольку вы будете создавать много дочерних процессов, лучше всего начать с создания функции, которая создает дочерний процесс и выполняет его, указанный вызывающей стороной.Предположим, что и идентификационный номер, и оценка int
с.Затем
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Run func(id, grade) in a child process.
Returns the child process PID if success,
or -1 with errno set in case an error occurs.
*/
pid_t run_child(int id, int grade,
int (*func)(int id, int grade))
{
pid_t p;
p = fork();
if (p == -1) {
/* fork() failed; it set errno to indicate the error. */
return -1;
} else
if (!p) {
/* Run child process function. When it returns,
have the child exit with that exit status. */
exit(func(id, grade));
} else {
/* Parent process. p is positive. */
return p;
}
}
Обратите внимание, что третий параметр является указателем на функцию.Мы указываем это, используя имя функции.Эта функция должна принимать два параметра int
(идентификатор и оценку соответственно) и возвращать int
.Например:
/* Each child process runs this function.
*/
int child_process(int id, int grade)
{
printf("Child: id = %d, grade = %d, PID = %d.\n", id, grade, (int)getpid());
return EXIT_SUCCESS;
}
Мы можем создать дочерний процесс, который выполняет эту функцию, используя child_pid = run_child(123456, 5, child_process);
.Обратите внимание, как имя функции можно использовать для указания указателя функции.Стандартная функция C qsort()
использует точно такой же механизм, который позволяет быстро сортировать все;вызывающей стороне просто нужно указать функцию, которая может сравнивать два элемента в массиве для сортировки.
Мы будем создавать несколько дочерних элементов и пожинать их сразу.Это означает, что имеет смысл написать функцию, которая собирает все дочерние процессы, по существу блокируя до тех пор, пока все они не завершатся.Скорее всего, нас интересуют состояния выхода по крайней мере для некоторых из них, поэтому давайте передадим в качестве параметров интересные идентификаторы дочерних процессов, целочисленные значения для сохранения состояния и количество процессов в этих массивах:
/* Reap all child processes.
If child_count > 0, child processes with PID in child_pid[]
will have child_pid[] negated when reaped, with exit status saved
in child_status.
The function returns the number of child processes reaped.
*/
size_t reap_children(pid_t *child_pid, int *child_status, size_t child_count)
{
size_t reaped = 0;
size_t i;
int status;
pid_t p;
while (1) {
/* Reap a child process, if any. */
p = wait(&status);
if (p == -1) {
/* errno == EINTR is not an error; it occurs when a
signal is delivered to a hander installed without
SA_RESTART flag. This will not occur in this program,
but it is good practice to handle that case gracefully. */
if (errno == EINTR)
continue;
/* errno set by wait(). */
return reaped;
}
/* Another child process was reaped. */
reaped++;
/* If the reaped child was one of the interesting ones,
negate its pid and save the exit status. */
for (i = 0; i < child_count; i++) {
if (child_pid[i] == p) {
child_pid[i] = -p;
child_status[i] = status;
break;
}
}
}
}
Обратите внимание, что p = wait(&status)
пожинает дочерний процесс.Это означает, что если один или несколько дочерних процессов уже завершили работу, он выбирает один из них и возвращает свой PID со статусом выхода, сохраненным в &status
.Если все оставшиеся дочерние процессы все еще работают, вызов будет ждать, пока не завершится хотя бы один из них.Если дочерних процессов больше нет, возвращается -1
с errno
, установленным на ECHILD
.
Если использовались обработчики сигналов, wait()
также может возвращать -1
с errno
, установленным наEINTR
, если сигнал был доставлен в обработчик сигнала, который был установлен без флага SA_RESTART
с sigaction()
.Многие программисты отказываются от этой проверки (потому что «она никогда не произойдет»), но я действительно хотел бы включить эту проверку, потому что она проста и гарантирует, что добавление обработки кода в мой код не укусит меня в задницу позже.Я тоже очень часто это делаю.(Я имею в виду обработку сигналов.)
Причина, по которой мы отменяем pids при получении соответствующего дочернего процесса, проста: она позволяет нам легко определить, какие дочерние процессы были получены.(POSIX говорит, что все идентификаторы процессов являются положительными, а pid_t
является типом со знаком. Отрицание PID также является широко используемой техникой; просто посмотрите, например, waitpid()
.)
Еслимы хотели получить определенный дочерний процесс, мы бы использовали waitpid()
.Например,
pid_t child, p; /* wait for 'child'. */
int status;
do {
p = waitpid(child, &status, 0);
if (p == -1) {
if (errno == EINTR)
continue;
break;
}
} while (p != child);
if (p == child) {
/* Reaped 'child', status in 'status'. */
} else {
/* Error: failed to reap 'child'. See 'strerror(errno)'. */
}
Обратите внимание, что в терминологии POSIX / Unix «дочерний процесс» относится только к процессам, созданным только этим процессом;не "внуки", процессы, созданные дочерними процессами.
Я предпочитаю писать свои процессы, чтобы получать параметры из командной строки.Если параметры не указаны или указаны -h
или --help
, отображается краткая справка («использование»);это очень часто встречается в инструментах командной строки POSIX и Unix и поэтому очень интуитивно понятно.
Следующий main()
принимает один или несколько ID:grade
в качестве параметров командной строки.Для каждого из них создается дочерний процесс, и он запускает функцию child_process()
с указанным идентификатором и оценкой.Затем основная программа пожнет их все и опишет состояние завершения каждого дочернего процесса.
int main(int argc, char *argv[])
{
pid_t child_pid[argc];
int child_status[argc];
int count, i, n, arg, id, grade, status;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s ID:GRADE [ ID:GRADE ]*\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
status = EXIT_SUCCESS;
count = 0;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], "%d:%d %c", &id, &grade, &dummy) == 2) {
child_pid[count] = run_child(id, grade, child_process);
if (child_pid[count] == -1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
status = EXIT_FAILURE;
} else
count++;
} else {
fprintf(stderr, "%s: Not a valid ID:GRADE specification.\n", argv[arg]);
status = EXIT_FAILURE;
}
}
if (count < 0) {
fprintf(stderr, "No running child processes.\n");
return EXIT_FAILURE;
}
n = reap_children(child_pid, child_status, count);
printf("Reaped %d child processes.\n", n);
for (i = 0; i < count; i++) {
if (child_pid[i] < 0) {
printf("Child process %d (%d of %d)", (int)(-child_pid[i]), i + 1, count);
if (WIFEXITED(child_status[i])) {
if (WEXITSTATUS(child_status[i]) == EXIT_SUCCESS)
printf(" exited with success (EXIT_SUCCESS), %d.\n", EXIT_SUCCESS);
else
if (WEXITSTATUS(child_status[i]) == EXIT_FAILURE)
printf(" exited with failure (EXIT_FAILURE), %d.\n", EXIT_FAILURE);
else
printf(" exited with status %d.\n", WEXITSTATUS(child_status[i]));
} else
if (WIFSIGNALED(child_status[i])) {
printf(" died from signal %d.\n", WTERMSIG(child_status[i]));
} else {
printf(" died from unknown causes.\n");
}
} else {
printf("Child process %d (%d of %d) was lost!\n", (int)child_pid[i], i + 1, count);
}
}
return status;
}
Если вы сохраните вышеприведенное как example.c , вы можете скомпилировать его в пример с использованием, например,
gcc -Wall -O2 example.c -o example
Если вы затем выполните, скажем,
./example 100001:1 100002:5 100003:3 21532:4
, результат будет примерно таким:
Child: id = 100002, grade = 5, PID = 1260.
Child: id = 100001, grade = 1, PID = 1259.
Child: id = 100003, grade = 3, PID = 1261.
Child: id = 21532, grade = 4, PID = 1262.
Reaped 4 child processes.
Child process 1259 (1 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1260 (2 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1261 (3 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1262 (4 of 4) exited with success (EXIT_SUCCESS), 0.
Обратите внимание, что начальныйChild:
строки могут быть в любом порядке, потому что дочерние процессы выполняются по существу параллельно.Каждый дочерний процесс запускается сразу после его запуска, поэтому этот пример не является ответом копирования и вставки на требования OP.
Если вы хотите экспериментировать со сложными иерархиями процессов, я рекомендую использовать Graphvizвизуализировать их.Например, dot-kids.c :
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static void reap_all(void)
{
pid_t p;
int status;
while (1) {
p = wait(&status);
if (p == -1) {
if (errno == EINTR)
continue;
if (errno == ECHILD)
return;
fprintf(stderr, "Process %d: reap_all(): %s.\n", (int)getpid(), strerror(errno));
return;
}
printf(" \"%d\" -> \"%d\" [ color=\"#ff0000\" ];\n", (int)p, (int)getpid());
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS)
printf(" \"%d\" [ label=\"%d\" ];\n", (int)p, (int)p);
else
printf(" \"%d\" [ label=\"%d (exit %d)\" ];\n", (int)p, (int)p, WEXITSTATUS(status));
} else
if (WIFSIGNALED(status))
printf(" \"%d\" [ label=\"%d (signal %d)\" ];\n", (int)p, (int)p, WTERMSIG(status));
else
printf(" \"%d\" [ label=\"%d (lost)\" ];\n", (int)p, (int)p);
fflush(stdout);
}
}
static pid_t run_child(int (*child)(int depth, int width), int depth, int width)
{
pid_t p;
fflush(stdout);
fflush(stderr);
p = fork();
if (p == -1) {
fprintf(stderr, "Process %d: Cannot fork: %s.\n", (int)getpid(), strerror(errno));
return -1;
} else
if (!p) {
exit(child(depth, width));
} else {
printf(" \"%d\" -> \"%d\" [ color=\"#0000ff\" ];\n", (int)getpid(), (int)p);
fflush(stdout);
return p;
}
}
int child(int depth, int width)
{
if (depth > 0) {
while (width > 0)
run_child(child, depth - 1, width--);
reap_all();
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
int depth, width, i;
char dummy;
if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[2], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s depth width | dot -Tx11\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (sscanf(argv[1], " %d %c", &depth, &dummy) != 1 || depth < 0) {
fprintf(stderr, "%s: Invalid depth.\n", argv[1]);
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %d %c", &width, &dummy) != 1 || width < 1) {
fprintf(stderr, "%s: Invalid width.\n", argv[2]);
return EXIT_FAILURE;
}
printf("digraph {\n");
printf(" \"%d\" [ shape=\"box\", label=\"%d\" ];\n", (int)getpid(), (int)getpid());
fflush(stdout);
for (i = 0; i < width; i++)
run_child(child, depth, width - 1);
reap_all();
printf("}\n");
return EXIT_SUCCESS;
}
Скомпилируйте его, например, с помощью
gcc -Wall -O2 dot-kids.c -o dot-kids
и запустите, используя, например,
./dot-kids 1 3 | dot -Tx11
, чтобы увидеть график процесса, аналогичный , где номера - это идентификаторы процессов, синие стрелки показывают, какиекакой процесс создан, а красные стрелки показывают, какой процесс какой был запущен.