2012-02-13 12 views
7

El programa corto a continuación tiene la intención de iterar sobre argv pasado desde la línea de comando y ejecutar cada argumento. Esta no es mi tarea, sino que es algo que estoy haciendo en preparación para hacer mi tarea.¿Entiendo cómo funcionan los descriptores de archivos Unix en C?

El primer argumento obtiene la entrada de STDIN y STDOUT, y escribe en una tubería. Al final de cada iteración (excepto la última), los descriptores de archivo se intercambian, de modo que la tubería escrita por el último ejecutivo se leerá a partir de la siguiente. De esta manera me propongo, por ejemplo, para

./a.out /bin/pwd /usr/bin/wc 

imprimir solo la longitud del directorio de trabajo. Código sigue

#include <stdio.h>                
#include <unistd.h>                
#include <sys/types.h>               
#include <stdlib.h>                
#include <string.h>                

main(int argc, char * argv[]) {             

    int i; 
    int left[2], right[2], nbytes; /* arrays for file descriptors */ 

    /* pointers for swapping */ 
    int (* temp); 
    int (* leftPipe) = left;     
    int (* rightPipe) = right; 

    pid_t childpid;                
    char readbuffer[80];               

    /* for the first iteration, leftPipe is STDIN */ 
    leftPipe[0] = STDIN_FILENO; 
    leftPipe[1] = STDOUT_FILENO; 

    for (i = 1; i < argc; i++) {             

    /* reopen the right pipe (is this necessary?) */ 
    pipe(rightPipe);                
    fprintf(stderr, "%d: %s\n", i, argv[i]); 
    fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);                      
    if ((childpid = fork()) == -1) {            
     perror("fork");               
     exit(1);                 
    }                   

    if (childpid == 0) {               

     /* read input from the left */            
     close(leftPipe[1]); /* close output */          
     dup2(leftPipe[0], STDIN_FILENO);           
     close(leftPipe[0]); /* is this necessary? A tutorial seemed to be doing this */ 

     /* write output to the right */           
     close(rightPipe[0]); /* close input */          
     dup2(rightPipe[1], STDOUT_FILENO);           
     close(rightPipe[1]);              

     execl(argv[i], argv[i], NULL);            
     exit(0);                 
    }                   

    wait();                  

    /* on all but the last iteration, swap the pipes */ 
    if (i + 1 < argc) {    

     /* swap the pipes */              
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
     temp = leftPipe;               
     leftPipe = rightPipe;              
     rightPipe = temp;               
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    }                   
    }                    

    /* read what was last written to the right pipe */       
    close(rightPipe[1]); /* the receiving process closes 1 */     

    nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));  
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer);         

    return 0;                  
} 

ACTUALIZACIÓN: en todos los casos de prueba a continuación que había utilizado originalmente/bin/wc, pero que WC reveiled que el inodoro no es en absoluto en el que pensé. Estoy en el proceso de modificar los resultados.

La salida en un caso trivial (./a.out/bin/pwd) es como se esperaba:

1: /bin/pwd 
Received string: /home/zeigfreid/Works/programmatical/Langara/spring_2012/OS/labs/lab02/play 

La salida de la ejecución de este programa con el primer ejemplo (./a.out/bin/pwd/usr/bin/wc):

1: /bin/pwd 
0 1 3 4 
3 4 0 1 
2: /bin/wc 

en ese punto, se bloquea el terminales (tal vez esperando en la entrada).

Como puede ver, la secuencia no se está recibiendo. Lo que imagino es que he hecho algo mal arriba, ya sea al intercambiar punteros, o que no entiendo los descriptores de archivos de Unix. Mi tarea, al final, será interpretar tubos arbitrariamente largos, y esta es una de las ideas que tuve para resolver el problema. Tengo problemas para juzgar si estoy en el camino correcto de ladrar en un árbol. ¿Comprendo los descriptores de archivos de Unix?

ACTUALIZACIÓN:

Correr con/bin/ls como el segundo argumento, que tiene el siguiente resultado (los números son los descriptores de fichero en varios puntos):

1: /bin/pwd 
0 1 3 4 
0 1 3 4 
3 4 0 1 
2: /bin/ls 
3 4 5 6 
Received string: a.out 
log 
pipe2.c 
play.c 
@ 

Hay Todavía hay algo de basura al final, pero ahora estoy más preocupado porque no entiendo los indicadores. Sin embargo, estos dos comandos son independientes entre sí, realmente no hacen uso de la tubería.

ACTUALIZACIÓN: el carácter de basura fue por no cerrar la cadena. Ahora lo cierro, y no hay basura.

+0

me gustaría sugerir cambiar toda su 'printf (...)' 'pone en fprintf (stderr , ...) '. Mezclar IO estándar ('printf (3)') con las rutinas de nivel inferior ('pipe (2)', 'dup2 (2)', 'close (2)') es más problemático de lo que vale. – sarnold

+0

¡Debidamente anotado! Me imagino que la férula estaría de acuerdo. – Ziggy

+0

No termina la cadena antes de imprimirla, esto explica la basura. Pruebe 'readbytes [nbytes] = 0' después de' leer'. –

Respuesta

2

El ahorcamiento es causado por el hecho de que el extremo de escritura de la tubería "derecha" no se cierra correctamente en el proceso principal después del horquillado. Debido a esto, wc nunca dejará de leer (¡después de todo, el proceso principal aún podría escribir cosas en la tubería!).Solo deja de leer después de que se hayan cerrado todas las copias del descriptor de archivo del final de escritura.

Aquí está una versión fija:

#include <stdio.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <stdlib.h> 
#include <string.h> 

int main(int argc, char * argv[]) 
{ 
    int i; 
    int left[2], right[2], nbytes; /* arrays for file descriptors */ 

    /* pointers for swapping */ 
    int (* temp); 
    int (* leftPipe) = left; 
    int (* rightPipe) = right; 

    pid_t childpid; 
    char readbuffer[80]; 

    leftPipe[0] = STDIN_FILENO; 
    // no need to assign leftPipe[1] here, it will not be used 

    for (i = 1; i < argc; i++) { 
    pipe(rightPipe); // create new pipe 

    fprintf(stderr, "%d: %s\n", i, argv[i]); 
    fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    if ((childpid = fork()) == -1) { 
     perror("fork"); 
     exit(1); 
    } 

    if (childpid == 0) { 
     // use the reading end of the left pipe as STDIN 
     dup2(leftPipe[0], STDIN_FILENO); 
     // use the writing end of the right pipe as STDOUT 
     dup2(rightPipe[1], STDOUT_FILENO); 
     // close reading end of the right pipe 
     close(rightPipe[0]); 
     execl(argv[i], argv[i], NULL); 
     exit(0); 
    } 
    // IMPORTANT!! close writing end of the right pipe, otherwise 
    // the program will hang (this is the main bug in your original 
    // implementation) 
    close(rightPipe[1]); 

    // wait properly! 
    waitpid(childpid, NULL, 0); 

    /* on all but the last iteration, swap */ 
    if (i + 1 < argc) { 
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
     temp = leftPipe; 
     leftPipe = rightPipe; 
     rightPipe = temp; 
     fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]); 
    } 
    } 

    nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer)); 
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer); 

    return 0; 
} 

Salida:

>> ./a.out /bin/ls /bin/cat /usr/bin/wc 
1: /bin/ls 
0 32767 3 4 
0 32767 3 4 
3 4 0 32767 
2: /bin/cat 
3 4 4 5 
3 4 4 5 
4 5 3 4 
3: /usr/bin/wc 
4 5 5 6 
Received string:  266  294 4280 

Si usted tiene preguntas específicas acerca de esta solución, por favor hágamelo saber :) Hay también algunos otros problemas menores con su código original:

  • usando punteros es innecesario , Sólo puede copiar alrededor de las tuberías (rendimiento seguramente no será un problema;)
  • int se utiliza en lugar de size_t
  • que no fijó la única advertencia que se presentaría a la hora de compilar con la bandera -Wall

Si está interesado, así es como yo lo hubiera escrito:

#include <stdio.h> 
#include <unistd.h> 
#include <sys/wait.h> 
#include <stdlib.h> 
#include <string.h> 

int main(int argc, char **argv) { 
    size_t i, nbytes; 
    int left[2], right[2], tmp[2]; 
    pid_t childpid; 
    char readbuffer[80]; 

    left[0] = STDIN_FILENO; 

    for (i = 1; i < argc; ++i) { 
    pipe(right); 

    switch ((childpid = fork())) { 
     case -1: 
     perror("fork"); 
     exit(1); 
     case 0: 
     dup2(left[0], STDIN_FILENO); 
     dup2(right[1], STDOUT_FILENO); 
     close(right[0]); 
     execl(argv[i], argv[i], NULL); 
     default: 
     close(right[1]); 
     waitpid(childpid, NULL, 0); 
    } 

    if (i == argc - 1) break; 
    memcpy(tmp, left, sizeof tmp); 
    memcpy(left, right, sizeof left); 
    memcpy(right, tmp, sizeof right); 
    } 

    nbytes = read(right[0], readbuffer, sizeof readbuffer); 
    readbuffer[nbytes] = 0; 
    fprintf(stderr, "Received string: %s\n", readbuffer); 

    return 0; 
} 
+0

¡Agradable! No estaba compilando con -Wall, tienes razón. Normalmente lo hago, y también arreglo férulas-advertencias débiles, pero esto fue solo un experimento, así que no estaba siendo tan completo. Me alegra ver que la respuesta era algo relativamente pequeño, en lugar de un error categórico como temía. Entonces, la respuesta es "sí", pero necesito más práctica con los detalles. Muchas gracias tu solución es muy buena! – Ziggy

+0

@Ziggy: Si te ayudó, estás invitado a aceptar esta respuesta :) –

+0

¡Sin duda lo haré! Tiendo a :) – Ziggy

0

Para arreglar la basura al final de la salida, agregue la siguiente línea antes del printf final.

readbuffer[nbytes] = 0; 

En cuanto al problema de colgar, necesito un poco más de reflexión para solucionarlo. Supongo que es algo relacionado con la fontanería y el almacenamiento en búfer.

Cuestiones relacionadas