Tuberías, dup2 y exec()

8 minutos de lectura

avatar de usuario
Aris Kantas

Tengo que escribir un shell que pueda ejecutar tuberías. Por ejemplo, comandos como ls -l | wc -l“. He analizado con éxito el comando dado por el usuario de la siguiente manera:

“ls” = primercmd

“-l” = frsarg

“wc” = scmd

“-l” = cargar

Ahora tengo que usar dos tenedores ya que los comandos son dos y una pipa. El bloque de código que escribí para ejecutar el comando es el siguiente:

pid_t pid;
int fd[2];

pipe(fd);
pid = fork();

if(pid==0)
{        
    dup2(fd[WRITE_END], STDOUT_FILENO);
    close(fd[READ_END]);
    execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
}
else
{ 
    pid=fork();

    if(pid==0)
    {
        dup2(fd[READ_END], STDIN_FILENO);
        close(fd[WRITE_END]);
        execlp(scmd, scmd, secarg, (char*) NULL);
    }
}

Entonces, cuando ejecuto mi shell e ingreso el comando ls -l | wc -l (por ejemplo) el resultado de los execs no aparece pero el shell sigue funcionando normalmente.

Lo extraño es que el resultado del comando se muestra solo cuando termino mi shell con “exit” o “^C”.

¿Qué tiene de malo esa salida? ¿Por qué no aparece justo después de ingresar el comando?

  • Debe cerrar los FD de tubería en los procesos principales.

    – Barmar

    24 de noviembre de 2015 a las 2:19

  • ¿Puedes editar mi código para ayudarme a entender lo que quieres decir, por favor? @Barmar

    – Aris Kantas

    24 de noviembre de 2015 a las 2:22

  • Regla de oro: si duplica un extremo de una tubería a una entrada o salida estándar, debe cerrar ambos extremos de la tubería original antes de usar exec*() funciones (o continuar de otro modo). Hay excepciones; Son pocos y distantes entre sí. Es muy raro (en SO e IRL) que se encuentre con un programa que use conductos que cierren demasiados descriptores; es muy común encontrar un programa que no cierra suficientes descriptores. Es especialmente común si hay varios hijos y/o varias tuberías para confundir las cosas.

    –Jonathan Leffler

    19 mayo 2017 a las 15:07

  • Por cierto, si miras el código en github.com/jleffler/soq/tree/master/src/so-4380-8114, encontrará un ejemplo interesante en el que ‘no cerrar suficientes descriptores de archivo hizo que el programa fallara en entradas lo suficientemente grandes’. Cuando la entrada era pequeña, funcionó bien. Cuando la entrada era lo suficientemente grande, el programa se atascaba porque las tuberías no estaban cerradas correctamente. (No, el código de interferencia no está directamente en Stack Overflow).

    –Jonathan Leffler

    19 mayo 2017 a las 15:22


avatar de usuario
usuario1969104

Debe cerrar todos los descriptores de tubería tanto en el proceso principal como en el proceso secundario (después de la duplicación en el proceso secundario). En su código, el problema principal es que, el wc El proceso no finaliza porque todavía hay escritores presentes (ya que el proceso principal no ha cerrado el final de escritura). Los cambios se muestran a continuación. También he añadido el waitpid en el proceso padre para esperar el wc proceso.

pid_t pid;
int fd[2];

pipe(fd);
pid = fork();

if(pid==0)
{
    dup2(fd[WRITE_END], STDOUT_FILENO);
    close(fd[READ_END]);
    close(fd[WRITE_END]);
    execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
    fprintf(stderr, "Failed to execute '%s'\n", firstcmd);
    exit(1);
}
else
{ 
    pid=fork();

    if(pid==0)
    {
        dup2(fd[READ_END], STDIN_FILENO);
        close(fd[WRITE_END]);
        close(fd[READ_END]);
        execlp(scmd, scmd, secarg,(char*) NULL);
        fprintf(stderr, "Failed to execute '%s'\n", scmd);
        exit(1);
    }
    else
    {
        int status;
        close(fd[READ_END]);
        close(fd[WRITE_END]);
        waitpid(pid, &status, 0);
    }
}

  • Oh si. Esa es la respuesta más correcta que he visto. Funciona perfectamente. Muchas gracias. Estuve atascado con eso durante horas. @usuario1969104

    – Aris Kantas

    24 de noviembre de 2015 a las 12:47

avatar de usuario
islam

Mmmm, lo suficientemente cerca. Echas de menos manejar el cierre de algún descriptor de archivo después de la bifurcación.

Aquí hay alguna referencia:

  1. sobre pipa, http://unixwiz.net/techtips/remap-pipe-fds.html
  2. Acerca del descriptor de archivo,
    http://www.usna.edu/Users/cs/aviv/classes/ic221/s14/lec/09/lec.html

Aquí está mi código:

#include  <fcntl.h>                              //
#include  <stdio.h>                              //
#include  <stdlib.h>                             //
#include  <string.h>                             //
#include  <sys/types.h>                          //
#include  <sys/wait.h>                           //
#include  <sys/stat.h>                           //
#include  <termios.h>                            //
#include  <unistd.h>                             //
                                                 //
#define INPUT_END 1                              // INPUT_END means where the pipe takes input
#define OUTPUT_END 0                             // OUTPUT_END means where the pipe produces output
                                                 //
int main(int argc, char* argv[])                 //
{                                                //
    pid_t pid1;                                  // [STDIN -> terminal_input, STDOUT -> terminal_output]                       (of the parent process)
    pid_t pid2;                                  //
    int fd[2];                                   //
                                                 //
    pipe(fd);                                    // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input, fd[1] -> pipe_output]
    pid1 = fork();                               //
                                                 //
    if(pid1==0)                                  //
    {                                            // I am going to be the wc process (i.e. taking input from the pipe)
        close(fd[INPUT_END]);                    // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
        dup2(fd[OUTPUT_END], STDIN_FILENO);      // [STDIN -> pipe_output, STDOUT -> terminal_output, fd[1] -> pipe_output]    (of the WC process)
        close(fd[OUTPUT_END]);                   // [STDIN -> pipe_output, STDOUT -> terminal_output]                          (of the WC process)
        execlp("wc", "wc", "-l",(char*) NULL);   //
    }                                            //
    else                                         //
    {                                            //
        pid2=fork();                             //
                                                 //
        if(pid2==0)                              //
        {                                        // I am going to be the ls process (i.e. producing output to the pipe)
            close(fd[OUTPUT_END]);               // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the ls process)
            dup2(fd[INPUT_END], STDOUT_FILENO);  // [STDIN -> terminal_input, STDOUT -> pipe_input, fd[0] -> pipe_input]      (of the ls process)
            close(fd[INPUT_END]);                // [STDIN -> terminal_input, STDOUT -> pipe_input]                           (of the ls process)
            execlp("ls","ls","-l",(char*) NULL); //
        }                                        //
                                                 //
        close(fd[OUTPUT_END]);                   // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the parent process)
        close(fd[INPUT_END]);                    // [STDIN -> terminal_input, STDOUT -> terminal_output]                      (of the parent process)
        waitpid(-1, NULL, 0);                    // As the parent process - we wait for a process to die (-1) means I don't care which one - it could be either ls or wc
        waitpid(-1, NULL, 0);                    // As the parent process - we wait for the another process to die.
                                                 // At this point we can safely assume both process are completed
    }                                            //
}                                                //

  • También hay problemas en su código. Primero INPUT_END/OUTPUT_END tiene valores incorrectos según la documentación de pipe. Para solucionar este problema, ha intercambiado wc y ls comandos Finalmente, no ha cerrado los descriptores en el proceso padre.

    – usuario1969104

    24 de noviembre de 2015 a las 3:35

  • @ user1969104 Ejecuté su código y funcionó, pero encontré otro problema. Para su información, mi código (demasiado grande para escribirlo aquí) usa otros execs en caso de que el usuario dé solo “ls” o “ls-l” o “ls -l /bin”. En general comandos con máximo dos argumentos. Cuando el usuario da la orden “ls -l | wc -l” todo está bien, el resultado se muestra de inmediato, pero cuando después de ese comando solo intento “ls” o cualquier otro comando sin tubería, no pasa nada. ¿Por qué está pasando eso?

    – Aris Kantas

    24 de noviembre de 2015 a las 18:44


  • No estoy seguro de lo que quiere decir sin ver su código, pero asegúrese de que la redirección de tubería (llamar a tubería, bifurcación, dup2) ocurra solo cuando hay un segundo comando dado en el argumento. De lo contrario, si solo llamas execlp("ls","ls","-l",(char*) NULL)no hay ninguna razón por la que no haya salida.

    – usuario1969104

    25 de noviembre de 2015 a las 5:49

  • Tenga en cuenta que using namespace std; es un error de sintaxis en C a menos que tenga una colección inusual de macros definidas en la línea de comandos del compilador (ya que esas macros no se definirán en los encabezados del sistema). Por favor, no publique código C++ como respuesta a una pregunta C. También tiene varios encabezados sin usar en la lista; eso no es muy elegante. <string.h>, <sys/wait.h>, <sys/stat.h>, <termios.h>no se usan y el POSIX moderno no requiere <sys/types.h> incluirse explícitamente. Deberías al menos salir si execlp() devoluciones; Podría decirse que también debería imprimir un mensaje de error.

    –Jonathan Leffler

    19 mayo 2017 a las 15:12


avatar de usuario
Delphoenyx

Bueno, un trabajo simple y eficiente para algo como esto es hacer un script con las cosas de la tubería y luego llamar al script con algún comando exec desde su código C.

script.sh

#!/bin/sh
ls -l | wc -l

Y lo que acaba de hacer en su programa C algo como esto

char *argv[] = {"script.sh", NULL};
execv(argv[0], argv);

Tenga en cuenta que deberá tener script.sh en el mismo directorio de su programa C.

  • Sí, escriba un shell para ejecutar ese script que canaliza y luego haga que el shell se llame a sí mismo para ejecutar un script que canalice. Vea cuánto tarda en quedarse sin recursos.

    – SS Ana

    17 de marzo de 2020 a las 21:39

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad