Вы ждете завершения только одного процесса, а не завершения всех процессов. Это наверное одна проблема. Исправьте цикл на waitpid()
, пока он не вернет «детей больше нет».
Структура кода оставляет желать лучшего — это кроличьи лабиринты вложенных if; ик!
Я беспокоюсь, что вы не закрываете достаточно каналов до выполнения других команд. Вы можете быть в порядке, если команды не зависят от обнаружения EOF в канале; в противном случае вас ждет долгое ожидание.
Вам нужна такая функция, как:
#include <stdarg.h>
static void err_exit(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(EXIT_FAILURE);
}
Это упрощает обработку ошибок. Вы также можете делать такие вещи, как автоматическое добавление умирающего PID или ошибки, вызвавшей выход, если хотите.
Мы также можем создать функцию для запуска другой команды:
static pid_t run_command(const char *cmd, const char *shmarg, const char *semarg,
const char *fdarg1, const char *fdarg2)
{
pid_t pid = fork();
if (pid < 0)
err_exit("Failed to fork\n");
else if (pid == 0)
{
execl(cmd, cmd, shmarg, semarg, fdarg1, fdarg2, (char *)0);
err_exit("Failed to exec %s\n", cmd);
}
return pid;
}
Имея их, мы можем попытаться сократить ваш код до этого...
// Create child processes
pid_t pid1 = run_command("/home/tropix/hw11-2", semarg, pipe_to_p3, pipe_to_p4);
pid_t pid2 = run_command("/home/tropix/hw11-3", shmarg, semarg, pipe_from_p2, pipe_to_p5_1);
pid_t pid3 = run_command("/home/tropix/hw11-4", shmarg, semarg, pipe_from_p2_2, pipe_to_p5_2);
pid_t pid4 = run_command("/home/tropix/hw11-5", semarg, pipe_from_p3, pipe_from_p4);
Хм... у некоторых из них есть shmarg
, а у некоторых нет - это несоответствие преднамеренное или случайное? Мы предполагаем намеренно, поэтому нам нужны две версии run_command():
static pid_t run_cmd4(const char *cmd, const char *shmarg, const char *semarg,
const char *fdarg1, const char *fdarg2)
{
pid_t pid = fork();
if (pid < 0)
err_exit("Failed to fork\n");
else if (pid == 0)
{
execl(cmd, cmd, shmarg, semarg, fdarg1, fdarg2, (char *)0);
err_exit("Failed to exec %s\n", cmd);
}
return pid;
}
static pid_t run_cmd3(const char *cmd, const char *semarg,
const char *fdarg1, const char *fdarg2)
{
pid_t pid = fork();
if (pid < 0)
err_exit("Failed to fork\n");
else if (pid == 0)
{
execl(cmd, cmd, semarg, fdarg1, fdarg2, (char *)0);
err_exit("Failed to exec %s\n", cmd);
}
return pid;
}
А потом:
// Create child processes
pid_t pid1 = run_cmd3("/home/tropix/hw11-2", semarg, pipe_to_p3, pipe_to_p4);
pid_t pid2 = run_cmd4("/home/tropix/hw11-3", shmarg, semarg, pipe_from_p2, pipe_to_p5_1);
pid_t pid3 = run_cmd4("/home/tropix/hw11-4", shmarg, semarg, pipe_from_p2_2, pipe_to_p5_2);
pid_t pid4 = run_cmd3("/home/tropix/hw11-5", semarg, pipe_from_p3, pipe_from_p4);
Если бы это был мой код, имена переменных были бы более однообразными — и, вероятно, в массивах:
// Create child processes
pid_t pid1 = run_cmd3("/home/tropix/hw11-2", semarg, pipearg[0], pipearg[1]);
pid_t pid2 = run_cmd4("/home/tropix/hw11-3", shmarg, semarg, pipearg[2], pipearg[3]);
pid_t pid3 = run_cmd4("/home/tropix/hw11-4", shmarg, semarg, pipearg[4], pipearg[5]);
pid_t pid4 = run_cmd3("/home/tropix/hw11-5", semarg, pipearg[6], pipearg[7]);
Тогда, наконец, у вас есть код:
// Closing Pipes
close(pipe1[1]);
close(pipe2[1]);
close(pipe3[1]);
close(pipe4[1]);
close(pipe1[0]);
close(pipe2[0]);
close(pipe3[0]);
close(pipe4[0]);
//Wait for child process completion
while (waitpid(0, NULL, 0) != 0)
;
printf("Child Processes Complete.\n");
// Remove Semaphores and Shared Memory
semctl(sem_id,0,IPC_RMID);
shmctl(shmid, IPC_RMID, NULL);
Я глубоко подозреваю, что функции run_cmdX()
также должны закрывать большой выбор каналов - по крайней мере, каждый дескриптор каналов, не предназначенный для связи с их подпроцессом.
Организовать это аккуратно сложнее, но можно сделать с осторожностью. Я бы, вероятно, создал каналы в одном массиве:
if (pipe(&pipes[0]) != 0 || pipe(&pipes[2]) != 0 ||
pipe(&pipes[4]) != 0 || pipe(&pipes[6]) != 0)
err_exit("Failed to create a pipe\n");
Затем я бы создал функцию:
void pipe_closer(int *pipes, int close_mask)
{
for (i = 0; i < 8; i++)
{
if ((mask & (1 << i)) != 0)
close(pipes[i]);
}
}
Затем его можно вызвать, чтобы закрыть ненужные трубы:
pipe_closer(pipes, 0xFF); // Close them all - main()
pipe_closer(pipes, 0xFC); // All except 0, 1
pipe_closer(pipes, 0xF3); // All except 2, 3
pipe_closer(pipes, 0xCF); // All except 4, 5
pipe_closer(pipes, 0x3F); // All except 6, 7
Вам просто нужно организовать передачу правильной маски с каждой из функций run_cmdN()
и выполнение правильных вызовов. Если массив pipes
не является глобальным, его также необходимо передать. Я бы также посмотрел, как аккуратно кодировать данные, чтобы вызовы run_cmdN()
были как можно более регулярными и симметричными.
Керниган и Плаугер "Элементы стиля программирования" ( 2-е изд., 1978; подозреваю, что его трудно найти) содержит много великолепных цитат. Наиболее подходящим является (выделено жирным шрифтом, курсив в оригинале):
- [T]вызов подпрограммы позволяет нам суммировать неполадки в списке аргументов, где мы можем быстро увидеть, что происходит.
- Сама подпрограмма обобщает регулярности кода, поэтому нет необходимости использовать повторяющиеся шаблоны.
Это можно рассматривать как часть принципа программирования DRY (не повторяйся). Вызов функции err_exit()
инкапсулирует три или четыре строки кода — печать и выход плюс фигурные скобки, в зависимости от вашего предпочтительного макета. Функции run_command()
являются простым случаем DRY. Предлагаемый pipe_closer()
это еще один.
person
Jonathan Leffler
schedule
09.05.2011