Pregunta

Estoy interesado en escribir módulos de programa separados que se ejecuten como subprocesos independientes que pueda conectar con tuberías.La motivación sería que podría escribir y probar cada módulo de forma completamente independiente, tal vez incluso escribirlos en diferentes idiomas o ejecutar los diferentes módulos en diferentes máquinas.Aquí hay una amplia variedad de posibilidades.Llevo un tiempo utilizando tuberías, pero no estoy familiarizado con los matices de su comportamiento.

  • Parece que el extremo receptor bloqueará la espera de entrada, lo cual era de esperar, pero ¿el extremo emisor bloqueará a veces la espera de que alguien lea la transmisión?
  • Si escribo un eof en la secuencia, ¿puedo seguir escribiendo en esa secuencia hasta que la cierre?
  • ¿Existen diferencias en el comportamiento de las tuberías con nombre y sin nombre?
  • ¿Importa qué extremo de la tubería abro primero con tuberías con nombre?
  • ¿El comportamiento de las tuberías es consistente entre diferentes sistemas Linux?
  • ¿El comportamiento de las tuberías depende del shell que estoy usando o de la forma en que lo configuré?
  • ¿Hay alguna otra pregunta que deba hacer o problemas que deba tener en cuenta si quiero utilizar tuberías de esta manera?
¿Fue útil?

Solución

Vaya, eso es un montón de preguntas. Vamos a ver si puedo cubrir todo ...

  

Parece que el extremo receptor se   bloque de espera de entrada, que lo haría   esperar

Se espera correctamente una llamada real 'leer' se bloqueará hasta que algo está ahí. Sin embargo, creo que hay algunas funciones de C que le permitan a 'vistazo' a lo que (y cuánto) está esperando en la tubería. Por desgracia, no recuerdo si esto bloquea también.

  

será el bloque de extremo emisor veces   esperando que alguien lee desde el   corriente

No, el envío nunca debe bloquear. Piense en las consecuencias si se tratara de un tubo a través de la red a otro equipo. ¿Le gustaría que esperar (a través de, posiblemente, la alta latencia) para el otro equipo responda que lo recibió? Ahora bien, este es un caso diferente si el mango lector del destino ha sido cerrado. En este caso, usted debe tener la comprobación algún error de manejar eso.

  

Si escribo un EOF a la corriente que puede   mantener a seguir escribiendo a esa corriente   hasta que lo cierro

Me gustaría pensar que esto depende de qué idioma se está utilizando y la aplicación de las tuberías. En C, yo diría que no. En una cáscara de Linux, yo diría que sí. Otra persona con más experiencia tendría que responder a eso.

  

¿Hay diferencias en el comportamiento   nombrado y tuberías sin nombre?   Por lo que yo sé, sí. Sin embargo, no tengo mucha experiencia con el nombre vs sin nombre. Creo que la diferencia es:

  • dirección individual vs comunicación bidireccional
  • leer y escribir en el "en" y "corrientes" fuera de un hilo
  

¿Importa qué extremo de la tubería que   abrir primero con canalizaciones con nombre?

En general no, pero usted podría tener problemas en la inicialización tratando de crear y vincular los hilos entre sí. Que había necesidad de tener un hilo principal que crea todas las sub-hilos y sincroniza sus respectivos tubos entre sí.

  

es el comportamiento de los tubos consistentes   entre los diferentes sistemas Linux?

Una vez más, esto depende de qué idioma, pero en general sí. Has oído hablar de POSIX? Esa es la norma (al menos para Linux, Windows hace su propia cosa).

  

¿El comportamiento de las tuberías depende   en la cáscara que estoy usando o la forma en que he   configurado?

Esto se está poniendo en un poco más de un área gris. La respuesta debe haber puesto que el depósito debe ser esencialmente haciendo llamadas al sistema. Sin embargo, todo lo que hasta ese momento está en el aire.

  

¿Hay otras preguntas que deben   estar preguntando

Las preguntas que has realizado muestra que usted tiene una idea bastante acertada del sistema. Seguir investigando y se centran en el nivel que va a estar trabajando en (cáscara, C, etc.). Vas a aprender mucho más con sólo probarlo sin embargo.

Otros consejos

Todo esto se basa en un sistema de tipo UNIX; No estoy familiarizado con el comportamiento específico de las versiones recientes de Windows.

Parece que el extremo receptor bloqueará la espera de la entrada, lo cual esperaría, sino que el bloque extremo emisor veces esperando que alguien lee de la corriente?

Sí, aunque en una máquina moderna que no puede suceder a menudo. El tubo tiene un tampón intermedio que puede potencialmente llenar. Si lo hace, el lado de escritura de la tubería será de hecho bloquear. Pero si se piensa en ello, no hay una gran cantidad de archivos que son lo suficientemente grande como para arriesgarse a esto.

Si escribo un EOF a la corriente puedo evitar seguir escribiendo a esa corriente hasta que lo cierro?

Um, quieres decir como un CTRL-D, 0x04? Claro, siempre y cuando la corriente está configurado de esa manera. A saber.

506 # cat | od -c
abc
^D
efg
0000000    a   b   c  \n 004  \n   e   f   g  \n                        
0000012

¿Hay diferencias en el comportamiento con nombre y tuberías sin nombre?

Sí, pero son sutiles y dependiente de la implementación. El más grande es que se puede escribir en una tubería con nombre antes de que el otro extremo está en ejecución; con tuberías sin nombre, los descriptores de archivos que se comparten durante el proceso de tenedor / exec, así que no hay manera de acceder a la memoria intermedia transitoria sin los procesos de estar arriba.

¿Importa qué extremo de la tubería abro primero con canalizaciones con nombre?

Nop.

¿Es el comportamiento de los tubos consistentes entre diferentes sistemas Linux?

Dentro de lo razonable, sí. Buffer tamaños etc pueden variar.

¿El comportamiento de los tubos depende de la cáscara que estoy usando o la forma en que he configurado?

No. Cuando se crea una tubería, bajo las sábanas lo que sucede es que su proceso padre (la cáscara) crea una tubería que tiene un par de descriptores de archivo, a continuación, hace un ejecutivo de tenedor como esto pseudocódigo:

principal

create pipe, returning two file descriptors, call them fd[0] and fd[1]
fork write-side process
fork read-side process

Escribir lado

close fd[0]
connect fd[1] to stdout
exec writer program

Leer del lado

close fd[1]
connect fd[0] to stdin
exec reader program

¿Hay alguna otra pregunta que se debería hacer o cuestiones que debería tener en cuenta si quiero usar tubos de esta manera?

Es todo lo que quieres hacer realmente va a sentar en una línea como esta? Si no es así, es posible que desee pensar en una arquitectura más general. Pero la idea de que tener un montón de procesos separados que interactúan a través de la interfaz "estrecho" de un tubo es deseable es una buena.

[Actualizado: tenía los índices de descriptores de archivos invertidos en un principio. Son correcta ahora, consulte man 2 pipe.]

Como señalaron Dashogun y Charlie Martin, ésta es una gran pregunta.Algunas partes de sus respuestas son inexactas, así que yo también responderé.

Estoy interesado en escribir módulos de programa separados que se ejecuten como subprocesos independientes que pueda conectar con tuberías.

Tenga cuidado al intentar utilizar tuberías como mecanismo de comunicación entre subprocesos de un solo proceso.Debido a que tendría abiertos los extremos de lectura y escritura de la tubería en un solo proceso, nunca obtendría la indicación EOF (cero bytes).

Si realmente te referías a procesos, entonces esta es la base del enfoque clásico de Unix para crear herramientas.Muchos de los programas estándar de Unix son filtros que leen la entrada estándar, la transforman de alguna manera y escriben el resultado en la salida estándar.Por ejemplo, tr, sort, grep, y cat son todos filtros, por nombrar sólo algunos.Este es un excelente paradigma a seguir cuando los datos que estás manipulando lo permiten.No todas las manipulaciones de datos conducen a este enfoque, pero hay muchas que sí lo son.

La motivación sería que podría escribir y probar cada módulo de forma completamente independiente, tal vez incluso escribirlos en diferentes idiomas o ejecutar los diferentes módulos en diferentes máquinas.

Buenos puntos.Tenga en cuenta que en realidad no existe un mecanismo de tubería entre máquinas, aunque puede acercarse a él con programas como rsh o mejor) ssh.Sin embargo, internamente, dichos programas pueden leer datos locales de tuberías y enviar esos datos a máquinas remotas, pero se comunican entre máquinas a través de sockets, sin utilizar tuberías.

Aquí hay una amplia variedad de posibilidades.Llevo un tiempo utilizando tuberías, pero no estoy familiarizado con los matices de su comportamiento.

DE ACUERDO;hacer preguntas es una (buena) manera de aprender.Experimentar es otra, por supuesto.

Parece que el extremo receptor bloqueará la espera de entrada, lo cual era de esperar, pero ¿el extremo emisor bloqueará a veces la espera de que alguien lea la transmisión?

Sí.Hay un límite para el tamaño de una zona de influencia de tubería.Clásicamente, esto era bastante pequeño: 4096 o 5120 eran valores comunes.Es posible que descubra que Linux moderno utiliza un valor mayor.Puedes usar fpathconf() y _PC_PIPE_BUF para averiguar el tamaño de un búfer de tubería.POSIX solo requiere que el búfer sea 512 (es decir, _POSIX_PIPE_BUF es 512).

Si escribo un eof en la secuencia, ¿puedo seguir escribiendo en esa secuencia hasta que la cierre?

Técnicamente, no hay forma de escribir EOF en una secuencia;cierra el descriptor de tubería para indicar EOF.Si está pensando en control-D o control-Z como un carácter EOF, entonces esos son solo caracteres normales en lo que respecta a las canalizaciones; solo tienen un efecto como EOF cuando se escriben en una terminal que se ejecuta en modo canónico (preparado). , o normal).

¿Existen diferencias en el comportamiento de las tuberías con nombre y sin nombre?

Si y no.Las mayores diferencias son que las canalizaciones sin nombre deben ser configuradas por un proceso y solo pueden ser utilizadas por ese proceso y por los hijos que comparten ese proceso como un ancestro común.Por el contrario, las canalizaciones con nombre pueden ser utilizadas por procesos no asociados previamente.La siguiente gran diferencia es consecuencia de la primera;con una canalización sin nombre, obtienes dos descriptores de archivos de una sola llamada de función (sistema) a pipe(), pero abres una FIFO o una canalización con nombre usando el método normal open() función.(Alguien debe crear un FIFO con el mkfifo() llame antes de poder abrirlo;Las canalizaciones sin nombre no necesitan ninguna configuración previa). Sin embargo, una vez que tiene un descriptor de archivo abierto, hay muy poca diferencia entre una canalización con nombre y una canalización sin nombre.

¿Importa qué extremo de la tubería abro primero con tuberías con nombre?

No.El primer proceso que abra el FIFO (normalmente) se bloqueará hasta que haya un proceso con el otro extremo abierto.Si lo abres para leer y escribir (algo convencional pero posible), no serás bloqueado;Si usas la bandera O_NONBLOCK, no serás bloqueado.

¿El comportamiento de las tuberías es consistente entre diferentes sistemas Linux?

Sí.No he oído hablar ni he experimentado ningún problema con las tuberías en ninguno de los sistemas donde las he usado.

¿El comportamiento de las tuberías depende del shell que estoy usando o de la forma en que lo configuré?

No:Las tuberías y los FIFO son independientes del shell que utilice.

¿Hay alguna otra pregunta que deba hacer o problemas que deba tener en cuenta si quiero utilizar tuberías de esta manera?

Sólo recuerda que debes cerrar el extremo de lectura de una tubería en el proceso que estará escribiendo, y el extremo de escritura de la tubería en el proceso que estará leyendo.Si desea comunicación bidireccional a través de tuberías, utilice dos tuberías separadas.Si crea arreglos de plomería complicados, tenga cuidado con el punto muerto: es posible.Sin embargo, una canalización lineal no se bloquea (aunque si el primer proceso nunca cierra su salida, los procesos posteriores pueden esperar indefinidamente).


Observé tanto arriba como en los comentarios de otras respuestas que los amortiguadores de tuberías están clásicamente limitados a tamaños bastante pequeños.@Charlie Martin respondió que algunas versiones de Unix tienen buffers de canalización dinámicos y estos pueden ser bastante grandes.

No estoy seguro de cuáles tiene en mente.Utilicé el programa de prueba que sigue en Solaris, AIX, HP-UX, MacOS X, Linux y Cygwin/Windows XP (resultados a continuación):

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static const char *arg0;

static void err_syserr(char *str)
{
    int errnum = errno;
    fprintf(stderr, "%s: %s - (%d) %s\n", arg0, str, errnum, strerror(errnum));
    exit(1);
}

int main(int argc, char **argv)
{
    int pd[2];
    pid_t kid;
    size_t i = 0;
    char buffer[2] = "a";
    int flags;

    arg0 = argv[0];

    if (pipe(pd) != 0)
        err_syserr("pipe() failed");
    if ((kid = fork()) < 0)
        err_syserr("fork() failed");
    else if (kid == 0)
    {
        close(pd[1]);
        pause();
    }
    /* else */
    close(pd[0]);
    if (fcntl(pd[1], F_GETFL, &flags) == -1)
        err_syserr("fcntl(F_GETFL) failed");
    flags |= O_NONBLOCK;
    if (fcntl(pd[1], F_SETFL, &flags) == -1)
        err_syserr("fcntl(F_SETFL) failed");
    while (write(pd[1], buffer, sizeof(buffer)-1) == sizeof(buffer)-1)
    {
        putchar('.');
        if (++i % 50 ==  0)
            printf("%u\n", (unsigned)i);
    }
    if (i % 50 !=  0)
        printf("%u\n", (unsigned)i);
    kill(kid, SIGINT);
    return 0;
}

Me gustaría obtener resultados adicionales de otras plataformas.Aquí están los tamaños que encontré.Todos los resultados son mayores de lo que esperaba, debo confesar, pero es posible que Charlie y yo estemos debatiendo el significado de "bastante grande" cuando se trata de tamaños de buffer.

  • 8196 - HP-UX 11.23 para IA-64 (fcntl(F_SETFL) falló)
  • 16384 - Solaris 10
  • 16384 - MacOS X 10.5 (O_NONBLOCK no funcionó, aunque fcntl(F_SETFL) no falló)
  • 32768-AIX 5.3
  • 65536 - Cygwin/Windows XP (O_NONBLOCK no funcionó, aunque fcntl(F_SETFL) no falló)
  • 65536 - SuSE Linux 10 (y CentOS) (fcntl(F_SETFL) falló)

Un punto que queda claro de estas pruebas es que O_NONBLOCK funciona con tuberías en algunas plataformas y no en otras.

El programa crea una tubería y se bifurca.El niño cierra el extremo de escritura del tubo y luego se duerme hasta que recibe una señal; eso es lo que hace la pausa().Luego, el padre cierra el extremo de lectura de la tubería y establece las banderas en el descriptor de escritura para que no se bloquee al intentar escribir en una tubería llena.Luego se repite, escribiendo un carácter a la vez e imprimiendo un punto por cada carácter escrito, y un conteo y una nueva línea cada 50 caracteres.Cuando detecta un problema de escritura (búfer lleno, ya que el niño no está leyendo nada), detiene el ciclo, escribe el recuento final y mata al niño.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top