GNU/Linux >> Tutoriales Linux >  >> Linux

Implementación de múltiples tuberías en C

Creo que el problema aquí es que estás esperando y cerrándote dentro del mismo ciclo que está creando niños. En la primera iteración, el hijo ejecutará (lo que destruirá el programa hijo, sobrescribiéndolo con su primer comando) y luego el padre cerrará todos sus descriptores de archivo y esperará a que el hijo termine antes de iterar para crear el siguiente hijo. . En ese momento, dado que el padre ha cerrado todas sus tuberías, los demás hijos no tendrán nada para escribir o leer. Dado que no está comprobando el éxito de sus llamadas dup2, esto pasa desapercibido.

Si desea mantener la misma estructura de bucle, deberá asegurarse de que el padre solo cierre los descriptores de archivo que ya se han utilizado, pero deje solos los que no lo han hecho. Luego, después de que se hayan creado todos los niños, su padre puede esperar.

EDITAR :confundí el padre/hijo en mi respuesta, pero el razonamiento sigue siendo válido:el proceso que se bifurca nuevamente cierra todas sus copias de las canalizaciones, por lo que cualquier proceso después de la primera bifurcación no tendrá descriptores de archivo válidos para leer a/escribir desde.

pseudocódigo, utilizando una serie de conductos creados por adelantado:

/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
    if( pipe(pipefds + i*2) < 0 ){
        perror and exit
    }
}

commandc = 0
while( command ){
    pid = fork()
    if( pid == 0 ){
        /* child gets input from the previous command,
            if it's not the first command */
        if( not first command ){
            if( dup2(pipefds[(commandc-1)*2], 0) < ){
                perror and exit
            }
        }
        /* child outputs to next command, if it's not
            the last command */
        if( not last command ){
            if( dup2(pipefds[commandc*2+1], 1) < 0 ){
                perror and exit
            }
        }
        close all pipe-fds
        execvp
        perror and exit
    } else if( pid < 0 ){
        perror and exit
    }
    cmd = cmd->next
    commandc++
}

/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
    close( pipefds[i] );
}

En este código, el proceso principal original crea un elemento secundario para cada comando y, por lo tanto, sobrevive a toda la prueba. Los niños verifican si deben obtener su entrada del comando anterior y si deben enviar su salida al siguiente comando. Luego cierran todas sus copias de los descriptores de archivos de tubería y luego ejecutan. El padre no hace nada más que bifurcar hasta que crea un hijo para cada comando. Luego cierra todas sus copias de los descriptores y puede continuar esperando.

Crear todas las tuberías que necesita primero y luego administrarlas en el ciclo es complicado y requiere algo de aritmética de matriz. Sin embargo, el objetivo se ve así:

cmd0    cmd1   cmd2   cmd3   cmd4
   pipe0   pipe1  pipe2  pipe3
   [0,1]   [2,3]  [4,5]  [6,7]

Darse cuenta de que, en un momento dado, solo necesita dos conjuntos de tuberías (la tubería al comando anterior y la tubería al siguiente comando) simplificará su código y lo hará un poco más robusto. Ephement proporciona un pseudocódigo para esto aquí. Su código es más limpio, porque el padre y el hijo no tienen que hacer bucles innecesarios para cerrar descriptores de archivo innecesarios y porque el padre puede cerrar fácilmente sus copias de los descriptores de archivo inmediatamente después de la bifurcación.

Como nota al margen:siempre debe verificar los valores de retorno de pipe, dup2, fork y exec.

EDITAR 2 :error tipográfico en pseudocódigo. OP:num-pipes sería el número de tuberías. Por ejemplo, "ls | grep foo | sort -r" tendría 2 conductos.


Aquí está el código de funcionamiento correcto

void runPipedCommands(cmdLine* command, char* userInput) {
    int numPipes = countPipes(userInput);


    int status;
    int i = 0;
    pid_t pid;

    int pipefds[2*numPipes];

    for(i = 0; i < (numPipes); i++){
        if(pipe(pipefds + i*2) < 0) {
            perror("couldn't pipe");
            exit(EXIT_FAILURE);
        }
    }


    int j = 0;
    while(command) {
        pid = fork();
        if(pid == 0) {

            //if not last command
            if(command->next){
                if(dup2(pipefds[j + 1], 1) < 0){
                    perror("dup2");
                    exit(EXIT_FAILURE);
                }
            }

            //if not first command&& j!= 2*numPipes
            if(j != 0 ){
                if(dup2(pipefds[j-2], 0) < 0){
                    perror(" dup2");///j-2 0 j+1 1
                    exit(EXIT_FAILURE);

                }
            }


            for(i = 0; i < 2*numPipes; i++){
                    close(pipefds[i]);
            }

            if( execvp(*command->arguments, command->arguments) < 0 ){
                    perror(*command->arguments);
                    exit(EXIT_FAILURE);
            }
        } else if(pid < 0){
            perror("error");
            exit(EXIT_FAILURE);
        }

        command = command->next;
        j+=2;
    }
    /**Parent closes the pipes and wait for children*/

    for(i = 0; i < 2 * numPipes; i++){
        close(pipefds[i]);
    }

    for(i = 0; i < numPipes + 1; i++)
        wait(&status);
}

El código relevante (abreviado) es:

    if(fork() == 0){
            // do child stuff here
            ....
    }
    else{
            // do parent stuff here
            if(command != NULL)
                command = command->next;

            j += 2;
            for(i = 0; i < (numPipes ); i++){
               close(pipefds[i]);
            }
           while(waitpid(0,0,0) < 0);
    }

Lo que significa que el proceso principal (de control) hace esto:

  • tenedor
  • cerrar todas las tuberías
  • esperar el proceso secundario
  • siguiente bucle/hijo

Pero debería ser algo como esto:

  • tenedor
  • tenedor
  • tenedor
  • cierre todas las tuberías (todo debería haber sido engañado ahora)
  • esperar a los niños

Linux
  1. Múltiples comandos en Sshpass?

  2. Dd:¿Múltiples archivos de entrada?

  3. Tuberías, dup2 y exec()

  4. ¿Guardar múltiples diseños de terminales de gnomos?

  5. Dividir imagen en varias páginas

implementación del operador sizeof

Ejecute múltiples scripts de python al mismo tiempo

Cortar páginas de un PDF en varias páginas

systemd - Dando a mi servicio múltiples argumentos

Dividir una imagen dd en varios archivos

Cola múltiples archivos remotos