Cómo funcionan las tuberías en Linux

25

He estado leyendo sobre cómo se implementan las canalizaciones en el kernel de Linux y quería validar mi comprensión. Si soy incorrecto, se seleccionará la respuesta con la explicación correcta.

  • Linux tiene un VFS llamado pipefs que está montado en el kernel (no en el espacio del usuario)
  • pipefs tiene un súper bloque único y está montado en su propia raíz ( pipe:), junto a/
  • pipefs no se puede ver directamente a diferencia de la mayoría de los sistemas de archivos
  • La entrada a pipefs es a través de pipe(2)syscall
  • La pipe(2)llamada al sistema utilizada por los shells para canalizar con el |operador (o manualmente desde cualquier otro proceso) crea un nuevo archivo en pipefs que se comporta más o menos como un archivo normal
  • El archivo en el lado izquierdo del operador de tubería se stdoutredirige al archivo temporal creado en pipefs
  • El archivo en el lado derecho del operador de tubería tiene su stdinconjunto en el archivo en pipefs
  • pipefs se almacena en la memoria y, a través de la magia del kernel, no se debe paginar

¿Es esta explicación de cómo funcionan las tuberías (p ls -la | less. Ej. ) Bastante correcta?

Una cosa que no entiendo es cómo algo como bash establecería un proceso ' stdino stdoutal descriptor de archivo devuelto por pipe(2). Todavía no he podido encontrar nada al respecto.

Brandon Wamboldt
fuente
Tenga en cuenta que está hablando de dos capas de cosas considerablemente diferentes con el mismo nombre. La pipe()llamada del kernel junto con la maquinaria que lo soporta ( pipefs, etc.) es de un nivel mucho más bajo que el |operador ofrecido en su shell. Este último generalmente se implementa utilizando el primero, pero no tiene que ser así.
Greg Hewgill
Sí, me refiero específicamente a las operaciones de nivel inferior, con la suposición de que el |operador solo está llamando pipe(2)como un proceso como lo hace bash.
Brandon Wamboldt

Respuestas:

19

Su análisis hasta ahora es generalmente correcto. La forma en que un shell puede establecer el stdin de un proceso en un descriptor de tubería podría ser (pseudocódigo):

pipe(p) // create a new pipe with two handles p[0] and p[1]
fork() // spawn a child process
    close(p[0]) // close the write end of the pipe in the child
    dup2(p[1], 0) // duplicate the pipe descriptor on top of fd 0 (stdin)
    close(p[1]) // close the other pipe descriptor
    exec() // run a new process with the new descriptors in place
Greg Hewgill
fuente
¡Gracias! ¿Es curioso por qué dup2se necesita la llamada y no puede asignar directamente el descriptor de tubería a stdin?
Brandon Wamboldt
3
La persona que llama no puede elegir cuál es el valor numérico del descriptor de archivo cuando se crea pipe(). La dup2()llamada permite a la persona que llama copiar el descriptor de archivo a un valor numérico específico (necesario porque 0, 1, 2 son stdin, stdout, stderr). Ese es el equivalente del núcleo de "asignar directamente a stdin". Tenga en cuenta que la variable global de la biblioteca de tiempo de ejecución C stdines a FILE *, que no está relacionada con el núcleo (aunque se inicializa para conectarse al descriptor 0).
Greg Hewgill
¡Gran respuesta! Estoy un poco perdido en los detalles. ¿Me pregunto por qué cierras (p [1]) antes de ejecutar exec ()? Una vez que dup2 regrese, ¿no apuntaría p [1] a fd 0? Luego close (p [1]) cierra el descriptor de archivo 0. Entonces, ¿cómo podemos leer el stdin del proceso hijo?
user1559897
@ user1559897: la dup2llamada no cambia p[1]. En su lugar, hace los dos identificadores p[1]y 0apuntan al mismo objeto del núcleo (la tubería). Dado que el proceso secundario no necesita dos identificadores estándar (y de p[1]todos modos no sabría cuál es el identificador numerado ), p[1]se cierra antes exec.
Greg Hewgill
@GregHewgill Gotchu. ¡Gracias!
user1559897