Comprender los comandos canalizados en Unix / Linux

16

Tengo dos programas simples: Ay B. Acorrería primero, luego Bobtiene el "stdout" de Ay lo usa como su "stdin". Suponga que estoy usando un sistema operativo GNU / Linux y la forma más simple de hacerlo sería:

./A | ./B

Si tuviera que describir este comando, diría que es un comando que toma información (es decir, lee) de un productor ( A) y escribe a un consumidor ( B). ¿Es esa una descripción correcta? ¿Me estoy perdiendo algo?

nihulus
fuente
Relacionado: ¿En qué orden se ejecutan los comandos canalizados?
G-Man dice 'Restablecer a Mónica' el
No es un comando, es un objeto kenerl creado por el proceso bash, que se utiliza como stdout del proceso A y stdin como B. Dos procesos se inician casi al mismo tiempo.
炸鱼 薯条 德里克
1
@ 炸鱼 Estás en lo correcto, porque la canalización del núcleo es un objeto en el sistema de archivos pipefs, pero en lo que respecta al shell, técnicamente es un comando de canalización
Sergiy Kolodyazhnyy

Respuestas:

26

Lo único sobre su pregunta que se destaca como incorrecto es que usted dice

A correría primero, luego B obtiene el stdout de A

De hecho, ambos programas se iniciarían casi al mismo tiempo. Si no hay entrada para Bcuando intenta leer, se bloqueará hasta que haya entrada para leer. Del mismo modo, si no hay nadie que lea la salida A, sus escrituras se bloquearán hasta que se lea su salida (parte de la tubería se amortiguará).

Lo único que sincroniza los procesos que participan en una tubería es la E / S, es decir, la lectura y escritura a través de la tubería. Si no ocurre escritura o lectura, entonces los dos procesos se ejecutarán de manera totalmente independiente el uno del otro. Si uno ignora la lectura o escritura del otro, el proceso ignorado se bloqueará y eventualmente será eliminado por una SIGPIPEseñal (si está escribiendo) u obtendrá una condición de fin de archivo en su flujo de entrada estándar (si está leyendo) cuando el otro proceso finalice .

La forma idiomática de describir A | Bes que es una tubería que contiene dos programas. La salida producida en la salida estándar del primer programa está disponible para ser leída en la entrada estándar por el segundo ("[la salida de] Ase canaliza a [la entrada de] B"). El shell realiza las tuberías necesarias para permitir que esto suceda.

Si desea utilizar las palabras "consumidor" y "productor", supongo que también está bien.

El hecho de que se trate de programas escritos en C no es relevante. El hecho de que esto sea Linux, macOS, OpenBSD o AIX no es relevante.

Kusalananda
fuente
2
Escribir en un archivo temporal se usó en DOS, ya que no admitía múltiples procesos.
CSM
2
@AlexVong Tenga en cuenta que su ejemplo con un archivo temporal no es exactamente equivalente. Un programa puede elegir buscar los contenidos de un archivo, pero los datos que salen de una tubería no son buscables. Un mejor examen sería usar mkfifopara crear una tubería con nombre, luego comenzar B en la lectura de fondo de la tubería y luego A escribir en ella. Sin embargo, esto es difícil, ya que el efecto sería el mismo.
Kusalananda
2
@AlexVong Las simplificaciones hechas en ese artículo lo separan de las tuberías reales; La ejecución paralela es verdaderamente semántica, no una optimización. Es una explicación razonable de mentiras a los niños de la evaluación o composición monádica para alguien que ha visto tuberías de proyectiles, pero no es válida en la otra dirección. La versión Fifo de Kusalananda está más cerca, pero las partes de propagación de errores del modelo son realmente importantes y no son replicables. (Todo lo cual digo como alguien que está muy en el tren de "las tuberías de shell son solo composición de funciones")
Michael Homer
66
@AlexVong No, eso está completamente fuera de lugar. Eso no puede explicar incluso algo tan simple como yes | sed 10q
tío Billy
1
@UncleBilly Estoy de acuerdo con tu ejemplo. Esto muestra que la ejecución paralela es realmente necesaria también notada por Michael. De lo contrario, obtendremos la no terminación.
Alex Vong
2

El término generalmente utilizado en la documentación es "canalización", que consiste en uno o más comandos, consulte la definición POSIX. Técnicamente hablando, son dos comandos que tiene allí, dos subprocesos para el shell ( fork()+exec()comandos externos o subcapas)

En cuanto a la parte productor-consumidor , la tubería se puede describir por ese patrón, ya que:

  • El productor y el consumidor comparten un búfer de tamaño fijo, y al menos en Linux y MacOS X, hay un tamaño fijo para el búfer de canalización
  • Productor y Consumidor están débilmente acoplados, los comandos en proceso no conocen la existencia del otro (a menos que estén revisando activamente el /proc/<pid>/fddirectorio).
  • Los productores escriben stdouty los consumidores leen stdincomo si se tratara de un solo comando en ejecución, es decir, pueden existir el uno sin el otro .

La diferencia que veo aquí es que, a diferencia de Producer-Consumer en otros idiomas, los comandos de shell usan el almacenamiento en búfer y escriben stdout una vez que se llena el búfer, pero no se menciona que Producer-Consumer tenga que seguir esa regla: solo espere cuando la cola esté llena o descarte datos (que es algo más que la tubería no hace).

Sergiy Kolodyazhnyy
fuente