2010-07-24 12 views
7

Esto parece ser una cosa bastante común de hacer, y he logrado enseñarme todo lo que necesito para que funcione, excepto que ahora tengo un solo problema, que está desafiando mi solución de problemas.C fork/exec con tubería sin bloqueo IO

int nonBlockingPOpen(char *const argv[]){ 
    int inpipe; 
    pid_t pid; 
    /* open both ends of pipe nonblockingly */ 
    pid = fork(); 

    switch(pid){ 
     case 0:   /*child*/ 
      sleep(1); /*child should open after parent has open for reading*/ 

      /*redirect stdout to opened pipe*/ 
      int outpipe = open("./fifo", O_WRONLY); 
      /*SHOULD BLOCK UNTIL MAIN PROCESS OPENS FOR WRITING*/ 
      dup2(outpipe, 1); 
      fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK); 

      printf("HELLO WORLD I AM A CHILD PROCESS\n"); 
      /*This seems to be written to the pipe immediately, blocking or not.*/ 
      execvp(*argv, argv); 
      /*All output from this program, which outputs "one" sleeps for 1 second 
      *outputs "two" sleeps for a second, etc, is captured only after the 
      *exec'd program exits! 
      */ 
      break; 

     default:  /*parent*/ 
      inpipe = open("./fifo", O_RDONLY | O_NONBLOCK); 
      sleep(2); 
      /*no need to do anything special here*/ 
      break; 
    } 

    return inpipe; 
} 

¿Por qué el proceso secundario no escribirá su stdout en la tubería cada vez que se genere una línea? ¿Hay algo que me falta en la forma en que funcionan execvp o dup2? Soy consciente de que mi enfoque de todo esto es un poco extraño, pero no puedo encontrar otra forma de capturar la salida de los binarios de código cerrado programáticamente.

+0

acabo de encontrar http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/dont-set-shared-file-descriptors-to-non-blocking-mode.html esta página, lo que indica que no debería intentar pasar un proceso secundario a un descriptor que no sea de bloqueo, pero ninguno de los motivos enumerados * parece * aplicarse a lo que estoy haciendo. Me doy cuenta de que el programa que estoy ejecutando probablemente fallaría si el proceso de escucha se desconectara de la tubería (generando EAGAIN en el niño), pero eso no me molesta. – conartist6

Respuesta

3

Supongo que solo obtendrá la salida del programa ejecutado después de que se cierra porque no se llama flush después de cada mensaje. Si es así, no hay nada que puedas hacer desde el exterior.

No estoy muy seguro de cómo se supone que esto se relaciona con la elección entre E/S de bloqueo y sin bloqueo en su pregunta. Una escritura sin bloqueo puede fallar total o parcialmente: en lugar de bloquear el programa hasta que haya espacio disponible en la tubería, la llamada vuelve inmediatamente y dice que no pudo escribir todo lo que debería tener. La E/S sin bloqueo no hace que el búfer sea más grande ni obliga a que la salida se vacíe, y puede ser mal soportado por algunos programas.

No puede forzar el programa binario solo que está ejecutando ejecutar. Si pensabas que la E/S sin bloqueo era una solución a ese problema, lo siento, pero me temo que es bastante ortogonal.

EDITAR: Bueno, si el programa ejecutado solo utiliza el almacenamiento en búfer provisto por libc (no implementa el suyo) y está vinculado dinámicamente, puede forzarlo a enjuagar vinculándolo contra una libc modificada que vacía cada escritura . Esta sería una medida desesperada. para probar solo si todo lo demás falló.

+0

Acepto su solución y la amplío con la mía. Al parecer, no fui informado sobre el número completo y el tipo de búferes que están en funcionamiento en esta situación. http://www.pixelbeat.org/programming/stdio_buffering/ me llenó de manera excelente. Tuviste razón al decir que la causa de la falla fue que el buffer de línea no se descargó hasta la finalización del programa, sin embargo, la página enlazada detallaba muchas soluciones posibles para cambiar la forma de almacenamiento intermedio utilizado por stdio.El método que estoy usando por ahora es simplemente llamar a unbuffer antes de mi comando. No estoy seguro de cuán eficiente es eso, ¡pero funciona! – conartist6

1

¿Por qué el proceso secundario no escribe su stdout en la tubería cada vez que se genera una línea?

¿Cómo lo sabes? Ni siquiera intentas leer la salida de la fifo.

N.B. por el nombre del archivo supongo que está utilizando el fifo. ¿O es un archivo simple?

Y el error menor en el niño: después de dup2(), necesita close(outpipe).

fcntl (1, F_SETFL, fcntl (1, F_GETFL) | O_NONBLOCK);

Dependiendo del programa que ejecute(), puede perder alguna salida o hacer que el programa falle ya que escribir en stdout ahora podría fallar con EWOULDBLOCK.

IIRC fifos tiene el mismo tamaño de buffer que las tuberías. Por mínimo POSIX es 512 bytes, comúnmente 4K o 8K.

Es probable que desee explicar por qué necesita eso en absoluto. El IO no bloqueante tiene una semántica diferente en comparación con el IO de bloqueo y, a menos que el proceso secundario espere que se encuentre con varios problemas.

printf ("HELLO WORLD SOY UN PROCESO INFANTIL \ n");

stdout está almacenado en el búfer, tendría después de eso fflush(stdout). (No se puede encontrar la documentación de si exec() por sí solo vaciaría stdout o no).

¿Hay algo que me falta en la forma en que funcionan execvp o dup2? Soy consciente de que mi enfoque de todo esto es un poco extraño, pero no puedo encontrar otra forma de capturar la salida de los binarios de código cerrado programáticamente.

No jugaría con E/S sin bloqueo y lo dejaré como está en modo bloqueo.

Y yo usaría pipe() en lugar de fifo. El man pipe de Linux tiene un ejemplo conveniente con la horquilla().

De lo contrario, esa es una práctica bastante normal.

+0

Lo sentimos, no incluí todo el programa aquí, solo la función que abre el tubo para leer. El archivo es de hecho un nombre fifo. El problema no estaba en el código que está en ese código, sino que es que el bloque ejecute el código (un servidor) que se ejecutará indefinidamente, produciendo solo salidas ocasionales (no lo suficiente para llenar un buffer si todo va bien). Además, tiene razón, parece que después de * todo * esto, está perfectamente bien si los bloques de IO del niño, y para ese caso, probablemente incluso los de los padres también. Gracias. – conartist6

+0

@ conartist6: la bandera IO no bloqueante tiene un efecto totalmente diferente para los descriptores de archivos. pero igualmente destructivo de lógica de programa. o ningún efecto en absoluto. dependiendo del programa. http://linux.die.net/man/2/fcntl - y busca O_NONBLOCK. simplemente quítalo. – Dummy00001

3

Cuando se inicia un proceso (a través de execvp() en su ejemplo), el comportamiento de la salida estándar depende de si el dispositivo de salida es un terminal o no. Si no lo es (y un FIFO no es un terminal), entonces la salida estará completamente almacenada en el búfer, en lugar de la línea almacenada. No hay nada que puedas hacer al respecto; la biblioteca C (Estándar) hace eso.

Si realmente desea que funcione la línea en búfer, tendrá que proporcionar al programa un pseudo-terminal como salida estándar. Eso entra en reinos interesantes: los pseudo terminales o ptys no son tan fáciles de manejar. Para las funciones POSIX, consulte:

  • grantpt() - permitir el acceso al dispositivo pseudo-terminal esclavo
  • posix_openpt() - abrir un dispositivo pseudo-terminal
  • ptsname() - obtiene el nombre del dispositivo pseudo-terminal esclavo
  • unlockpt() - desbloquear un par maestro/esclavo pseudo-terminal
0

Los sleep() s hacen no garantiza que el padre abrirá primero la tubería, como dice Dummy00001, debe usar una tubería pipe(), no una tubería con nombre. También debe verificar si el execvp() y el fork() fallan, y no debe configurar el lado del niño para que no sea bloqueante; esa es una decisión que debe tomar el proceso secundario.

int nonBlockingPOpen(char *const argv[]) 
{ 
    int childpipe[2]; 
    pid_t pid; 

    pipe(childpipe); 
    pid = fork(); 

    if (pid == 0) 
    { 
     /*child*/ 

     /*redirect stdout to opened pipe*/ 
     dup2(childpipe[1], 1); 

     /* close leftover pipe file descriptors */ 
     close(childpipe[0]); 
     close(childpipe[1]); 

     execvp(*argv, argv); 

     /* Only reached if execvp fails */ 
     perror("execvp"); 
     exit(1); 
    } 

    /*parent*/ 

    /* Close leftover pipe file descriptor */ 
    close(childpipe[1]); 

    /* Check for fork() failing */ 
    if (pid < 0) 
    { 
     close(childpipe[0]); 
     return -1; 
    } 

    /* Set file descriptor non-blocking */ 
    fcntl(childpipe[0], F_SETFL, fcntl(childpipe[0], F_GETFL) | O_NONBLOCK); 

    return childpipe[0]; 
}