forking, проблемы с ожиданием в c

По какой-то причине этот код немедленно выполняет родительские команды, завершая работу моих семафоров и портя управление потоком других программ. Кто-нибудь может сказать мне, почему не работает waitpid()?

    //Create child processes
pid = fork();
if(pid < 0){
    fprintf(stderr, "Fork Failed.\n");
    exit(1);
    return;
}else if(pid==0){
        if(execl("/home/tropix/hw11-2","/home/tropix/hw11-2",semarg,pipe_to_p3,pipe_to_p4,(char*)0)){
            fprintf(stderr, "File Exexecution of hw11-2 failed.\n");
            exit(1);
        }
} else {
    pid = fork();
    if(pid < 0){
        fprintf(stderr, "Fork Failed.\n");
        exit(1);
        return;
    } else if(pid==0){
        if(execl("/home/tropix/hw11-3","/home/tropix/hw11-3",shmarg,semarg,pipe_from_p2,pipe_to_p5_1, (char*)0)){
            fprintf(stderr, "File Execution of hw11-3 failed.\n");
            exit(1);
        }
    } else {
        pid = fork();
        if(pid < 0){
            fprintf(stderr, "Fork Failed.\n");
            exit(1);
            return;
        } else if (pid == 0){
            if(execl("/home/tropix/hw11-4","/home/tropix/hw11-4",shmarg,semarg,pipe_from_p2_2,pipe_to_p5_2, (char*)0)){
                fprintf(stderr, "File Execution of hw11-4 failed.\n");
                exit(1);
            }
        } else {
            pid = fork();
            if(pid < 0){
                fprintf(stderr, "Fork Failed.\n");
                exit(1);
                return;
            } else if (pid == 0){
                if(execl("/home/tropix/hw11-5","/home/tropix/hw11-5",semarg,pipe_from_p3,pipe_from_p4,(char*)0)){
                    fprintf(stderr, "File Execution of hw11-5 failed.\n");
                    exit(1);
                }
            } else if (pid > 0) {
            }
        }

    }

    //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 completetion
    waitpid(pid,NULL,0);
    printf("Child Processes Complete.\n");

    //Terminate Semaphores
    semctl(sem_id,0,IPC_RMID);

    //Terminate Shared Memory Segement
    shmctl(shmid, IPC_RMID, NULL);



}

}

Спасибо!

РЕДАКТИРОВАТЬ: Хорошо, я заменил waitpid на:

while (pid = waitpid(-1, NULL, 0)) {
       if (errno == ECHILD) {
          break;
       }
    }

и это дало мне часть пути туда. Он не выполняет родительский контроль немедленно, но, похоже, он никогда не выполняется сейчас. Что касается проблемы канала, о которой вы говорили, программа 1 (эта) должна завершать все элементы IPC, включая каналы. Если есть лучший способ, я хотел бы услышать его.

Спасибо @Jonathan


person Josh    schedule 09.05.2011    source источник


Ответы (1)


Вы ждете завершения только одного процесса, а не завершения всех процессов. Это наверное одна проблема. Исправьте цикл на 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
comment
лучший ответ, который я когда-либо получал. Вы, сэр, профессионал, и я ценю ваше время!!! - person Josh; 09.05.2011